From ac7b20cca2f7189f44fd3b86a9339d8f1763f17a Mon Sep 17 00:00:00 2001 From: SrdjanCosicPrica Date: Mon, 4 Mar 2019 14:46:14 +0100 Subject: [PATCH 001/907] Fix get_search_fields example (#6487) --- docs/api-guide/filtering.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index aff267818b..8a500f386f 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -220,10 +220,13 @@ By default, the search parameter is named `'search`', but this may be overridden 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) + from rest_framework import filters + + class CustomSearchFilter(filters.SearchFilter): + 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) For more details, see the [Django documentation][search-django-admin]. From dfc277cce669af8c574c5fd0ac0a3a432b6d45e6 Mon Sep 17 00:00:00 2001 From: Luoxzhg Date: Tue, 5 Mar 2019 18:50:13 +0800 Subject: [PATCH 002/907] Corrected tutorial 1 example renderer output to bytes. (#6486) --- docs/tutorial/1-serialization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index ec507df05f..07ee8f208d 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -150,7 +150,7 @@ At this point we've translated the model instance into Python native datatypes. content = JSONRenderer().render(serializer.data) content - # '{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}' + # b'{"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... From 9d06e43d05abf1ec57f15566b29ad53ac418ae05 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 5 Mar 2019 03:11:46 -0800 Subject: [PATCH 003/907] Replace type('') with six.text_type (#6482) As all source files import unicode_literals, type('') is always equivalent to six.text_type (str on Python 3 and unicode on Python 2). Removes the need to call type(), is more explicit, and will be easier to catch places to change for when it is time to eventually drop Python 2. --- rest_framework/fields.py | 4 ++-- rest_framework/relations.py | 2 +- tests/test_pagination.py | 9 +++++---- tests/test_validation.py | 3 ++- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index b5fafeaa33..c8f65db0e5 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1486,7 +1486,7 @@ def get_value(self, dictionary): return dictionary.get(self.field_name, empty) def to_internal_value(self, data): - if isinstance(data, type('')) or not hasattr(data, '__iter__'): + if isinstance(data, six.text_type) 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') @@ -1660,7 +1660,7 @@ def to_internal_value(self, data): """ if html.is_html_input(data): data = html.parse_html_list(data, default=[]) - if isinstance(data, (type(''), Mapping)) or not hasattr(data, '__iter__'): + if isinstance(data, (six.text_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') diff --git a/rest_framework/relations.py b/rest_framework/relations.py index e8a4ec2ac3..31c1e75618 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -518,7 +518,7 @@ def get_value(self, dictionary): return dictionary.get(self.field_name, empty) def to_internal_value(self, data): - if isinstance(data, type('')) or not hasattr(data, '__iter__'): + if isinstance(data, six.text_type) 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') diff --git a/tests/test_pagination.py b/tests/test_pagination.py index d9ad9e6f6c..6d940fe2b0 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -5,6 +5,7 @@ from django.core.paginator import Paginator as DjangoPaginator from django.db import models from django.test import TestCase +from django.utils import six from rest_framework import ( exceptions, filters, generics, pagination, serializers, status @@ -207,7 +208,7 @@ def test_no_page_number(self): ] } assert self.pagination.display_page_controls - assert isinstance(self.pagination.to_html(), type('')) + assert isinstance(self.pagination.to_html(), six.text_type) def test_second_page(self): request = Request(factory.get('/', {'page': 2})) @@ -313,7 +314,7 @@ def test_no_page_number(self): ] } assert not self.pagination.display_page_controls - assert isinstance(self.pagination.to_html(), type('')) + assert isinstance(self.pagination.to_html(), six.text_type) def test_invalid_page(self): request = Request(factory.get('/', {'page': 'invalid'})) @@ -368,7 +369,7 @@ def test_no_offset(self): ] } assert self.pagination.display_page_controls - assert isinstance(self.pagination.to_html(), type('')) + assert isinstance(self.pagination.to_html(), six.text_type) def test_pagination_not_applied_if_limit_or_default_limit_not_set(self): class MockPagination(pagination.LimitOffsetPagination): @@ -631,7 +632,7 @@ def test_cursor_pagination(self): assert current == [1, 1, 1, 1, 1] assert next == [1, 2, 3, 4, 4] - assert isinstance(self.pagination.to_html(), type('')) + assert isinstance(self.pagination.to_html(), six.text_type) def test_cursor_pagination_with_page_size(self): (previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=20') diff --git a/tests/test_validation.py b/tests/test_validation.py index 8b71693c52..4132a7b00f 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -5,6 +5,7 @@ from django.core.validators import MaxValueValidator, RegexValidator from django.db import models from django.test import TestCase +from django.utils import six from rest_framework import generics, serializers, status from rest_framework.test import APIRequestFactory @@ -111,7 +112,7 @@ def test_serializer_errors_has_only_invalid_data_error(self): assert not serializer.is_valid() assert serializer.errors == { 'non_field_errors': [ - 'Invalid data. Expected a dictionary, but got %s.' % type('').__name__ + 'Invalid data. Expected a dictionary, but got %s.' % six.text_type.__name__ ] } From 9e1e32f678fad5b0222b4cec092abfd34c2d8fa0 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Thu, 7 Mar 2019 15:22:00 +0600 Subject: [PATCH 004/907] upgraded pytest dependencies (#6492) --- requirements/requirements-testing.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index fbddc4f205..a2a2fa7532 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -1,4 +1,4 @@ # Pytest for running the tests. -pytest==3.6.2 -pytest-django==3.3.2 -pytest-cov==2.5.1 +pytest==4.3.0 +pytest-django==3.4.8 +pytest-cov==2.6.1 From fd32dd7ca4fa584efc31e1d913bbca7ffbd1c586 Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Thu, 7 Mar 2019 01:44:20 -0800 Subject: [PATCH 005/907] Explicitly raise exc in 'raise_uncaught_exception' (#6435) --- rest_framework/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/views.py b/rest_framework/views.py index 04951ed93d..9d5d959e9d 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -463,7 +463,7 @@ def raise_uncaught_exception(self, exc): renderer_format = getattr(request.accepted_renderer, 'format') use_plaintext_traceback = renderer_format not in ('html', 'api', 'admin') request.force_plaintext_errors(use_plaintext_traceback) - raise + raise exc # Note: Views are made CSRF exempt from within `as_view` as to prevent # accidental removal of this exemption in cases where `dispatch` needs to From 86c72bb2268ad06c503de4fb7caa46c9e495f8d8 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Thu, 7 Mar 2019 05:26:03 -0500 Subject: [PATCH 006/907] Fix schema generation of `ManyRelatedField` to detect the child type (#6489) * Introspect ManyRelatedField data type recursively For all `ManyRelatedField` objects, we were assuming that the inner type was always a `String`. While this may be true for the default output, a `ManyRelatedField` is a wrapper for a lot of other classes which includes more than just strings. This should allow us to document lists of things other than strings. * Added test for schemas for many-to-many fields This adds a test that makes sure we generate the schema for a many-to-many field such that it actually has the right type. For some reason we did not previously have any tests for schema generation that included them, so hopefully this will prevent any future issues from popping up. This should serve as a regression test for the `items` field on to-many relationships, which was previously forced to a `String` even though in most cases it is a different inner type within the array. --- rest_framework/schemas/inspectors.py | 4 ++- tests/test_schemas.py | 47 +++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/rest_framework/schemas/inspectors.py b/rest_framework/schemas/inspectors.py index a17a1f1aac..85142edce4 100644 --- a/rest_framework/schemas/inspectors.py +++ b/rest_framework/schemas/inspectors.py @@ -51,8 +51,10 @@ def field_to_schema(field): description=description ) elif isinstance(field, serializers.ManyRelatedField): + related_field_schema = field_to_schema(field.child_relation) + return coreschema.Array( - items=coreschema.String(), + items=related_field_schema, title=title, description=description ) diff --git a/tests/test_schemas.py b/tests/test_schemas.py index d3bd430735..3cb9e0cda8 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -24,7 +24,7 @@ from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet, ModelViewSet -from .models import BasicModel, ForeignKeySource +from .models import BasicModel, ForeignKeySource, ManyToManySource factory = APIRequestFactory() @@ -701,6 +701,51 @@ def test_schema_for_regular_views(self): assert schema == expected +class ManyToManySourceSerializer(serializers.ModelSerializer): + class Meta: + model = ManyToManySource + fields = ('id', 'name', 'targets') + + +class ManyToManySourceView(generics.CreateAPIView): + queryset = ManyToManySource.objects.all() + serializer_class = ManyToManySourceSerializer + + +@unittest.skipUnless(coreapi, 'coreapi is not installed') +class TestSchemaGeneratorWithManyToMany(TestCase): + def setUp(self): + self.patterns = [ + url(r'^example/?$', ManyToManySourceView.as_view()), + ] + + def test_schema_for_regular_views(self): + """ + Ensure that AutoField many to many fields are output as Integer. + """ + generator = SchemaGenerator(title='Example API', patterns=self.patterns) + schema = generator.get_schema() + + expected = coreapi.Document( + url='', + title='Example API', + content={ + 'example': { + 'create': coreapi.Link( + url='/example/', + action='post', + encoding='application/json', + fields=[ + coreapi.Field('name', required=True, location='form', schema=coreschema.String(title='Name')), + coreapi.Field('targets', required=True, location='form', schema=coreschema.Array(title='Targets', items=coreschema.Integer())), + ] + ) + } + } + ) + assert schema == expected + + @unittest.skipUnless(coreapi, 'coreapi is not installed') class Test4605Regression(TestCase): def test_4605_regression(self): From bcdfcf7e493663d8738b9f07d59f9426b0cfb5a3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 7 Mar 2019 11:00:50 +0000 Subject: [PATCH 007/907] Sponsor updates (#6495) --- README.md | 10 ++++------ docs/img/premium/auklet-readme.png | Bin 48745 -> 0 bytes docs/img/premium/release-history.png | Bin 0 -> 18009 bytes docs/index.md | 6 ++---- 4 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 docs/img/premium/auklet-readme.png create mode 100644 docs/img/premium/release-history.png diff --git a/README.md b/README.md index 0309ee2bdb..bb05b1d924 100644 --- a/README.md +++ b/README.md @@ -19,17 +19,15 @@ continued development by [signing up for a paid plan][funding]. The initial aim is to provide a single full-time position on REST framework. *Every single sign-up makes a significant impact towards making that possible.* -[![][rover-img]][rover-url] [![][sentry-img]][sentry-url] [![][stream-img]][stream-url] [![][rollbar-img]][rollbar-url] [![][cadre-img]][cadre-url] -[![][load-impact-img]][load-impact-url] [![][kloudless-img]][kloudless-url] -[![][auklet-img]][auklet-url] +[![][release-history-img]][release-history-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], [Auklet][auklet-url], and [Lights On Software][lightson-url]. +Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Kloudless][kloudless-url], [Release History][release-history-url], and [Lights On Software][lightson-url]. --- @@ -201,7 +199,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou [cadre-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cadre-readme.png [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 +[release-history-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/release-history-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/ @@ -211,7 +209,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou [cadre-url]: https://cadre.com/ [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/ +[release-history-url]: https://releasehistory.io [lightson-url]: https://lightsonsoftware.com [oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth diff --git a/docs/img/premium/auklet-readme.png b/docs/img/premium/auklet-readme.png deleted file mode 100644 index f55f7a70ea7e8a5b9ec6a9de62246606c21f1136..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48745 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 MNn5c&!6xGW08Og_`v3p{ diff --git a/docs/img/premium/release-history.png b/docs/img/premium/release-history.png new file mode 100644 index 0000000000000000000000000000000000000000..b732b1ca23b8ce74f758ce03e90b4e0674ad425a GIT binary patch literal 18009 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 literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md index c74b2caf04..9f5d3fa153 100644 --- a/docs/index.md +++ b/docs/index.md @@ -66,19 +66,17 @@ continued development by **[signing up for a paid plan][funding]**. *Every single sign-up helps us make REST framework long-term financially sustainable.*
-*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).* +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Release History](https://releasehistory.io), [Rollbar](https://rollbar.com), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), and [Lights On Software](https://lightsonsoftware.com).* --- From 9bfb58746ef813fefa6c2528f4b26e462a8ffc1f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 7 Mar 2019 11:02:43 +0000 Subject: [PATCH 008/907] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb05b1d924..66079edf07 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou [cadre-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cadre-readme.png [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 -[release-history-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/release-history-readme.png +[release-history-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/release-history.png [lightson-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/lightson-readme.png [rover-url]: http://jobs.rover.com/ From 6f24c21cfb1fa072c7bf2f36b54a63b51102f903 Mon Sep 17 00:00:00 2001 From: Matt Hegarty Date: Tue, 12 Mar 2019 11:46:02 +0000 Subject: [PATCH 009/907] Fixed typo: /Janurary/January/ (#6506) --- docs/community/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 288bf6d589..0f08342f54 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -62,7 +62,7 @@ You can determine your currently installed version using `pip show`: ### 3.9.1 -**Date**: [16th Janurary 2019][3.9.1-milestone] +**Date**: [16th January 2019][3.9.1-milestone] * Resolve XSS issue in browsable API. [#6330][gh6330] * Upgrade Bootstrap to 3.4.0 to resolve XSS issue. From d2d1888217e8cc5aba995edf522fec903b1f91be Mon Sep 17 00:00:00 2001 From: Ryan Siemens Date: Tue, 12 Mar 2019 21:15:12 -0700 Subject: [PATCH 010/907] Document DateTimeField default_timezone argument (#6469) --- docs/api-guide/fields.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 74ce2251d7..ede4f15ad5 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -306,10 +306,11 @@ A date and time representation. Corresponds to `django.db.models.fields.DateTimeField`. -**Signature:** `DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)` +**Signature:** `DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None, default_timezone=None)` * `format` - A string representing the output format. If not specified, this defaults to the same value as the `DATETIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `datetime` objects should be returned by `to_representation`. In this case the datetime encoding will be determined by the renderer. * `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATETIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. +* `default_timezone` - A `pytz.timezone` representing the timezone. If not specified and the `USE_TZ` setting is enabled, this defaults to the [current timezone][django-current-timezone]. If `USE_TZ` is disabled, then datetime objects will be naive. #### `DateTimeField` format strings. @@ -835,3 +836,4 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide [django-rest-framework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore [django-hstore]: https://github.com/djangonauts/django-hstore [python-decimal-rounding-modes]: https://docs.python.org/3/library/decimal.html#rounding-modes +[django-current-timezone]: https://docs.djangoproject.com/en/stable/topics/i18n/timezones/#default-time-zone-and-current-time-zone From b25d245b893ce0a04c9ed33a12ebf6a6a1ff6b44 Mon Sep 17 00:00:00 2001 From: Patrickcai Date: Fri, 22 Mar 2019 20:29:45 +0800 Subject: [PATCH 011/907] Merge multiple isinstance() calls to one (#6513) * Merge multiple isinstance() calls to one See https://docs.python.org/3/library/functions.html#isinstance * Fix `)` mismatch Fix `)` mismatch --- rest_framework/utils/field_mapping.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index f11b4b94e6..927d08ff25 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -106,8 +106,7 @@ def get_field_kwargs(field_name, model_field): if model_field.null and not isinstance(model_field, models.NullBooleanField): kwargs['allow_null'] = True - if model_field.blank and (isinstance(model_field, models.CharField) or - isinstance(model_field, models.TextField)): + if model_field.blank and (isinstance(model_field, (models.CharField, models.TextField))): kwargs['allow_blank'] = True if isinstance(model_field, models.FilePathField): @@ -193,9 +192,7 @@ def get_field_kwargs(field_name, model_field): # Ensure that max_length is passed explicitly as a keyword arg, # rather than as a validator. max_length = getattr(model_field, 'max_length', None) - if max_length is not None and (isinstance(model_field, models.CharField) or - isinstance(model_field, models.TextField) or - isinstance(model_field, models.FileField)): + if max_length is not None and (isinstance(model_field, (models.CharField, models.TextField, models.FileField))): kwargs['max_length'] = max_length validator_kwarg = [ validator for validator in validator_kwarg From d784e4220762bd8efa6c99887ef1a1c43b51d52c Mon Sep 17 00:00:00 2001 From: Turfa Auliarachman Date: Tue, 26 Mar 2019 00:42:27 +0700 Subject: [PATCH 012/907] Fix `basename` deprecation warnings in tests (#6529) --- tests/test_renderers.py | 2 +- tests/test_routers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_renderers.py b/tests/test_renderers.py index b4c41b148a..60a0c0307d 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -636,7 +636,7 @@ def list_action(self, request): raise NotImplementedError router = SimpleRouter() - router.register('examples', ExampleViewSet, base_name='example') + router.register('examples', ExampleViewSet, basename='example') urlpatterns = [url(r'^api/', include(router.urls))] def setUp(self): diff --git a/tests/test_routers.py b/tests/test_routers.py index a3a731f939..cca2ea7122 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -121,7 +121,7 @@ def action3_delete(self, request, pk, *args, **kwargs): class TestSimpleRouter(URLPatternsTestCase, TestCase): router = SimpleRouter() - router.register('basics', BasicViewSet, base_name='basic') + router.register('basics', BasicViewSet, basename='basic') urlpatterns = [ url(r'^api/', include(router.urls)), From ac19c695396cdd0a72b81dc5fece7a850ae3d1b2 Mon Sep 17 00:00:00 2001 From: Jabi Date: Thu, 28 Mar 2019 11:45:13 +0100 Subject: [PATCH 013/907] Corrected typo in permissions docs. (#6540) --- docs/api-guide/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 6a1297e60f..901f810c5d 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -51,7 +51,7 @@ For example: --- **Note**: With the exception of `DjangoObjectPermissions`, the provided -permission classes in `rest_framework.permssions` **do not** implement the +permission classes in `rest_framework.permissions` **do not** implement the methods necessary to check object permissions. If you wish to use the provided permission classes in order to check object From 13b9b0fb98b58c867fa3ef625d75b6835b9c3b70 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Fri, 29 Mar 2019 02:19:06 +0600 Subject: [PATCH 014/907] Upgraded to Django 2.2rc1 on Tox (#6544) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4226f1a92a..776af3b6e7 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,7 @@ deps = django111: Django>=1.11,<2.0 django20: Django>=2.0,<2.1 django21: Django>=2.1,<2.2 - django22: Django>=2.2b1,<3.0 + django22: Django>=2.2rc1,<3.0 djangomaster: https://github.com/django/django/archive/master.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From f34a0a4e6a4de5d58c7abd365bd2343c228eb915 Mon Sep 17 00:00:00 2001 From: Matt Hegarty Date: Fri, 29 Mar 2019 06:32:25 +0000 Subject: [PATCH 015/907] Minor documentation fixes (#6543) --- docs/api-guide/schemas.md | 2 +- docs/tutorial/1-serialization.md | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 3d07ed6210..b09b1606e4 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -20,7 +20,7 @@ can render the schema into the commonly used YAML-based OpenAPI format. ## Quickstart -There are two different ways you can serve a schema description for you API. +There are two different ways you can serve a schema description for your API. ### Generating a schema with the `generateschema` management command diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 07ee8f208d..224ebf25b2 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -8,7 +8,7 @@ The tutorial is fairly in-depth, so you should probably get a cookie and a cup o --- -**Note**: The code for this tutorial is available in the [tomchristie/rest-framework-tutorial][repo] repository on GitHub. The completed implementation is also online as a sandbox version for testing, [available here][sandbox]. +**Note**: The code for this tutorial is available in the [encode/rest-framework-tutorial][repo] repository on GitHub. The completed implementation is also online as a sandbox version for testing, [available here][sandbox]. --- @@ -218,7 +218,6 @@ Edit the `snippets/views.py` file, and add the following. from django.http import HttpResponse, JsonResponse from django.views.decorators.csrf import csrf_exempt - from rest_framework.renderers import JSONRenderer from rest_framework.parsers import JSONParser from snippets.models import Snippet from snippets.serializers import SnippetSerializer From b1122a441aeab5664b43351378b63e5dd87ab7a5 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 1 Apr 2019 16:30:26 +0200 Subject: [PATCH 016/907] Update tox to use Django 2.2 final. (#6556) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 776af3b6e7..5d7a4987e3 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,7 @@ deps = django111: Django>=1.11,<2.0 django20: Django>=2.0,<2.1 django21: Django>=2.1,<2.2 - django22: Django>=2.2rc1,<3.0 + django22: Django>=2.2,<3.0 djangomaster: https://github.com/django/django/archive/master.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From cceb416098362a582132c1f9622e85d8775b894e Mon Sep 17 00:00:00 2001 From: jozo Date: Thu, 4 Apr 2019 11:31:08 +0200 Subject: [PATCH 017/907] Link DRF Condition (cache headers) third party package. (#6557) --- docs/community/third-party-packages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 0d36b8ee0a..ace54f6f70 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -263,6 +263,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [django-rest-messaging][django-rest-messaging], [django-rest-messaging-centrifugo][django-rest-messaging-centrifugo] and [django-rest-messaging-js][django-rest-messaging-js] - A real-time pluggable messaging service using DRM. * [djangorest-alchemy][djangorest-alchemy] - SQLAlchemy support for REST framework. * [djangorestframework-datatables][djangorestframework-datatables] - Seamless integration between Django REST framework and [Datatables](https://datatables.net). +* [django-rest-framework-condition][django-rest-framework-condition] - Decorators for managing HTTP cache headers for Django REST framework (ETag and Last-modified). [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework @@ -336,3 +337,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless [djangorest-alchemy]: https://github.com/dealertrack/djangorest-alchemy [djangorestframework-datatables]: https://github.com/izimobil/django-rest-framework-datatables +[django-rest-framework-condition]: https://github.com/jozo/django-rest-framework-condition From 29cbe574a384c3bcc09434a3a9c5ff0cb7576b99 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Sat, 6 Apr 2019 03:27:07 +0600 Subject: [PATCH 018/907] Fix DeprecationWarning in tests (#6551) --- tests/test_filters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_filters.py b/tests/test_filters.py index 088d25436d..a53fa192a1 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -172,11 +172,11 @@ class SearchListView(generics.ListAPIView): search_fields = ('$title', '$text') view = SearchListView.as_view() - request = factory.get('/', {'search': '^\w{3}$'}) + request = factory.get('/', {'search': r'^\w{3}$'}) response = view(request) assert len(response.data) == 10 - request = factory.get('/', {'search': '^\w{3}$', 'title_only': 'true'}) + request = factory.get('/', {'search': r'^\w{3}$', 'title_only': 'true'}) response = view(request) assert response.data == [ {'id': 3, 'title': 'zzz', 'text': 'cde'} From f8c4e5079ee580c354d75523882be184961c05ac Mon Sep 17 00:00:00 2001 From: Billy Rotich Date: Sat, 13 Apr 2019 15:02:19 +0200 Subject: [PATCH 019/907] Minor documentation fixes (#6581) --- docs/api-guide/serializers.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index e25053936b..e77e78c152 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -572,6 +572,8 @@ This option is a dictionary, mapping field names to a dictionary of keyword argu user.save() return user +Please keep in mind that, if the field has already been explicitly declared on the serializer class, then the `extra_kwargs` option will be ignored. + ## Relational fields When serializing model instances, there are a number of different ways you might choose to represent relationships. The default representation for `ModelSerializer` is to use the primary keys of the related instances. From 1ac0f63aa9a6ceed5e4221926929117528af2714 Mon Sep 17 00:00:00 2001 From: Dmitry Alimov Date: Sun, 21 Apr 2019 19:27:13 +0300 Subject: [PATCH 020/907] Fix private attributes ignore in documentation (#6601) --- docs/api-guide/serializers.md | 2 +- docs/community/3.0-announcement.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index e77e78c152..8c17adbaf9 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -965,7 +965,7 @@ The following class is an example of a generic serializer that can handle coerci def to_representation(self, obj): for attribute_name in dir(obj): attribute = getattr(obj, attribute_name) - if attribute_name('_'): + if attribute_name.startswith('_'): # Ignore private attributes. pass elif hasattr(attribute, '__call__'): diff --git a/docs/community/3.0-announcement.md b/docs/community/3.0-announcement.md index dc118d70cb..7a29b55542 100644 --- a/docs/community/3.0-announcement.md +++ b/docs/community/3.0-announcement.md @@ -523,7 +523,7 @@ The following class is an example of a generic serializer that can handle coerci def to_representation(self, obj): for attribute_name in dir(obj): attribute = getattr(obj, attribute_name) - if attribute_name('_'): + if attribute_name.startswith('_'): # Ignore private attributes. pass elif hasattr(attribute, '__call__'): From 95e28b2252b58e7d5d2e33ee5cb705029eb322c5 Mon Sep 17 00:00:00 2001 From: David Sanders Date: Sat, 27 Apr 2019 12:07:49 -0700 Subject: [PATCH 021/907] Fix typo in docs --- docs/api-guide/serializers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 8c17adbaf9..feb5651f71 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -626,7 +626,7 @@ The default implementation returns a serializer class based on the `serializer_f Called to generate a serializer field that maps to a relational model field. -The default implementation returns a serializer class based on the `serializer_relational_field` attribute. +The default implementation returns a serializer class based on the `serializer_related_field` attribute. The `relation_info` argument is a named tuple, that contains `model_field`, `related_model`, `to_many` and `has_through_model` properties. From bf9859de51b8856014848b3c5eac45ade67b34dd Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 29 Apr 2019 16:08:39 +0200 Subject: [PATCH 022/907] Adjust django-guardian check for PY2 compatible version. (#6613) --- rest_framework/compat.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 9422e6ad56..d61ca5dbba 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -168,7 +168,12 @@ def is_guardian_installed(): """ django-guardian is optional and only imported if in INSTALLED_APPS. """ - if six.PY2: + try: + import guardian + except ImportError: + guardian = None + + if six.PY2 and (not guardian or guardian.VERSION >= (1, 5)): # Guardian 1.5.0, for Django 2.2 is NOT compatible with Python 2.7. # Remove when dropping PY2. return False From 83d09c7bc53c6c0b79e400fe05e1aff6788c2b2d Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 29 Apr 2019 16:30:44 +0200 Subject: [PATCH 023/907] Update version and release notes for v3.9.3. --- docs/community/release-notes.md | 13 +++++++++++++ rest_framework/__init__.py | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 0f08342f54..b61c5fb951 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -42,6 +42,16 @@ You can determine your currently installed version using `pip show`: ### 3.9.2 +**Date**: [29th April 2019] + +This is the last Django REST Framework release that will support Python 2. +Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10. + +* Adjusted the compat check for django-guardian to allow the last guardian + version (v1.4.9) compatible with Python 2. [#6613][gh6613] + +### 3.9.2 + **Date**: [3rd March 2019][3.9.1-milestone] * Routers: invalidate `_urls` cache on `register()` [#6407][gh6407] @@ -2106,3 +2116,6 @@ For older release notes, [please see the version 2.x documentation][old-release- [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 + + +[gh6613]: https://github.com/encode/django-rest-framework/issues/6613 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 55c06982d9..53dc7bd47f 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ """ __title__ = 'Django REST framework' -__version__ = '3.9.2' +__version__ = '3.9.3' __author__ = 'Tom Christie' __license__ = 'BSD 2-Clause' __copyright__ = 'Copyright 2011-2019 Encode OSS Ltd' From 7f16ed772720509b476e3cc4208c01b3972fa99b Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 29 Apr 2019 16:33:07 +0200 Subject: [PATCH 024/907] Correct version number in release notes. --- docs/community/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index b61c5fb951..6fcb5bb6b3 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -40,7 +40,7 @@ You can determine your currently installed version using `pip show`: ## 3.9.x series -### 3.9.2 +### 3.9.3 **Date**: [29th April 2019] From 1a0a8dde00187994bd4206e9d74219f148afc1c6 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 30 Apr 2019 01:44:01 -0700 Subject: [PATCH 025/907] Correct misspelled module 'typing' (#6616) https://docs.python.org/3/library/typing.html --- tests/test_fields.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 12c936b229..42adedfed9 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -19,9 +19,9 @@ from rest_framework.fields import DjangoImageField, is_simple_callable try: - import typings + import typing except ImportError: - typings = False + typing = False # Tests for helper functions. @@ -93,11 +93,12 @@ class Meta: assert is_simple_callable(ChoiceModel().get_choice_field_display) - @unittest.skipUnless(typings, 'requires python 3.5') + @unittest.skipUnless(typing, 'requires python 3.5') def test_type_annotation(self): # The annotation will otherwise raise a syntax error in python < 3.5 - exec("def valid(param: str='value'): pass", locals()) - valid = locals()['valid'] + locals = {} + exec("def valid(param: str='value'): pass", locals) + valid = locals['valid'] assert is_simple_callable(valid) From 908236a5767430ca293c71f4c2cc95a8347b1edb Mon Sep 17 00:00:00 2001 From: Jithesh Eriyakkadan Janardhanan Date: Tue, 30 Apr 2019 18:01:17 +0530 Subject: [PATCH 026/907] Correct misspelled class name --- docs/api-guide/validators.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index 3b50442cc1..ab042ac034 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -100,7 +100,7 @@ The validator should be applied to *serializer classes*, like so: --- -**Note**: The `UniqueTogetherValidation` class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input. +**Note**: The `UniqueTogetherValidator` class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input. --- From 0407a0df8a16fdac94bbd08d49143a74a88001cd Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 30 Apr 2019 17:53:44 +0200 Subject: [PATCH 027/907] Dropped Python 2 compatibility. (#6615) Thanks to Jon Dufresne (@jdufresne) for review. Co-authored-by: Asif Saif Uddin Co-authored-by: Rizwan Mansuri --- .travis.yml | 5 +- README.md | 2 +- docs/api-guide/fields.md | 4 +- rest_framework/authentication.py | 7 +- .../management/commands/drf_create_token.py | 4 +- .../authtoken/migrations/0001_initial.py | 3 - .../migrations/0002_auto_20160226_1747.py | 3 - rest_framework/authtoken/models.py | 4 +- rest_framework/compat.py | 58 +---- rest_framework/decorators.py | 5 +- rest_framework/exceptions.py | 26 +- rest_framework/fields.py | 226 ++++++++---------- rest_framework/filters.py | 11 +- rest_framework/generics.py | 2 - rest_framework/metadata.py | 4 +- rest_framework/mixins.py | 12 +- rest_framework/negotiation.py | 6 +- rest_framework/pagination.py | 20 +- rest_framework/parsers.py | 15 +- rest_framework/permissions.py | 6 +- rest_framework/relations.py | 51 ++-- rest_framework/renderers.py | 17 +- rest_framework/request.py | 13 +- rest_framework/response.py | 14 +- rest_framework/reverse.py | 5 +- rest_framework/routers.py | 15 +- rest_framework/schemas/generators.py | 9 +- rest_framework/schemas/inspectors.py | 15 +- rest_framework/schemas/views.py | 4 +- rest_framework/serializers.py | 33 ++- rest_framework/settings.py | 7 +- rest_framework/status.py | 1 - rest_framework/templatetags/rest_framework.py | 11 +- rest_framework/test.py | 54 ++--- rest_framework/throttling.py | 6 +- rest_framework/urlpatterns.py | 2 - rest_framework/urls.py | 2 - rest_framework/utils/breadcrumbs.py | 2 - rest_framework/utils/encoders.py | 10 +- rest_framework/utils/field_mapping.py | 2 +- rest_framework/utils/formatting.py | 2 - rest_framework/utils/json.py | 3 - rest_framework/utils/mediatypes.py | 6 +- rest_framework/utils/representation.py | 6 +- rest_framework/utils/serializer_helpers.py | 18 +- rest_framework/utils/urls.py | 19 +- rest_framework/validators.py | 21 +- rest_framework/versioning.py | 11 +- rest_framework/views.py | 4 +- rest_framework/viewsets.py | 6 +- runtests.py | 4 +- setup.cfg | 3 - setup.py | 36 ++- .../authentication/migrations/0001_initial.py | 3 - tests/authentication/models.py | 3 - tests/authentication/test_authentication.py | 11 +- tests/browsable_api/auth_urls.py | 2 - tests/browsable_api/no_auth_urls.py | 2 - tests/browsable_api/test_browsable_api.py | 2 - .../test_browsable_nested_api.py | 2 - tests/browsable_api/views.py | 2 - tests/generic_relations/models.py | 6 - .../test_generic_relations.py | 2 - tests/models.py | 2 - tests/test_api_client.py | 2 - tests/test_atomic_requests.py | 4 +- tests/test_authtoken.py | 3 +- tests/test_bound_fields.py | 4 +- tests/test_decorators.py | 2 - tests/test_description.py | 9 +- tests/test_encoders.py | 2 +- tests/test_exceptions.py | 11 +- tests/test_fields.py | 19 +- tests/test_filters.py | 6 +- tests/test_generateschema.py | 6 +- tests/test_generics.py | 13 +- tests/test_htmlrenderer.py | 20 +- tests/test_metadata.py | 2 - tests/test_middleware.py | 4 +- tests/test_model_serializer.py | 69 ++---- tests/test_multitable_inheritance.py | 2 - tests/test_negotiation.py | 4 +- tests/test_one_to_one_with_inheritance.py | 2 - tests/test_pagination.py | 26 +- tests/test_parsers.py | 8 +- tests/test_permissions.py | 9 +- tests/test_relations.py | 2 +- tests/test_relations_hyperlink.py | 2 - tests/test_relations_pk.py | 7 +- tests/test_renderers.py | 10 +- tests/test_request.py | 13 +- tests/test_requests_client.py | 2 - tests/test_response.py | 9 +- tests/test_reverse.py | 4 +- tests/test_routers.py | 2 - tests/test_schemas.py | 6 +- tests/test_serializer.py | 26 +- tests/test_serializer_bulk_update.py | 6 +- tests/test_settings.py | 2 - tests/test_status.py | 2 - tests/test_templatetags.py | 21 +- tests/test_testing.py | 7 +- tests/test_throttling.py | 3 +- tests/test_urlpatterns.py | 2 - tests/test_utils.py | 3 - tests/test_validation.py | 9 +- tests/test_validators.py | 8 +- tests/test_versioning.py | 4 +- tests/test_views.py | 8 +- tests/utils.py | 6 +- tox.ini | 5 +- 111 files changed, 473 insertions(+), 795 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9543cb4525..04a5ff99ea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ dist: xenial matrix: fast_finish: true include: - - { python: "2.7", env: DJANGO=1.11 } - { python: "3.4", env: DJANGO=1.11 } - { python: "3.4", env: DJANGO=2.0 } @@ -26,8 +25,8 @@ matrix: - { python: "3.7", env: DJANGO=master } - { python: "3.7", env: TOXENV=base } - - { python: "2.7", env: TOXENV=lint } - - { python: "2.7", env: TOXENV=docs } + - { python: "3.7", env: TOXENV=lint } + - { python: "3.7", env: TOXENV=docs } - python: "3.7" env: TOXENV=dist diff --git a/README.md b/README.md index 66079edf07..7d0bdd2ad4 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements -* Python (2.7, 3.4, 3.5, 3.6, 3.7) +* Python (3.4, 3.5, 3.6, 3.7) * Django (1.11, 2.0, 2.1, 2.2) We **highly recommend** and only officially support the latest patch release of diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index ede4f15ad5..d371bb8fd7 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -629,7 +629,7 @@ Our `ColorField` class above currently does not perform any data validation. To indicate invalid data, we should raise a `serializers.ValidationError`, like so: def to_internal_value(self, data): - if not isinstance(data, six.text_type): + if not isinstance(data, str): msg = 'Incorrect type. Expected a string, but got %s' raise ValidationError(msg % type(data).__name__) @@ -653,7 +653,7 @@ The `.fail()` method is a shortcut for raising `ValidationError` that takes a me } def to_internal_value(self, data): - if not isinstance(data, six.text_type): + if not isinstance(data, str): self.fail('incorrect_type', input_type=type(data).__name__) if not re.match(r'^rgb\([0-9]+,[0-9]+,[0-9]+\)$', data): diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 25150d5255..0612563e47 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -1,14 +1,11 @@ """ Provides various authentication policies. """ -from __future__ import unicode_literals - import base64 import binascii from django.contrib.auth import authenticate, get_user_model from django.middleware.csrf import CsrfViewMiddleware -from django.utils.six import text_type from django.utils.translation import ugettext_lazy as _ from rest_framework import HTTP_HEADER_ENCODING, exceptions @@ -21,7 +18,7 @@ def get_authorization_header(request): Hide some test client ickyness where the header can be unicode. """ auth = request.META.get('HTTP_AUTHORIZATION', b'') - if isinstance(auth, text_type): + if isinstance(auth, str): # Work around django test client oddness auth = auth.encode(HTTP_HEADER_ENCODING) return auth @@ -33,7 +30,7 @@ def _reject(self, request, reason): return reason -class BaseAuthentication(object): +class BaseAuthentication: """ All authentication classes should extend BaseAuthentication. """ diff --git a/rest_framework/authtoken/management/commands/drf_create_token.py b/rest_framework/authtoken/management/commands/drf_create_token.py index 8e06812db6..3d65392442 100644 --- a/rest_framework/authtoken/management/commands/drf_create_token.py +++ b/rest_framework/authtoken/management/commands/drf_create_token.py @@ -38,8 +38,8 @@ def handle(self, *args, **options): token = self.create_user_token(username, reset_token) except UserModel.DoesNotExist: raise CommandError( - 'Cannot create the Token: user {0} does not exist'.format( + 'Cannot create the Token: user {} does not exist'.format( username) ) self.stdout.write( - 'Generated token {0} for user {1}'.format(token.key, username)) + 'Generated token {} for user {}'.format(token.key, username)) diff --git a/rest_framework/authtoken/migrations/0001_initial.py b/rest_framework/authtoken/migrations/0001_initial.py index 75780fedf2..6a46ccfffe 100644 --- a/rest_framework/authtoken/migrations/0001_initial.py +++ b/rest_framework/authtoken/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models diff --git a/rest_framework/authtoken/migrations/0002_auto_20160226_1747.py b/rest_framework/authtoken/migrations/0002_auto_20160226_1747.py index 9f7e58e226..43119099a3 100644 --- a/rest_framework/authtoken/migrations/0002_auto_20160226_1747.py +++ b/rest_framework/authtoken/migrations/0002_auto_20160226_1747.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 7e96eff93b..0ed02c4154 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -3,11 +3,9 @@ from django.conf import settings from django.db import models -from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ -@python_2_unicode_compatible class Token(models.Model): """ The default authorization token model. @@ -32,7 +30,7 @@ class Meta: def save(self, *args, **kwargs): if not self.key: self.key = self.generate_key() - return super(Token, self).save(*args, **kwargs) + return super().save(*args, **kwargs) def generate_key(self): return binascii.hexlify(os.urandom(20)).decode() diff --git a/rest_framework/compat.py b/rest_framework/compat.py index d61ca5dbba..aad44e3421 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -2,23 +2,13 @@ The `compat` module provides support for backwards compatibility with older versions of Django/Python, and compatibility wrappers around optional packages. """ - -from __future__ import unicode_literals - import sys +from collections.abc import Mapping, MutableMapping # noqa from django.conf import settings from django.core import validators -from django.utils import six from django.views.generic import View -try: - # Python 3 - from collections.abc import Mapping, MutableMapping # noqa -except ImportError: - # Python 2.7 - from collections import Mapping, MutableMapping # noqa - try: from django.urls import ( # noqa URLPattern, @@ -36,11 +26,6 @@ except ImportError: ProhibitNullCharactersValidator = None -try: - from unittest import mock -except ImportError: - mock = None - def get_original_route(urlpattern): """ @@ -89,23 +74,6 @@ def make_url_resolver(regex, urlpatterns): return URLResolver(regex, urlpatterns) -def unicode_repr(instance): - # Get the repr of an instance, but ensure it is a unicode string - # on both python 3 (already the case) and 2 (not the case). - if six.PY2: - return repr(instance).decode('utf-8') - return repr(instance) - - -def unicode_to_repr(value): - # Coerce a unicode string to the correct repr return type, depending on - # the Python version. We wrap all our `__repr__` implementations with - # this and then use unicode throughout internally. - if six.PY2: - return value.encode('utf-8') - return value - - def unicode_http_header(value): # Coerce HTTP header value to unicode. if isinstance(value, bytes): @@ -168,15 +136,6 @@ def is_guardian_installed(): """ django-guardian is optional and only imported if in INSTALLED_APPS. """ - try: - import guardian - except ImportError: - guardian = None - - if six.PY2 and (not guardian or guardian.VERSION >= (1, 5)): - # 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 @@ -289,17 +248,12 @@ def md_filter_add_syntax_highlight(md): # `separators` argument to `json.dumps()` differs between 2.x and 3.x # See: https://bugs.python.org/issue22767 -if six.PY3: - SHORT_SEPARATORS = (',', ':') - LONG_SEPARATORS = (', ', ': ') - INDENT_SEPARATORS = (',', ': ') -else: - SHORT_SEPARATORS = (b',', b':') - LONG_SEPARATORS = (b', ', b': ') - INDENT_SEPARATORS = (b',', b': ') +SHORT_SEPARATORS = (',', ':') +LONG_SEPARATORS = (', ', ': ') +INDENT_SEPARATORS = (',', ': ') -class CustomValidatorMessage(object): +class CustomValidatorMessage: """ We need to avoid evaluation of `lazy` translated `message` in `django.core.validators.BaseValidator.__init__`. https://github.com/django/django/blob/75ed5900321d170debef4ac452b8b3cf8a1c2384/django/core/validators.py#L297 @@ -309,7 +263,7 @@ class CustomValidatorMessage(object): def __init__(self, *args, **kwargs): self.message = kwargs.pop('message', self.message) - super(CustomValidatorMessage, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) class MinValueValidator(CustomValidatorMessage, validators.MinValueValidator): diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 30bfcc4e53..5d7bd14a3f 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -6,13 +6,10 @@ based views, as well as the `@detail_route` and `@list_route` decorators, which are used to annotate methods on viewsets that should be included by routers. """ -from __future__ import unicode_literals - import types 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 @@ -28,7 +25,7 @@ def api_view(http_method_names=None): def decorator(func): WrappedAPIView = type( - six.PY3 and 'WrappedAPIView' or b'WrappedAPIView', + 'WrappedAPIView', (APIView,), {'__doc__': func.__doc__} ) diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index f79b161294..8fbdfcd084 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -4,18 +4,14 @@ In addition Django's built in 403 and 404 exceptions are handled. (`django.http.Http404` and `django.core.exceptions.PermissionDenied`) """ -from __future__ import unicode_literals - import math from django.http import JsonResponse -from django.utils import six from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext from rest_framework import status -from rest_framework.compat import unicode_to_repr from rest_framework.utils.serializer_helpers import ReturnDict, ReturnList @@ -64,19 +60,19 @@ def _get_full_details(detail): } -class ErrorDetail(six.text_type): +class ErrorDetail(str): """ A string-like object that can additionally have a code. """ code = None def __new__(cls, string, code=None): - self = super(ErrorDetail, cls).__new__(cls, string) + self = super().__new__(cls, string) self.code = code return self def __eq__(self, other): - r = super(ErrorDetail, self).__eq__(other) + r = super().__eq__(other) try: return r and self.code == other.code except AttributeError: @@ -86,10 +82,10 @@ def __ne__(self, other): return not self.__eq__(other) def __repr__(self): - return unicode_to_repr('ErrorDetail(string=%r, code=%r)' % ( - six.text_type(self), + return 'ErrorDetail(string=%r, code=%r)' % ( + str(self), self.code, - )) + ) def __hash__(self): return hash(str(self)) @@ -113,7 +109,7 @@ def __init__(self, detail=None, code=None): self.detail = _get_error_details(detail, code) def __str__(self): - return six.text_type(self.detail) + return str(self.detail) def get_codes(self): """ @@ -196,7 +192,7 @@ class MethodNotAllowed(APIException): def __init__(self, method, detail=None, code=None): if detail is None: detail = force_text(self.default_detail).format(method=method) - super(MethodNotAllowed, self).__init__(detail, code) + super().__init__(detail, code) class NotAcceptable(APIException): @@ -206,7 +202,7 @@ class NotAcceptable(APIException): def __init__(self, detail=None, code=None, available_renderers=None): self.available_renderers = available_renderers - super(NotAcceptable, self).__init__(detail, code) + super().__init__(detail, code) class UnsupportedMediaType(APIException): @@ -217,7 +213,7 @@ class UnsupportedMediaType(APIException): def __init__(self, media_type, detail=None, code=None): if detail is None: detail = force_text(self.default_detail).format(media_type=media_type) - super(UnsupportedMediaType, self).__init__(detail, code) + super().__init__(detail, code) class Throttled(APIException): @@ -238,7 +234,7 @@ def __init__(self, wait=None, detail=None, code=None): self.extra_detail_plural.format(wait=wait), wait)))) self.wait = wait - super(Throttled, self).__init__(detail, code) + super().__init__(detail, code) def server_error(request, *args, **kwargs): diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c8f65db0e5..ad9611e056 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import copy import datetime import decimal @@ -17,7 +15,7 @@ ) from django.forms import FilePathField as DjangoFilePathField from django.forms import ImageField as DjangoImageField -from django.utils import six, timezone +from django.utils import timezone from django.utils.dateparse import ( parse_date, parse_datetime, parse_duration, parse_time ) @@ -33,8 +31,7 @@ from rest_framework import ISO_8601 from rest_framework.compat import ( Mapping, MaxLengthValidator, MaxValueValidator, MinLengthValidator, - MinValueValidator, ProhibitNullCharactersValidator, unicode_repr, - unicode_to_repr + MinValueValidator, ProhibitNullCharactersValidator ) from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.settings import api_settings @@ -51,39 +48,21 @@ class empty: pass -if six.PY3: - def is_simple_callable(obj): - """ - True if the object is a callable that takes no arguments. - """ - if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)): - return False - - sig = inspect.signature(obj) - params = sig.parameters.values() - return all( - param.kind == param.VAR_POSITIONAL or - param.kind == param.VAR_KEYWORD or - param.default != param.empty - for param in params - ) - -else: - def is_simple_callable(obj): - function = inspect.isfunction(obj) - method = inspect.ismethod(obj) - - if not (function or method): - return False - - if method: - is_unbound = obj.im_self is None - - args, _, _, defaults = inspect.getargspec(obj) +def is_simple_callable(obj): + """ + True if the object is a callable that takes no arguments. + """ + if not (inspect.isfunction(obj) or inspect.ismethod(obj) or isinstance(obj, functools.partial)): + return False - len_args = len(args) if function or is_unbound else len(args) - 1 - len_defaults = len(defaults) if defaults else 0 - return len_args <= len_defaults + sig = inspect.signature(obj) + params = sig.parameters.values() + return all( + param.kind == param.VAR_POSITIONAL or + param.kind == param.VAR_KEYWORD or + param.default != param.empty + for param in params + ) def get_attribute(instance, attrs): @@ -108,7 +87,7 @@ def get_attribute(instance, attrs): # If we raised an Attribute or KeyError here it'd get treated # as an omitted field in `Field.get_attribute()`. Instead we # raise a ValueError to ensure the exception is not masked. - raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc)) + raise ValueError('Exception raised in callable attribute "{}"; original exception was: {}'.format(attr, exc)) return instance @@ -185,18 +164,18 @@ def iter_options(grouped_choices, cutoff=None, cutoff_text=None): """ Helper function for options and option groups in templates. """ - class StartOptionGroup(object): + class StartOptionGroup: start_option_group = True end_option_group = False def __init__(self, label): self.label = label - class EndOptionGroup(object): + class EndOptionGroup: start_option_group = False end_option_group = True - class Option(object): + class Option: start_option_group = False end_option_group = False @@ -251,7 +230,7 @@ def get_error_detail(exc_info): } -class CreateOnlyDefault(object): +class CreateOnlyDefault: """ This class may be used to provide default values that are only used for create operations, but that do not return any value for update @@ -273,12 +252,10 @@ def __call__(self): return self.default def __repr__(self): - return unicode_to_repr( - '%s(%s)' % (self.__class__.__name__, unicode_repr(self.default)) - ) + return '%s(%s)' % (self.__class__.__name__, repr(self.default)) -class CurrentUserDefault(object): +class CurrentUserDefault: def set_context(self, serializer_field): self.user = serializer_field.context['request'].user @@ -286,7 +263,7 @@ def __call__(self): return self.user def __repr__(self): - return unicode_to_repr('%s()' % self.__class__.__name__) + return '%s()' % self.__class__.__name__ class SkipField(Exception): @@ -305,7 +282,7 @@ class SkipField(Exception): ) -class Field(object): +class Field: _creation_counter = 0 default_error_messages = { @@ -618,7 +595,7 @@ def __new__(cls, *args, **kwargs): When a field is instantiated, we store the arguments that were used, so that we can present a helpful representation of the object. """ - instance = super(Field, cls).__new__(cls) + instance = super().__new__(cls) instance._args = args instance._kwargs = kwargs return instance @@ -647,7 +624,7 @@ def __repr__(self): This allows us to create descriptive representations for serializer instances that show all the declared fields on the serializer. """ - return unicode_to_repr(representation.field_repr(self)) + return representation.field_repr(self) # Boolean types... @@ -724,7 +701,7 @@ class NullBooleanField(Field): def __init__(self, **kwargs): assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.' kwargs['allow_null'] = True - super(NullBooleanField, self).__init__(**kwargs) + super().__init__(**kwargs) def to_internal_value(self, data): try: @@ -764,17 +741,14 @@ def __init__(self, **kwargs): self.trim_whitespace = kwargs.pop('trim_whitespace', True) self.max_length = kwargs.pop('max_length', None) self.min_length = kwargs.pop('min_length', None) - super(CharField, self).__init__(**kwargs) + super().__init__(**kwargs) if self.max_length is not None: - message = lazy( - self.error_messages['max_length'].format, - six.text_type)(max_length=self.max_length) + message = lazy(self.error_messages['max_length'].format, str)(max_length=self.max_length) self.validators.append( MaxLengthValidator(self.max_length, message=message)) if self.min_length is not None: message = lazy( - self.error_messages['min_length'].format, - six.text_type)(min_length=self.min_length) + self.error_messages['min_length'].format, str)(min_length=self.min_length) self.validators.append( MinLengthValidator(self.min_length, message=message)) @@ -786,23 +760,23 @@ def run_validation(self, data=empty): # Test for the empty string here so that it does not get validated, # and so that subclasses do not need to handle it explicitly # inside the `to_internal_value()` method. - if data == '' or (self.trim_whitespace and six.text_type(data).strip() == ''): + if data == '' or (self.trim_whitespace and str(data).strip() == ''): if not self.allow_blank: self.fail('blank') return '' - return super(CharField, self).run_validation(data) + return super().run_validation(data) def to_internal_value(self, data): # We're lenient with allowing basic numerics to be coerced into strings, # but other types should fail. Eg. unclear if booleans should represent as `true` or `True`, # and composites such as lists are likely user error. - if isinstance(data, bool) or not isinstance(data, six.string_types + six.integer_types + (float,)): + if isinstance(data, bool) or not isinstance(data, (str, int, float,)): self.fail('invalid') - value = six.text_type(data) + value = str(data) return value.strip() if self.trim_whitespace else value def to_representation(self, value): - return six.text_type(value) + return str(value) class EmailField(CharField): @@ -811,7 +785,7 @@ class EmailField(CharField): } def __init__(self, **kwargs): - super(EmailField, self).__init__(**kwargs) + super().__init__(**kwargs) validator = EmailValidator(message=self.error_messages['invalid']) self.validators.append(validator) @@ -822,7 +796,7 @@ class RegexField(CharField): } def __init__(self, regex, **kwargs): - super(RegexField, self).__init__(**kwargs) + super().__init__(**kwargs) validator = RegexValidator(regex, message=self.error_messages['invalid']) self.validators.append(validator) @@ -834,7 +808,7 @@ class SlugField(CharField): } def __init__(self, allow_unicode=False, **kwargs): - super(SlugField, self).__init__(**kwargs) + super().__init__(**kwargs) self.allow_unicode = allow_unicode if self.allow_unicode: validator = RegexValidator(re.compile(r'^[-\w]+\Z', re.UNICODE), message=self.error_messages['invalid_unicode']) @@ -849,7 +823,7 @@ class URLField(CharField): } def __init__(self, **kwargs): - super(URLField, self).__init__(**kwargs) + super().__init__(**kwargs) validator = URLValidator(message=self.error_messages['invalid']) self.validators.append(validator) @@ -866,16 +840,16 @@ def __init__(self, **kwargs): if self.uuid_format not in self.valid_formats: raise ValueError( 'Invalid format for uuid representation. ' - 'Must be one of "{0}"'.format('", "'.join(self.valid_formats)) + 'Must be one of "{}"'.format('", "'.join(self.valid_formats)) ) - super(UUIDField, self).__init__(**kwargs) + super().__init__(**kwargs) def to_internal_value(self, data): if not isinstance(data, uuid.UUID): try: - if isinstance(data, six.integer_types): + if isinstance(data, int): return uuid.UUID(int=data) - elif isinstance(data, six.string_types): + elif isinstance(data, str): return uuid.UUID(hex=data) else: self.fail('invalid', value=data) @@ -900,12 +874,12 @@ class IPAddressField(CharField): def __init__(self, protocol='both', **kwargs): self.protocol = protocol.lower() self.unpack_ipv4 = (self.protocol == 'both') - super(IPAddressField, self).__init__(**kwargs) + super().__init__(**kwargs) validators, error_message = ip_address_validators(protocol, self.unpack_ipv4) self.validators.extend(validators) def to_internal_value(self, data): - if not isinstance(data, six.string_types): + if not isinstance(data, str): self.fail('invalid', value=data) if ':' in data: @@ -915,7 +889,7 @@ def to_internal_value(self, data): except DjangoValidationError: self.fail('invalid', value=data) - return super(IPAddressField, self).to_internal_value(data) + return super().to_internal_value(data) # Number types... @@ -933,22 +907,20 @@ class IntegerField(Field): def __init__(self, **kwargs): self.max_value = kwargs.pop('max_value', None) self.min_value = kwargs.pop('min_value', None) - super(IntegerField, self).__init__(**kwargs) + super().__init__(**kwargs) if self.max_value is not None: message = lazy( - self.error_messages['max_value'].format, - six.text_type)(max_value=self.max_value) + self.error_messages['max_value'].format, str)(max_value=self.max_value) self.validators.append( MaxValueValidator(self.max_value, message=message)) if self.min_value is not None: message = lazy( - self.error_messages['min_value'].format, - six.text_type)(min_value=self.min_value) + self.error_messages['min_value'].format, str)(min_value=self.min_value) self.validators.append( MinValueValidator(self.min_value, message=message)) def to_internal_value(self, data): - if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: + if isinstance(data, str) and len(data) > self.MAX_STRING_LENGTH: self.fail('max_string_length') try: @@ -973,23 +945,23 @@ class FloatField(Field): def __init__(self, **kwargs): self.max_value = kwargs.pop('max_value', None) self.min_value = kwargs.pop('min_value', None) - super(FloatField, self).__init__(**kwargs) + super().__init__(**kwargs) if self.max_value is not None: message = lazy( self.error_messages['max_value'].format, - six.text_type)(max_value=self.max_value) + str)(max_value=self.max_value) self.validators.append( MaxValueValidator(self.max_value, message=message)) if self.min_value is not None: message = lazy( self.error_messages['min_value'].format, - six.text_type)(min_value=self.min_value) + str)(min_value=self.min_value) self.validators.append( MinValueValidator(self.min_value, message=message)) def to_internal_value(self, data): - if isinstance(data, six.text_type) and len(data) > self.MAX_STRING_LENGTH: + if isinstance(data, str) and len(data) > self.MAX_STRING_LENGTH: self.fail('max_string_length') try: @@ -1031,18 +1003,17 @@ def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value= else: self.max_whole_digits = None - super(DecimalField, self).__init__(**kwargs) + super().__init__(**kwargs) if self.max_value is not None: message = lazy( self.error_messages['max_value'].format, - six.text_type)(max_value=self.max_value) + str)(max_value=self.max_value) self.validators.append( MaxValueValidator(self.max_value, message=message)) if self.min_value is not None: message = lazy( - self.error_messages['min_value'].format, - six.text_type)(min_value=self.min_value) + self.error_messages['min_value'].format, str)(min_value=self.min_value) self.validators.append( MinValueValidator(self.min_value, message=message)) @@ -1121,7 +1092,7 @@ def to_representation(self, value): coerce_to_string = getattr(self, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING) if not isinstance(value, decimal.Decimal): - value = decimal.Decimal(six.text_type(value).strip()) + value = decimal.Decimal(str(value).strip()) quantized = self.quantize(value) @@ -1130,7 +1101,7 @@ def to_representation(self, value): if self.localize: return localize_input(quantized) - return '{0:f}'.format(quantized) + return '{:f}'.format(quantized) def quantize(self, value): """ @@ -1167,7 +1138,7 @@ def __init__(self, format=empty, input_formats=None, default_timezone=None, *arg self.input_formats = input_formats if default_timezone is not None: self.timezone = default_timezone - super(DateTimeField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def enforce_timezone(self, value): """ @@ -1226,7 +1197,7 @@ def to_representation(self, value): output_format = getattr(self, 'format', api_settings.DATETIME_FORMAT) - if output_format is None or isinstance(value, six.string_types): + if output_format is None or isinstance(value, str): return value value = self.enforce_timezone(value) @@ -1251,7 +1222,7 @@ def __init__(self, format=empty, input_formats=None, *args, **kwargs): self.format = format if input_formats is not None: self.input_formats = input_formats - super(DateField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def to_internal_value(self, value): input_formats = getattr(self, 'input_formats', api_settings.DATE_INPUT_FORMATS) @@ -1288,7 +1259,7 @@ def to_representation(self, value): output_format = getattr(self, 'format', api_settings.DATE_FORMAT) - if output_format is None or isinstance(value, six.string_types): + if output_format is None or isinstance(value, str): return value # Applying a `DateField` to a datetime value is almost always @@ -1317,7 +1288,7 @@ def __init__(self, format=empty, input_formats=None, *args, **kwargs): self.format = format if input_formats is not None: self.input_formats = input_formats - super(TimeField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def to_internal_value(self, value): input_formats = getattr(self, 'input_formats', api_settings.TIME_INPUT_FORMATS) @@ -1351,7 +1322,7 @@ def to_representation(self, value): output_format = getattr(self, 'format', api_settings.TIME_FORMAT) - if output_format is None or isinstance(value, six.string_types): + if output_format is None or isinstance(value, str): return value # Applying a `TimeField` to a datetime value is almost always @@ -1378,24 +1349,24 @@ class DurationField(Field): def __init__(self, **kwargs): self.max_value = kwargs.pop('max_value', None) self.min_value = kwargs.pop('min_value', None) - super(DurationField, self).__init__(**kwargs) + super().__init__(**kwargs) if self.max_value is not None: message = lazy( self.error_messages['max_value'].format, - six.text_type)(max_value=self.max_value) + str)(max_value=self.max_value) self.validators.append( MaxValueValidator(self.max_value, message=message)) if self.min_value is not None: message = lazy( self.error_messages['min_value'].format, - six.text_type)(min_value=self.min_value) + str)(min_value=self.min_value) self.validators.append( MinValueValidator(self.min_value, message=message)) def to_internal_value(self, value): if isinstance(value, datetime.timedelta): return value - parsed = parse_duration(six.text_type(value)) + parsed = parse_duration(str(value)) if parsed is not None: return parsed self.fail('invalid', format='[DD] [HH:[MM:]]ss[.uuuuuu]') @@ -1420,21 +1391,21 @@ def __init__(self, choices, **kwargs): self.allow_blank = kwargs.pop('allow_blank', False) - super(ChoiceField, self).__init__(**kwargs) + super().__init__(**kwargs) def to_internal_value(self, data): if data == '' and self.allow_blank: return '' try: - return self.choice_strings_to_values[six.text_type(data)] + return self.choice_strings_to_values[str(data)] except KeyError: self.fail('invalid_choice', input=data) def to_representation(self, value): if value in ('', None): return value - return self.choice_strings_to_values.get(six.text_type(value), value) + return self.choice_strings_to_values.get(str(value), value) def iter_options(self): """ @@ -1457,7 +1428,7 @@ def _set_choices(self, choices): # Allows us to deal with eg. integer choices while supporting either # integer or string input, but still get the correct datatype out. self.choice_strings_to_values = { - six.text_type(key): key for key in self.choices + str(key): key for key in self.choices } choices = property(_get_choices, _set_choices) @@ -1473,7 +1444,7 @@ class MultipleChoiceField(ChoiceField): def __init__(self, *args, **kwargs): self.allow_empty = kwargs.pop('allow_empty', True) - super(MultipleChoiceField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_value(self, dictionary): if self.field_name not in dictionary: @@ -1486,7 +1457,7 @@ def get_value(self, dictionary): return dictionary.get(self.field_name, empty) def to_internal_value(self, data): - if isinstance(data, six.text_type) or not hasattr(data, '__iter__'): + if isinstance(data, str) 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') @@ -1498,7 +1469,7 @@ def to_internal_value(self, data): def to_representation(self, value): return { - self.choice_strings_to_values.get(six.text_type(item), item) for item in value + self.choice_strings_to_values.get(str(item), item) for item in value } @@ -1516,7 +1487,7 @@ def __init__(self, path, match=None, recursive=False, allow_files=True, allow_folders=allow_folders, required=required ) kwargs['choices'] = field.choices - super(FilePathField, self).__init__(**kwargs) + super().__init__(**kwargs) # File types... @@ -1535,7 +1506,7 @@ def __init__(self, *args, **kwargs): self.allow_empty_file = kwargs.pop('allow_empty_file', False) if 'use_url' in kwargs: self.use_url = kwargs.pop('use_url') - super(FileField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def to_internal_value(self, data): try: @@ -1581,13 +1552,13 @@ class ImageField(FileField): def __init__(self, *args, **kwargs): self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField) - super(ImageField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def to_internal_value(self, data): # Image validation is a bit grungy, so we'll just outright # defer to Django's implementation so we don't need to # consider it, or treat PIL as a test dependency. - file_object = super(ImageField, self).to_internal_value(data) + file_object = super().to_internal_value(data) django_field = self._DjangoImageField() django_field.error_messages = self.error_messages return django_field.clean(file_object) @@ -1597,7 +1568,7 @@ def to_internal_value(self, data): class _UnvalidatedField(Field): def __init__(self, *args, **kwargs): - super(_UnvalidatedField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.allow_blank = True self.allow_null = True @@ -1630,7 +1601,7 @@ def __init__(self, *args, **kwargs): "Remove `source=` from the field declaration." ) - super(ListField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.child.bind(field_name='', parent=self) if self.max_length is not None: message = self.error_messages['max_length'].format(max_length=self.max_length) @@ -1660,7 +1631,7 @@ def to_internal_value(self, data): """ if html.is_html_input(data): data = html.parse_html_list(data, default=[]) - if isinstance(data, (six.text_type, Mapping)) or not hasattr(data, '__iter__'): + if isinstance(data, (str, 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') @@ -1703,7 +1674,7 @@ def __init__(self, *args, **kwargs): "Remove `source=` from the field declaration." ) - super(DictField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.child.bind(field_name='', parent=self) def get_value(self, dictionary): @@ -1725,7 +1696,7 @@ def to_internal_value(self, data): def to_representation(self, value): return { - six.text_type(key): self.child.to_representation(val) if val is not None else None + str(key): self.child.to_representation(val) if val is not None else None for key, val in value.items() } @@ -1734,7 +1705,7 @@ def run_child_validation(self, data): errors = OrderedDict() for key, value in data.items(): - key = six.text_type(key) + key = str(key) try: result[key] = self.child.run_validation(value) @@ -1750,7 +1721,7 @@ class HStoreField(DictField): child = CharField(allow_blank=True, allow_null=True) def __init__(self, *args, **kwargs): - super(HStoreField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) assert isinstance(self.child, CharField), ( "The `child` argument must be an instance of `CharField`, " "as the hstore extension stores values as strings." @@ -1764,15 +1735,15 @@ class JSONField(Field): def __init__(self, *args, **kwargs): self.binary = kwargs.pop('binary', False) - super(JSONField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_value(self, dictionary): if html.is_html_input(dictionary) and self.field_name in dictionary: # When HTML form input is used, mark up the input # as being a JSON string, rather than a JSON primitive. - class JSONString(six.text_type): + class JSONString(str): def __new__(self, value): - ret = six.text_type.__new__(self, value) + ret = str.__new__(self, value) ret.is_json_string = True return ret return JSONString(dictionary[self.field_name]) @@ -1795,7 +1766,7 @@ def to_representation(self, value): value = json.dumps(value) # On python 2.x the return type for json.dumps() is underspecified. # On python 3.x json.dumps() returns unicode strings. - if isinstance(value, six.text_type): + if isinstance(value, str): value = bytes(value.encode('utf-8')) return value @@ -1817,7 +1788,7 @@ class ExampleSerializer(Serializer): def __init__(self, **kwargs): kwargs['read_only'] = True - super(ReadOnlyField, self).__init__(**kwargs) + super().__init__(**kwargs) def to_representation(self, value): return value @@ -1834,7 +1805,7 @@ class HiddenField(Field): def __init__(self, **kwargs): assert 'default' in kwargs, 'default is a required argument.' kwargs['write_only'] = True - super(HiddenField, self).__init__(**kwargs) + super().__init__(**kwargs) def get_value(self, dictionary): # We always use the default value for `HiddenField`. @@ -1864,7 +1835,7 @@ def __init__(self, method_name=None, **kwargs): self.method_name = method_name kwargs['source'] = '*' kwargs['read_only'] = True - super(SerializerMethodField, self).__init__(**kwargs) + super().__init__(**kwargs) def bind(self, field_name, parent): # In order to enforce a consistent style, we error if a redundant @@ -1882,7 +1853,7 @@ def bind(self, field_name, parent): if self.method_name is None: self.method_name = default_method_name - super(SerializerMethodField, self).bind(field_name, parent) + super().bind(field_name, parent) def to_representation(self, value): method = getattr(self.parent, self.method_name) @@ -1905,11 +1876,10 @@ def __init__(self, model_field, **kwargs): # The `max_length` option is supported by Django's base `Field` class, # so we'd better support it here. max_length = kwargs.pop('max_length', None) - super(ModelField, self).__init__(**kwargs) + super().__init__(**kwargs) if max_length is not None: message = lazy( - self.error_messages['max_length'].format, - six.text_type)(max_length=self.max_length) + self.error_messages['max_length'].format, str)(max_length=self.max_length) self.validators.append( MaxLengthValidator(self.max_length, message=message)) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index bb1b86586c..b77069ddc9 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -2,8 +2,6 @@ Provides generic filtering backends that can be used to filter the results returned by list views. """ -from __future__ import unicode_literals - import operator import warnings from functools import reduce @@ -13,7 +11,6 @@ from django.db.models.constants import LOOKUP_SEP from django.db.models.sql.constants import ORDER_PATTERN from django.template import loader -from django.utils import six from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ @@ -24,7 +21,7 @@ from rest_framework.settings import api_settings -class BaseFilterBackend(object): +class BaseFilterBackend: """ A base class from which all filter backend classes should inherit. """ @@ -109,7 +106,7 @@ def filter_queryset(self, request, queryset, view): return queryset orm_lookups = [ - self.construct_search(six.text_type(search_field)) + self.construct_search(str(search_field)) for search_field in search_fields ] @@ -188,7 +185,7 @@ def get_ordering(self, request, queryset, view): def get_default_ordering(self, view): ordering = getattr(view, 'ordering', None) - if isinstance(ordering, six.string_types): + if isinstance(ordering, str): return (ordering,) return ordering @@ -237,7 +234,7 @@ def get_valid_fields(self, queryset, view, context={}): ] else: valid_fields = [ - (item, item) if isinstance(item, six.string_types) else item + (item, item) if isinstance(item, str) else item for item in valid_fields ] diff --git a/rest_framework/generics.py b/rest_framework/generics.py index 8d0bf284a9..c39b02ab7f 100644 --- a/rest_framework/generics.py +++ b/rest_framework/generics.py @@ -1,8 +1,6 @@ """ Generic views that provide commonly needed behaviour. """ -from __future__ import unicode_literals - from django.core.exceptions import ValidationError from django.db.models.query import QuerySet from django.http import Http404 diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py index 9f93244693..42442f91cb 100644 --- a/rest_framework/metadata.py +++ b/rest_framework/metadata.py @@ -6,8 +6,6 @@ Future implementations might use JSON schema or other definitions in order to return this information in a more standardized way. """ -from __future__ import unicode_literals - from collections import OrderedDict from django.core.exceptions import PermissionDenied @@ -19,7 +17,7 @@ from rest_framework.utils.field_mapping import ClassLookupDict -class BaseMetadata(object): +class BaseMetadata: def determine_metadata(self, request, view): """ Return a dictionary of metadata about the view. diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py index de10d69308..7fa8947cb9 100644 --- a/rest_framework/mixins.py +++ b/rest_framework/mixins.py @@ -4,14 +4,12 @@ We don't bind behaviour to http method handlers yet, which allows mixin classes to be composed in interesting ways. """ -from __future__ import unicode_literals - from rest_framework import status from rest_framework.response import Response from rest_framework.settings import api_settings -class CreateModelMixin(object): +class CreateModelMixin: """ Create a model instance. """ @@ -32,7 +30,7 @@ def get_success_headers(self, data): return {} -class ListModelMixin(object): +class ListModelMixin: """ List a queryset. """ @@ -48,7 +46,7 @@ def list(self, request, *args, **kwargs): return Response(serializer.data) -class RetrieveModelMixin(object): +class RetrieveModelMixin: """ Retrieve a model instance. """ @@ -58,7 +56,7 @@ def retrieve(self, request, *args, **kwargs): return Response(serializer.data) -class UpdateModelMixin(object): +class UpdateModelMixin: """ Update a model instance. """ @@ -84,7 +82,7 @@ def partial_update(self, request, *args, **kwargs): return self.update(request, *args, **kwargs) -class DestroyModelMixin(object): +class DestroyModelMixin: """ Destroy a model instance. """ diff --git a/rest_framework/negotiation.py b/rest_framework/negotiation.py index ca1b59f12e..76113a827f 100644 --- a/rest_framework/negotiation.py +++ b/rest_framework/negotiation.py @@ -2,8 +2,6 @@ Content negotiation deals with selecting an appropriate renderer given the incoming request. Typically this will be based on the request's Accept header. """ -from __future__ import unicode_literals - from django.http import Http404 from rest_framework import HTTP_HEADER_ENCODING, exceptions @@ -13,7 +11,7 @@ ) -class BaseContentNegotiation(object): +class BaseContentNegotiation: def select_parser(self, request, parsers): raise NotImplementedError('.select_parser() must be implemented') @@ -66,7 +64,7 @@ def select_renderer(self, request, renderers, format_suffix=None): # Accepted media type is 'application/json' full_media_type = ';'.join( (renderer.media_type,) + - tuple('{0}={1}'.format( + tuple('{}={}'.format( key, value.decode(HTTP_HEADER_ENCODING)) for key, value in media_type_wrapper.params.items())) return renderer, full_media_type diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index b11d7cdf3a..fcc78da43f 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -1,19 +1,15 @@ -# coding: utf-8 """ Pagination serializers determine the structure of the output that should be used for paginated responses. """ -from __future__ import unicode_literals - from base64 import b64decode, b64encode from collections import OrderedDict, namedtuple +from urllib import parse from django.core.paginator import InvalidPage from django.core.paginator import Paginator as DjangoPaginator from django.template import loader -from django.utils import six from django.utils.encoding import force_text -from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ from rest_framework.compat import coreapi, coreschema @@ -133,7 +129,7 @@ def invert(x): PAGE_BREAK = PageLink(url=None, number=None, is_active=False, is_break=True) -class BasePagination(object): +class BasePagination: display_page_controls = False def paginate_queryset(self, queryset, request, view=None): # pragma: no cover @@ -204,7 +200,7 @@ def paginate_queryset(self, queryset, request, view=None): self.page = paginator.page(page_number) except InvalidPage as exc: msg = self.invalid_page_message.format( - page_number=page_number, message=six.text_type(exc) + page_number=page_number, message=str(exc) ) raise NotFound(msg) @@ -716,13 +712,13 @@ def get_ordering(self, request, queryset, view): 'nearly-unique field on the model, such as "-created" or "pk".' ) - assert isinstance(ordering, (six.string_types, list, tuple)), ( + assert isinstance(ordering, (str, list, tuple)), ( 'Invalid ordering. Expected string or tuple, but got {type}'.format( type=type(ordering).__name__ ) ) - if isinstance(ordering, six.string_types): + if isinstance(ordering, str): return (ordering,) return tuple(ordering) @@ -737,7 +733,7 @@ def decode_cursor(self, request): try: querystring = b64decode(encoded.encode('ascii')).decode('ascii') - tokens = urlparse.parse_qs(querystring, keep_blank_values=True) + tokens = parse.parse_qs(querystring, keep_blank_values=True) offset = tokens.get('o', ['0'])[0] offset = _positive_int(offset, cutoff=self.offset_cutoff) @@ -763,7 +759,7 @@ def encode_cursor(self, cursor): if cursor.position is not None: tokens['p'] = cursor.position - querystring = urlparse.urlencode(tokens, doseq=True) + querystring = parse.urlencode(tokens, doseq=True) encoded = b64encode(querystring.encode('ascii')).decode('ascii') return replace_query_param(self.base_url, self.cursor_query_param, encoded) @@ -773,7 +769,7 @@ def _get_position_from_instance(self, instance, ordering): attr = instance[field_name] else: attr = getattr(instance, field_name) - return six.text_type(attr) + return str(attr) def get_paginated_response(self, data): return Response(OrderedDict([ diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 35d0d1aa70..5b5e3f1581 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -4,9 +4,8 @@ They give us a generic way of being able to handle various media types on the request, such as form content or json encoded data. """ -from __future__ import unicode_literals - import codecs +from urllib import parse from django.conf import settings from django.core.files.uploadhandler import StopFutureHandlers @@ -15,9 +14,7 @@ from django.http.multipartparser import \ MultiPartParser as DjangoMultiPartParser from django.http.multipartparser import MultiPartParserError, parse_header -from django.utils import six from django.utils.encoding import force_text -from django.utils.six.moves.urllib import parse as urlparse from rest_framework import renderers from rest_framework.exceptions import ParseError @@ -25,13 +22,13 @@ from rest_framework.utils import json -class DataAndFiles(object): +class DataAndFiles: def __init__(self, data, files): self.data = data self.files = files -class BaseParser(object): +class BaseParser: """ All parsers should extend `BaseParser`, specifying a `media_type` attribute, and overriding the `.parse()` method. @@ -67,7 +64,7 @@ def parse(self, stream, media_type=None, parser_context=None): parse_constant = json.strict_constant if self.strict else None return json.load(decoded_stream, parse_constant=parse_constant) except ValueError as exc: - raise ParseError('JSON parse error - %s' % six.text_type(exc)) + raise ParseError('JSON parse error - %s' % str(exc)) class FormParser(BaseParser): @@ -113,7 +110,7 @@ def parse(self, stream, media_type=None, parser_context=None): data, files = parser.parse() return DataAndFiles(data, files) except MultiPartParserError as exc: - raise ParseError('Multipart form parse error - %s' % six.text_type(exc)) + raise ParseError('Multipart form parse error - %s' % str(exc)) class FileUploadParser(BaseParser): @@ -221,7 +218,7 @@ def get_encoded_filename(self, filename_parm): encoded_filename = force_text(filename_parm['filename*']) try: charset, lang, filename = encoded_filename.split('\'', 2) - filename = urlparse.unquote(filename) + filename = parse.unquote(filename) except (ValueError, LookupError): filename = force_text(filename_parm['filename']) return filename diff --git a/rest_framework/permissions.py b/rest_framework/permissions.py index 5d75f54bad..3a8c580646 100644 --- a/rest_framework/permissions.py +++ b/rest_framework/permissions.py @@ -1,10 +1,7 @@ """ Provides a set of pluggable permission policies. """ -from __future__ import unicode_literals - from django.http import Http404 -from django.utils import six from rest_framework import exceptions @@ -101,8 +98,7 @@ class BasePermissionMetaclass(OperationHolderMixin, type): pass -@six.add_metaclass(BasePermissionMetaclass) -class BasePermission(object): +class BasePermission(metaclass=BasePermissionMetaclass): """ A base class from which all permission classes should inherit. """ diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 31c1e75618..76c4d70089 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -1,18 +1,12 @@ -# coding: utf-8 -from __future__ import unicode_literals - import sys from collections import OrderedDict +from urllib import parse from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.db.models import Manager from django.db.models.query import QuerySet from django.urls import NoReverseMatch, Resolver404, get_script_prefix, resolve -from django.utils import six -from django.utils.encoding import ( - python_2_unicode_compatible, smart_text, uri_to_iri -) -from django.utils.six.moves.urllib import parse as urlparse +from django.utils.encoding import smart_text, uri_to_iri from django.utils.translation import ugettext_lazy as _ from rest_framework.fields import ( @@ -46,14 +40,14 @@ class ObjectTypeError(TypeError): """ -class Hyperlink(six.text_type): +class Hyperlink(str): """ A string like object that additionally has an associated name. We use this for hyperlinked URLs that may render as a named link in some contexts, or render as a plain URL in others. """ def __new__(self, url, obj): - ret = six.text_type.__new__(self, url) + ret = str.__new__(self, url) ret.obj = obj return ret @@ -65,13 +59,12 @@ def name(self): # This ensures that we only called `__str__` lazily, # as in some cases calling __str__ on a model instances *might* # involve a database lookup. - return six.text_type(self.obj) + return str(self.obj) is_hyperlink = True -@python_2_unicode_compatible -class PKOnlyObject(object): +class PKOnlyObject: """ This is a mock object, used for when we only need the pk of the object instance, but still want to return an object with a .pk attribute, @@ -121,14 +114,14 @@ def __init__(self, **kwargs): ) kwargs.pop('many', None) kwargs.pop('allow_empty', None) - super(RelatedField, self).__init__(**kwargs) + super().__init__(**kwargs) def __new__(cls, *args, **kwargs): # We override this method in order to automagically create # `ManyRelatedField` classes instead when `many=True` is set. if kwargs.pop('many', False): return cls.many_init(*args, **kwargs) - return super(RelatedField, cls).__new__(cls, *args, **kwargs) + return super().__new__(cls, *args, **kwargs) @classmethod def many_init(cls, *args, **kwargs): @@ -157,7 +150,7 @@ def run_validation(self, data=empty): # We force empty strings to None values for relational fields. if data == '': data = None - return super(RelatedField, self).run_validation(data) + return super().run_validation(data) def get_queryset(self): queryset = self.queryset @@ -189,7 +182,7 @@ def get_attribute(self, instance): pass # Standard case, return the object instance. - return super(RelatedField, self).get_attribute(instance) + return super().get_attribute(instance) def get_choices(self, cutoff=None): queryset = self.get_queryset() @@ -225,7 +218,7 @@ def iter_options(self): ) def display_value(self, instance): - return six.text_type(instance) + return str(instance) class StringRelatedField(RelatedField): @@ -236,10 +229,10 @@ class StringRelatedField(RelatedField): def __init__(self, **kwargs): kwargs['read_only'] = True - super(StringRelatedField, self).__init__(**kwargs) + super().__init__(**kwargs) def to_representation(self, value): - return six.text_type(value) + return str(value) class PrimaryKeyRelatedField(RelatedField): @@ -251,7 +244,7 @@ class PrimaryKeyRelatedField(RelatedField): def __init__(self, **kwargs): self.pk_field = kwargs.pop('pk_field', None) - super(PrimaryKeyRelatedField, self).__init__(**kwargs) + super().__init__(**kwargs) def use_pk_only_optimization(self): return True @@ -297,7 +290,7 @@ def __init__(self, view_name=None, **kwargs): # implicit `self` argument to be passed. self.reverse = reverse - super(HyperlinkedRelatedField, self).__init__(**kwargs) + super().__init__(**kwargs) def use_pk_only_optimization(self): return self.lookup_field == 'pk' @@ -317,10 +310,10 @@ def get_object(self, view_name, view_args, view_kwargs): return queryset.get(**lookup_kwargs) except ValueError: exc = ObjectValueError(str(sys.exc_info()[1])) - six.reraise(type(exc), exc, sys.exc_info()[2]) + raise exc.with_traceback(sys.exc_info()[2]) except TypeError: exc = ObjectTypeError(str(sys.exc_info()[1])) - six.reraise(type(exc), exc, sys.exc_info()[2]) + raise exc.with_traceback(sys.exc_info()[2]) def get_url(self, obj, view_name, request, format): """ @@ -346,7 +339,7 @@ def to_internal_value(self, data): if http_prefix: # If needed convert absolute URLs to relative path - data = urlparse.urlparse(data).path + data = parse.urlparse(data).path prefix = get_script_prefix() if data.startswith(prefix): data = '/' + data[len(prefix):] @@ -432,7 +425,7 @@ def __init__(self, view_name=None, **kwargs): assert view_name is not None, 'The `view_name` argument is required.' kwargs['read_only'] = True kwargs['source'] = '*' - super(HyperlinkedIdentityField, self).__init__(view_name, **kwargs) + super().__init__(view_name, **kwargs) def use_pk_only_optimization(self): # We have the complete object instance already. We don't need @@ -453,7 +446,7 @@ class SlugRelatedField(RelatedField): def __init__(self, slug_field=None, **kwargs): assert slug_field is not None, 'The `slug_field` argument is required.' self.slug_field = slug_field - super(SlugRelatedField, self).__init__(**kwargs) + super().__init__(**kwargs) def to_internal_value(self, data): try: @@ -502,7 +495,7 @@ def __init__(self, child_relation=None, *args, **kwargs): self.html_cutoff_text or _(api_settings.HTML_SELECT_CUTOFF_TEXT) ) assert child_relation is not None, '`child_relation` is a required argument.' - super(ManyRelatedField, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.child_relation.bind(field_name='', parent=self) def get_value(self, dictionary): @@ -518,7 +511,7 @@ def get_value(self, dictionary): return dictionary.get(self.field_name, empty) def to_internal_value(self, data): - if isinstance(data, six.text_type) or not hasattr(data, '__iter__'): + if isinstance(data, str) 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') diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index f043e63278..eb5da008b3 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -6,10 +6,9 @@ REST framework also provides an HTML renderer that renders the browsable API. """ -from __future__ import unicode_literals - import base64 from collections import OrderedDict +from urllib import parse from django import forms from django.conf import settings @@ -19,9 +18,7 @@ from django.template import engines, loader 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 ( @@ -40,7 +37,7 @@ def zero_as_none(value): return None if value == 0 else value -class BaseRenderer(object): +class BaseRenderer: """ All renderers should extend this class, setting the `media_type` and `format` attributes, and override the `.render()` method. @@ -111,7 +108,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None): # but if ensure_ascii=False, the return type is underspecified, # and may (or may not) be unicode. # On python 3.x json.dumps() returns unicode strings. - if isinstance(ret, six.text_type): + if isinstance(ret, str): # We always fully escape \u2028 and \u2029 to ensure we output JSON # that is a strict javascript subset. If bytes were returned # by json.dumps() then we don't have these characters in any case. @@ -349,7 +346,7 @@ def render_field(self, field, parent_style): # Get a clone of the field with text-only value representation. field = field.as_form_field() - if style.get('input_type') == 'datetime-local' and isinstance(field.value, six.text_type): + if style.get('input_type') == 'datetime-local' and isinstance(field.value, str): field.value = field.value.rstrip('Z') if 'template' in style: @@ -791,7 +788,7 @@ def get_context(self, data, accepted_media_type, renderer_context): """ Render the HTML for the browsable API representation. """ - context = super(AdminRenderer, self).get_context( + context = super().get_context( data, accepted_media_type, renderer_context ) @@ -995,14 +992,14 @@ def get_paths(self, document): tag = None for name, link in document.links.items(): - path = urlparse.urlparse(link.url).path + path = parse.urlparse(link.url).path method = link.action.lower() paths.setdefault(path, {}) paths[path][method] = self.get_operation(link, name, tag=tag) for tag, section in document.data.items(): for name, link in section.links.items(): - path = urlparse.urlparse(link.url).path + path = parse.urlparse(link.url).path method = link.action.lower() paths.setdefault(path, {}) paths[path][method] = self.get_operation(link, name, tag=tag) diff --git a/rest_framework/request.py b/rest_framework/request.py index a6d92e2bdc..ec4b749c26 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -8,8 +8,6 @@ - full support of PUT method, including support for file uploads - form overloading of HTTP method, content type and content """ -from __future__ import unicode_literals - import io import sys from contextlib import contextmanager @@ -18,7 +16,6 @@ from django.http import HttpRequest, QueryDict from django.http.multipartparser import parse_header from django.http.request import RawPostDataException -from django.utils import six from django.utils.datastructures import MultiValueDict from rest_framework import HTTP_HEADER_ENCODING, exceptions @@ -34,7 +31,7 @@ def is_form_media_type(media_type): base_media_type == 'multipart/form-data') -class override_method(object): +class override_method: """ A context manager that temporarily overrides the method on a request, additionally setting the `view.request` attribute. @@ -78,10 +75,10 @@ def wrap_attributeerrors(): except AttributeError: info = sys.exc_info() exc = WrappedAttributeError(str(info[1])) - six.reraise(type(exc), exc, info[2]) + raise exc.with_traceback(info[2]) -class Empty(object): +class Empty: """ Placeholder for unset attributes. Cannot use `None`, as that may be a valid value. @@ -126,7 +123,7 @@ def clone_request(request, method): return ret -class ForcedAuthentication(object): +class ForcedAuthentication: """ This authentication class is used if the test client or request factory forcibly authenticated the request. @@ -140,7 +137,7 @@ def authenticate(self, request): return (self.force_user, self.force_token) -class Request(object): +class Request: """ Wrapper allowing to enhance a standard `HttpRequest` instance. diff --git a/rest_framework/response.py b/rest_framework/response.py index bf06632557..db79777701 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -4,11 +4,9 @@ The appropriate renderer is called during Django's template response rendering. """ -from __future__ import unicode_literals +from http.client import responses from django.template.response import SimpleTemplateResponse -from django.utils import six -from django.utils.six.moves.http_client import responses from rest_framework.serializers import Serializer @@ -29,7 +27,7 @@ def __init__(self, data=None, status=None, Setting 'renderer' and 'media_type' will typically be deferred, For example being set automatically by the `APIView`. """ - super(Response, self).__init__(None, status=status) + super().__init__(None, status=status) if isinstance(data, Serializer): msg = ( @@ -45,7 +43,7 @@ def __init__(self, data=None, status=None, self.content_type = content_type if headers: - for name, value in six.iteritems(headers): + for name, value in headers.items(): self[name] = value @property @@ -64,13 +62,13 @@ def rendered_content(self): content_type = self.content_type if content_type is None and charset is not None: - content_type = "{0}; charset={1}".format(media_type, charset) + content_type = "{}; charset={}".format(media_type, charset) elif content_type is None: content_type = media_type self['Content-Type'] = content_type ret = renderer.render(self.data, accepted_media_type, context) - if isinstance(ret, six.text_type): + if isinstance(ret, str): assert charset, ( 'renderer returned unicode, and did not specify ' 'a charset value.' @@ -94,7 +92,7 @@ def __getstate__(self): """ Remove attributes from the response that shouldn't be cached. """ - state = super(Response, self).__getstate__() + state = super().__getstate__() for key in ( 'accepted_renderer', 'renderer_context', 'resolver_match', 'client', 'request', 'json', 'wsgi_request' diff --git a/rest_framework/reverse.py b/rest_framework/reverse.py index e9cf737f19..55bf74af18 100644 --- a/rest_framework/reverse.py +++ b/rest_framework/reverse.py @@ -1,11 +1,8 @@ """ Provide urlresolver functions that return fully qualified URLs or view names """ -from __future__ import unicode_literals - from django.urls import NoReverseMatch from django.urls import reverse as django_reverse -from django.utils import six from django.utils.functional import lazy from rest_framework.settings import api_settings @@ -66,4 +63,4 @@ def _reverse(viewname, args=None, kwargs=None, request=None, format=None, **extr return url -reverse_lazy = lazy(reverse, six.text_type) +reverse_lazy = lazy(reverse, str) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 1cacea1812..9334706f89 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -13,8 +13,6 @@ urlpatterns = router.urls """ -from __future__ import unicode_literals - import itertools import warnings from collections import OrderedDict, namedtuple @@ -22,7 +20,6 @@ from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured from django.urls import NoReverseMatch -from django.utils import six from django.utils.deprecation import RenameMethodsBase from rest_framework import ( @@ -39,7 +36,7 @@ DynamicRoute = namedtuple('DynamicRoute', ['url', 'name', 'detail', 'initkwargs']) -class DynamicDetailRoute(object): +class DynamicDetailRoute: def __new__(cls, url, name, initkwargs): warnings.warn( "`DynamicDetailRoute` is deprecated and will be removed in 3.10 " @@ -50,7 +47,7 @@ def __new__(cls, url, name, initkwargs): return DynamicRoute(url, name, True, initkwargs) -class DynamicListRoute(object): +class DynamicListRoute: def __new__(cls, url, name, initkwargs): warnings.warn( "`DynamicListRoute` is deprecated and will be removed in 3.10 in " @@ -83,7 +80,7 @@ class RenameRouterMethods(RenameMethodsBase): ) -class BaseRouter(six.with_metaclass(RenameRouterMethods)): +class BaseRouter(metaclass=RenameRouterMethods): def __init__(self): self.registry = [] @@ -173,7 +170,7 @@ class SimpleRouter(BaseRouter): def __init__(self, trailing_slash=True): self.trailing_slash = '/' if trailing_slash else '' - super(SimpleRouter, self).__init__() + super().__init__() def get_default_basename(self, viewset): """ @@ -365,7 +362,7 @@ def __init__(self, *args, **kwargs): self.root_renderers = kwargs.pop('root_renderers') else: self.root_renderers = list(api_settings.DEFAULT_RENDERER_CLASSES) - super(DefaultRouter, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_api_root_view(self, api_urls=None): """ @@ -383,7 +380,7 @@ def get_urls(self): Generate the list of URL patterns, including a default root view for the API, and appending `.json` style format suffixes. """ - urls = super(DefaultRouter, self).get_urls() + urls = super().get_urls() if self.include_root_view: view = self.get_api_root_view(api_urls=urls) diff --git a/rest_framework/schemas/generators.py b/rest_framework/schemas/generators.py index db226a6c16..b8da446f72 100644 --- a/rest_framework/schemas/generators.py +++ b/rest_framework/schemas/generators.py @@ -11,7 +11,6 @@ from django.contrib.admindocs.views import simplify_regex from django.core.exceptions import PermissionDenied from django.http import Http404 -from django.utils import six from rest_framework import exceptions from rest_framework.compat import ( @@ -68,7 +67,7 @@ class LinkNode(OrderedDict): def __init__(self): self.links = [] self.methods_counter = Counter() - super(LinkNode, self).__init__() + super().__init__() def get_available_key(self, preferred_key): if preferred_key not in self: @@ -140,7 +139,7 @@ def endpoint_ordering(endpoint): ) -class EndpointEnumerator(object): +class EndpointEnumerator: """ A class to determine the available API endpoints that a project exposes. """ @@ -151,7 +150,7 @@ def __init__(self, patterns=None, urlconf=None): urlconf = settings.ROOT_URLCONF # Load the given URLconf module - if isinstance(urlconf, six.string_types): + if isinstance(urlconf, str): urls = import_module(urlconf) else: urls = urlconf @@ -232,7 +231,7 @@ def get_allowed_methods(self, callback): return [method for method in methods if method not in ('OPTIONS', 'HEAD')] -class SchemaGenerator(object): +class SchemaGenerator: # Map HTTP methods onto actions. default_mapping = { 'get': 'retrieve', diff --git a/rest_framework/schemas/inspectors.py b/rest_framework/schemas/inspectors.py index 85142edce4..91d8405eb7 100644 --- a/rest_framework/schemas/inspectors.py +++ b/rest_framework/schemas/inspectors.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ inspectors.py # Per-endpoint view introspection @@ -7,11 +6,11 @@ import re import warnings from collections import OrderedDict +from urllib import parse from weakref import WeakKeyDictionary from django.db import models from django.utils.encoding import force_text, smart_text -from django.utils.six.moves.urllib import parse as urlparse from django.utils.translation import ugettext_lazy as _ from rest_framework import exceptions, serializers @@ -125,7 +124,7 @@ def get_pk_description(model, model_field): ) -class ViewInspector(object): +class ViewInspector: """ Descriptor class on APIView. @@ -207,7 +206,7 @@ def __init__(self, manual_fields=None): * `manual_fields`: list of `coreapi.Field` instances that will be added to auto-generated fields, overwriting on `Field.name` """ - super(AutoSchema, self).__init__() + super().__init__() if manual_fields is None: manual_fields = [] self._manual_fields = manual_fields @@ -232,7 +231,7 @@ def get_link(self, path, method, base_url): path = path[1:] return coreapi.Link( - url=urlparse.urljoin(base_url, path), + url=parse.urljoin(base_url, path), action=method.lower(), encoding=encoding, fields=fields, @@ -475,7 +474,7 @@ def __init__(self, fields, description='', encoding=None): * `fields`: list of `coreapi.Field` instances. * `description`: String description for view. Optional. """ - super(ManualSchema, self).__init__() + super().__init__() assert all(isinstance(f, coreapi.Field) for f in fields), "`fields` must be a list of coreapi.Field instances" self._fields = fields self._description = description @@ -487,7 +486,7 @@ def get_link(self, path, method, base_url): path = path[1:] return coreapi.Link( - url=urlparse.urljoin(base_url, path), + url=parse.urljoin(base_url, path), action=method.lower(), encoding=self._encoding, fields=self._fields, @@ -498,7 +497,7 @@ def get_link(self, path, method, base_url): class DefaultSchema(ViewInspector): """Allows overriding AutoSchema using DEFAULT_SCHEMA_CLASS setting""" def __get__(self, instance, owner): - result = super(DefaultSchema, self).__get__(instance, owner) + result = super().__get__(instance, owner) if not isinstance(result, DefaultSchema): return result diff --git a/rest_framework/schemas/views.py b/rest_framework/schemas/views.py index f5e327a941..fa5cdbdc7a 100644 --- a/rest_framework/schemas/views.py +++ b/rest_framework/schemas/views.py @@ -17,7 +17,7 @@ class SchemaView(APIView): public = False def __init__(self, *args, **kwargs): - super(SchemaView, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if self.renderer_classes is None: self.renderer_classes = [ renderers.OpenAPIRenderer, @@ -38,4 +38,4 @@ def handle_exception(self, exc): 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) + return super().handle_exception(exc) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 9830edb3f0..90b31e068f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -10,8 +10,6 @@ 2. The process of marshalling between python primitives and request and response content is handled by parsers and renderers. """ -from __future__ import unicode_literals - import copy import inspect import traceback @@ -23,11 +21,11 @@ from django.db.models import DurationField as ModelDurationField from django.db.models.fields import Field as DjangoModelField from django.db.models.fields import FieldDoesNotExist -from django.utils import six, timezone +from django.utils import timezone from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import Mapping, postgres_fields, unicode_to_repr +from rest_framework.compat import Mapping, postgres_fields from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.fields import get_error_detail, set_value from rest_framework.settings import api_settings @@ -115,14 +113,14 @@ def __init__(self, instance=None, data=empty, **kwargs): self.partial = kwargs.pop('partial', False) self._context = kwargs.pop('context', {}) kwargs.pop('many', None) - super(BaseSerializer, self).__init__(**kwargs) + super().__init__(**kwargs) def __new__(cls, *args, **kwargs): # We override this method in order to automagically create # `ListSerializer` classes instead when `many=True` is set. if kwargs.pop('many', False): return cls.many_init(*args, **kwargs) - return super(BaseSerializer, cls).__new__(cls, *args, **kwargs) + return super().__new__(cls, *args, **kwargs) @classmethod def many_init(cls, *args, **kwargs): @@ -315,7 +313,7 @@ def _get_declared_fields(cls, bases, attrs): def __new__(cls, name, bases, attrs): attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs) - return super(SerializerMetaclass, cls).__new__(cls, name, bases, attrs) + return super().__new__(cls, name, bases, attrs) def as_serializer_error(exc): @@ -344,8 +342,7 @@ def as_serializer_error(exc): } -@six.add_metaclass(SerializerMetaclass) -class Serializer(BaseSerializer): +class Serializer(BaseSerializer, metaclass=SerializerMetaclass): default_error_messages = { 'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.') } @@ -466,7 +463,7 @@ def run_validators(self, value): to_validate.update(value) else: to_validate = value - super(Serializer, self).run_validators(to_validate) + super().run_validators(to_validate) def to_internal_value(self, data): """ @@ -535,7 +532,7 @@ def validate(self, attrs): return attrs def __repr__(self): - return unicode_to_repr(representation.serializer_repr(self, indent=1)) + return representation.serializer_repr(self, indent=1) # The following are used for accessing `BoundField` instances on the # serializer, for the purposes of presenting a form-like API onto the @@ -560,12 +557,12 @@ def __getitem__(self, key): @property def data(self): - ret = super(Serializer, self).data + ret = super().data return ReturnDict(ret, serializer=self) @property def errors(self): - ret = super(Serializer, self).errors + ret = super().errors if isinstance(ret, list) and len(ret) == 1 and getattr(ret[0], 'code', None) == 'null': # Edge case. Provide a more descriptive error than # "this field may not be null", when no data is passed. @@ -591,11 +588,11 @@ def __init__(self, *args, **kwargs): self.allow_empty = kwargs.pop('allow_empty', True) assert self.child is not None, '`child` is a required argument.' assert not inspect.isclass(self.child), '`child` has not been instantiated.' - super(ListSerializer, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.child.bind(field_name='', parent=self) def bind(self, field_name, parent): - super(ListSerializer, self).bind(field_name, parent) + super().bind(field_name, parent) self.partial = self.parent.partial def get_initial(self): @@ -758,19 +755,19 @@ def is_valid(self, raise_exception=False): return not bool(self._errors) def __repr__(self): - return unicode_to_repr(representation.list_repr(self, indent=1)) + return representation.list_repr(self, indent=1) # Include a backlink to the serializer class on return objects. # Allows renderers such as HTMLFormRenderer to get the full field info. @property def data(self): - ret = super(ListSerializer, self).data + ret = super().data return ReturnList(ret, serializer=self) @property def errors(self): - ret = super(ListSerializer, self).errors + ret = super().errors if isinstance(ret, list) and len(ret) == 1 and getattr(ret[0], 'code', None) == 'null': # Edge case. Provide a more descriptive error than # "this field may not be null", when no data is passed. diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 8db9c81eda..5d92d0cb42 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -18,13 +18,10 @@ REST framework settings, checking for user settings first, then falling back to the defaults. """ -from __future__ import unicode_literals - from importlib import import_module from django.conf import settings from django.test.signals import setting_changed -from django.utils import six from rest_framework import ISO_8601 @@ -166,7 +163,7 @@ def perform_import(val, setting_name): """ if val is None: return None - elif isinstance(val, six.string_types): + elif isinstance(val, str): return import_from_string(val, setting_name) elif isinstance(val, (list, tuple)): return [import_from_string(item, setting_name) for item in val] @@ -187,7 +184,7 @@ def import_from_string(val, setting_name): raise ImportError(msg) -class APISettings(object): +class APISettings: """ A settings object, that allows API settings to be accessed as properties. For example: diff --git a/rest_framework/status.py b/rest_framework/status.py index 4b4561cfcd..1489b440cf 100644 --- a/rest_framework/status.py +++ b/rest_framework/status.py @@ -5,7 +5,6 @@ And RFC 6585 - https://tools.ietf.org/html/rfc6585 And RFC 4918 - https://tools.ietf.org/html/rfc4918 """ -from __future__ import unicode_literals def is_informational(code): diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index f48675d5eb..56e2994ea1 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -1,12 +1,9 @@ -from __future__ import absolute_import, unicode_literals - import re from collections import OrderedDict from django import template from django.template import loader from django.urls import NoReverseMatch, reverse -from django.utils import six from django.utils.encoding import force_text, iri_to_uri from django.utils.html import escape, format_html, smart_urlquote from django.utils.safestring import SafeData, mark_safe @@ -187,7 +184,7 @@ def add_class(value, css_class): In the case of REST Framework, the filter is used to add Bootstrap-specific classes to the forms. """ - html = six.text_type(value) + html = str(value) match = class_re.search(html) if match: m = re.search(r'^%s$|^%s\s|\s%s\s|\s%s$' % (css_class, css_class, @@ -204,7 +201,7 @@ def add_class(value, css_class): @register.filter def format_value(value): if getattr(value, 'is_hyperlink', False): - name = six.text_type(value.obj) + name = str(value.obj) return mark_safe('%s' % (value, escape(name))) if value is None or isinstance(value, bool): return mark_safe('%s' % {True: 'true', False: 'false', None: 'null'}[value]) @@ -219,7 +216,7 @@ def format_value(value): template = loader.get_template('rest_framework/admin/dict_value.html') context = {'value': value} return template.render(context) - elif isinstance(value, six.string_types): + elif isinstance(value, str): if ( (value.startswith('http:') or value.startswith('https:')) and not re.search(r'\s', value) @@ -229,7 +226,7 @@ def format_value(value): return mark_safe('{value}'.format(value=escape(value))) elif '\n' in value: return mark_safe('
%s
' % escape(value)) - return six.text_type(value) + return str(value) @register.filter diff --git a/rest_framework/test.py b/rest_framework/test.py index edacf0066d..852d4919e7 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -1,9 +1,5 @@ -# -- coding: utf-8 -- - # Note that we import as `DjangoRequestFactory` and `DjangoClient` in order # to make it harder for the user to import the wrong thing without realizing. -from __future__ import unicode_literals - import io from importlib import import_module @@ -14,7 +10,6 @@ from django.test.client import Client as DjangoClient from django.test.client import ClientHandler from django.test.client import RequestFactory as DjangoRequestFactory -from django.utils import six from django.utils.encoding import force_bytes from django.utils.http import urlencode @@ -32,7 +27,7 @@ class HeaderDict(requests.packages.urllib3._collections.HTTPHeaderDict): def get_all(self, key, default): return self.getheaders(key) - class MockOriginalResponse(object): + class MockOriginalResponse: def __init__(self, headers): self.msg = HeaderDict(headers) self.closed = False @@ -109,7 +104,7 @@ def close(self): class RequestsClient(requests.Session): def __init__(self, *args, **kwargs): - super(RequestsClient, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) adapter = DjangoTestAdapter() self.mount('http://', adapter) self.mount('https://', adapter) @@ -117,7 +112,7 @@ def __init__(self, *args, **kwargs): def request(self, method, url, *args, **kwargs): if not url.startswith('http'): raise ValueError('Missing "http:" or "https:". Use a fully qualified URL, eg "http://testserver%s"' % url) - return super(RequestsClient, self).request(method, url, *args, **kwargs) + return super().request(method, url, *args, **kwargs) else: def RequestsClient(*args, **kwargs): @@ -129,7 +124,7 @@ class CoreAPIClient(coreapi.Client): def __init__(self, *args, **kwargs): self._session = RequestsClient() kwargs['transports'] = [coreapi.transports.HTTPTransport(session=self.session)] - return super(CoreAPIClient, self).__init__(*args, **kwargs) + return super().__init__(*args, **kwargs) @property def session(self): @@ -149,7 +144,7 @@ def __init__(self, enforce_csrf_checks=False, **defaults): self.renderer_classes = {} for cls in self.renderer_classes_list: self.renderer_classes[cls.format] = cls - super(APIRequestFactory, self).__init__(**defaults) + super().__init__(**defaults) def _encode_data(self, data, format=None, content_type=None): """ @@ -171,7 +166,7 @@ def _encode_data(self, data, format=None, content_type=None): format = format or self.default_format assert format in self.renderer_classes, ( - "Invalid format '{0}'. Available formats are {1}. " + "Invalid format '{}'. Available formats are {}. " "Set TEST_REQUEST_RENDERER_CLASSES to enable " "extra request formats.".format( format, @@ -184,12 +179,12 @@ def _encode_data(self, data, format=None, content_type=None): ret = renderer.render(data) # Determine the content-type header from the renderer - content_type = "{0}; charset={1}".format( + content_type = "{}; charset={}".format( renderer.media_type, renderer.charset ) # Coerce text to bytes if required. - if isinstance(ret, six.text_type): + if isinstance(ret, str): ret = bytes(ret.encode(renderer.charset)) return ret, content_type @@ -202,8 +197,7 @@ def get(self, path, data=None, **extra): # Fix to support old behavior where you have the arguments in the # url. See #1461. query_string = force_bytes(path.split('?')[1]) - if six.PY3: - query_string = query_string.decode('iso-8859-1') + query_string = query_string.decode('iso-8859-1') r['QUERY_STRING'] = query_string r.update(extra) return self.generic('GET', path, **r) @@ -234,11 +228,11 @@ def generic(self, method, path, data='', if content_type is not None: extra['CONTENT_TYPE'] = str(content_type) - return super(APIRequestFactory, self).generic( + return super().generic( method, path, data, content_type, secure, **extra) def request(self, **kwargs): - request = super(APIRequestFactory, self).request(**kwargs) + request = super().request(**kwargs) request._dont_enforce_csrf_checks = not self.enforce_csrf_checks return request @@ -252,18 +246,18 @@ class ForceAuthClientHandler(ClientHandler): def __init__(self, *args, **kwargs): self._force_user = None self._force_token = None - super(ForceAuthClientHandler, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def get_response(self, request): # This is the simplest place we can hook into to patch the # request object. force_authenticate(request, self._force_user, self._force_token) - return super(ForceAuthClientHandler, self).get_response(request) + return super().get_response(request) class APIClient(APIRequestFactory, DjangoClient): def __init__(self, enforce_csrf_checks=False, **defaults): - super(APIClient, self).__init__(**defaults) + super().__init__(**defaults) self.handler = ForceAuthClientHandler(enforce_csrf_checks) self._credentials = {} @@ -286,17 +280,17 @@ def force_authenticate(self, user=None, token=None): def request(self, **kwargs): # Ensure that any credentials set get added to every request. kwargs.update(self._credentials) - return super(APIClient, self).request(**kwargs) + return super().request(**kwargs) def get(self, path, data=None, follow=False, **extra): - response = super(APIClient, self).get(path, data=data, **extra) + response = super().get(path, data=data, **extra) if follow: response = self._handle_redirects(response, **extra) return response def post(self, path, data=None, format=None, content_type=None, follow=False, **extra): - response = super(APIClient, self).post( + response = super().post( path, data=data, format=format, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) @@ -304,7 +298,7 @@ def post(self, path, data=None, format=None, content_type=None, def put(self, path, data=None, format=None, content_type=None, follow=False, **extra): - response = super(APIClient, self).put( + response = super().put( path, data=data, format=format, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) @@ -312,7 +306,7 @@ def put(self, path, data=None, format=None, content_type=None, def patch(self, path, data=None, format=None, content_type=None, follow=False, **extra): - response = super(APIClient, self).patch( + response = super().patch( path, data=data, format=format, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) @@ -320,7 +314,7 @@ def patch(self, path, data=None, format=None, content_type=None, def delete(self, path, data=None, format=None, content_type=None, follow=False, **extra): - response = super(APIClient, self).delete( + response = super().delete( path, data=data, format=format, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) @@ -328,7 +322,7 @@ def delete(self, path, data=None, format=None, content_type=None, def options(self, path, data=None, format=None, content_type=None, follow=False, **extra): - response = super(APIClient, self).options( + response = super().options( path, data=data, format=format, content_type=content_type, **extra) if follow: response = self._handle_redirects(response, **extra) @@ -342,7 +336,7 @@ def logout(self): self.handler._force_token = None if self.session: - super(APIClient, self).logout() + super().logout() class APITransactionTestCase(testcases.TransactionTestCase): @@ -389,11 +383,11 @@ def setUpClass(cls): cls._module.urlpatterns = cls.urlpatterns cls._override.enable() - super(URLPatternsTestCase, cls).setUpClass() + super().setUpClass() @classmethod def tearDownClass(cls): - super(URLPatternsTestCase, cls).tearDownClass() + super().tearDownClass() cls._override.disable() if hasattr(cls, '_module_urlpatterns'): diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 834ced148e..0ba2ba66b1 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -1,8 +1,6 @@ """ Provides various throttling policies. """ -from __future__ import unicode_literals - import time from django.core.cache import cache as default_cache @@ -11,7 +9,7 @@ from rest_framework.settings import api_settings -class BaseThrottle(object): +class BaseThrottle: """ Rate throttling of requests. """ @@ -232,7 +230,7 @@ def allow_request(self, request, view): self.num_requests, self.duration = self.parse_rate(self.rate) # We can now proceed as normal. - return super(ScopedRateThrottle, self).allow_request(request, view) + return super().allow_request(request, view) def get_cache_key(self, request, view): """ diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index ab3a74978f..831d344ddc 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import include, url from rest_framework.compat import ( diff --git a/rest_framework/urls.py b/rest_framework/urls.py index 0e4c2661bd..482a0a3642 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -11,8 +11,6 @@ You should make sure your authentication settings include `SessionAuthentication`. """ -from __future__ import unicode_literals - from django.conf.urls import url from django.contrib.auth import views diff --git a/rest_framework/utils/breadcrumbs.py b/rest_framework/utils/breadcrumbs.py index e0374ffd00..54990e9f6c 100644 --- a/rest_framework/utils/breadcrumbs.py +++ b/rest_framework/utils/breadcrumbs.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.urls import get_script_prefix, resolve diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index d8f4aeb4eb..dee2f942e8 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -1,15 +1,13 @@ """ Helper classes for parsers. """ -from __future__ import absolute_import, unicode_literals - import datetime import decimal import json # noqa import uuid from django.db.models.query import QuerySet -from django.utils import six, timezone +from django.utils import timezone from django.utils.encoding import force_text from django.utils.functional import Promise @@ -39,12 +37,12 @@ def default(self, obj): representation = obj.isoformat() return representation elif isinstance(obj, datetime.timedelta): - return six.text_type(obj.total_seconds()) + return str(obj.total_seconds()) elif isinstance(obj, decimal.Decimal): # Serializers will coerce decimals to strings by default. return float(obj) elif isinstance(obj, uuid.UUID): - return six.text_type(obj) + return str(obj) elif isinstance(obj, QuerySet): return tuple(obj) elif isinstance(obj, bytes): @@ -65,4 +63,4 @@ def default(self, obj): pass elif hasattr(obj, '__iter__'): return tuple(item for item in obj) - return super(JSONEncoder, self).default(obj) + return super().default(obj) diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index 927d08ff25..1281ee1672 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -16,7 +16,7 @@ ) -class ClassLookupDict(object): +class ClassLookupDict: """ Takes a dictionary with classes as keys. Lookups against this object will traverses the object's inheritance diff --git a/rest_framework/utils/formatting.py b/rest_framework/utils/formatting.py index aa805f14e3..4e003f6140 100644 --- a/rest_framework/utils/formatting.py +++ b/rest_framework/utils/formatting.py @@ -1,8 +1,6 @@ """ Utility functions to return a formatted name and description for a given view. """ -from __future__ import unicode_literals - import re from django.utils.encoding import force_text diff --git a/rest_framework/utils/json.py b/rest_framework/utils/json.py index cb55723801..1c1e69bf16 100644 --- a/rest_framework/utils/json.py +++ b/rest_framework/utils/json.py @@ -5,9 +5,6 @@ spec-compliant encoding/decoding. Support for non-standard features should be handled by users at the renderer and parser layer. """ - -from __future__ import absolute_import - import functools import json # noqa diff --git a/rest_framework/utils/mediatypes.py b/rest_framework/utils/mediatypes.py index f4acf4807e..40bdf26153 100644 --- a/rest_framework/utils/mediatypes.py +++ b/rest_framework/utils/mediatypes.py @@ -3,10 +3,7 @@ See https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7 """ -from __future__ import unicode_literals - from django.http.multipartparser import parse_header -from django.utils.encoding import python_2_unicode_compatible from rest_framework import HTTP_HEADER_ENCODING @@ -46,8 +43,7 @@ def order_by_precedence(media_type_lst): return [media_types for media_types in ret if media_types] -@python_2_unicode_compatible -class _MediaType(object): +class _MediaType: def __init__(self, media_type_str): self.orig = '' if (media_type_str is None) else media_type_str self.full_type, self.params = parse_header(self.orig.encode(HTTP_HEADER_ENCODING)) diff --git a/rest_framework/utils/representation.py b/rest_framework/utils/representation.py index deeaf1f63f..ebead5d759 100644 --- a/rest_framework/utils/representation.py +++ b/rest_framework/utils/representation.py @@ -2,16 +2,12 @@ Helper functions for creating user-friendly representations of serializer classes and serializer fields. """ -from __future__ import unicode_literals - import re from django.db import models from django.utils.encoding import force_text from django.utils.functional import Promise -from rest_framework.compat import unicode_repr - def manager_repr(value): model = value.model @@ -34,7 +30,7 @@ def smart_repr(value): if isinstance(value, Promise) and value._delegate_text: value = force_text(value) - value = unicode_repr(value) + value = repr(value) # Representations like u'help text' # should simply be presented as 'help text' diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index c24e51d091..8709352f17 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -1,10 +1,8 @@ -from __future__ import unicode_literals - from collections import OrderedDict from django.utils.encoding import force_text -from rest_framework.compat import MutableMapping, unicode_to_repr +from rest_framework.compat import MutableMapping from rest_framework.utils import json @@ -17,7 +15,7 @@ class ReturnDict(OrderedDict): def __init__(self, *args, **kwargs): self.serializer = kwargs.pop('serializer') - super(ReturnDict, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def copy(self): return ReturnDict(self, serializer=self.serializer) @@ -40,7 +38,7 @@ class ReturnList(list): def __init__(self, *args, **kwargs): self.serializer = kwargs.pop('serializer') - super(ReturnList, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __repr__(self): return list.__repr__(self) @@ -51,7 +49,7 @@ def __reduce__(self): return (list, (list(self),)) -class BoundField(object): +class BoundField: """ A field object that also includes `.value` and `.error` properties. Returned when iterating over a serializer instance, @@ -73,9 +71,9 @@ def _proxy_class(self): return self._field.__class__ def __repr__(self): - return unicode_to_repr('<%s value=%s errors=%s>' % ( + return '<%s value=%s errors=%s>' % ( self.__class__.__name__, self.value, self.errors - )) + ) def as_form_field(self): value = '' if (self.value is None or self.value is False) else self.value @@ -103,9 +101,9 @@ class NestedBoundField(BoundField): """ def __init__(self, field, value, errors, prefix=''): - if value is None or value is '': + if value is None or value == '': value = {} - super(NestedBoundField, self).__init__(field, value, errors, prefix) + super().__init__(field, value, errors, prefix) def __iter__(self): for field in self.fields.values(): diff --git a/rest_framework/utils/urls.py b/rest_framework/utils/urls.py index 3766928d42..3534e5f49a 100644 --- a/rest_framework/utils/urls.py +++ b/rest_framework/utils/urls.py @@ -1,5 +1,6 @@ +from urllib import parse + from django.utils.encoding import force_str -from django.utils.six.moves.urllib import parse as urlparse def replace_query_param(url, key, val): @@ -7,11 +8,11 @@ def replace_query_param(url, key, val): Given a URL and a key/val pair, set or replace an item in the query parameters of the URL, and return the new URL. """ - (scheme, netloc, path, query, fragment) = urlparse.urlsplit(force_str(url)) - query_dict = urlparse.parse_qs(query, keep_blank_values=True) + (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url)) + query_dict = parse.parse_qs(query, keep_blank_values=True) query_dict[force_str(key)] = [force_str(val)] - query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True) - return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) + query = parse.urlencode(sorted(list(query_dict.items())), doseq=True) + return parse.urlunsplit((scheme, netloc, path, query, fragment)) def remove_query_param(url, key): @@ -19,8 +20,8 @@ def remove_query_param(url, key): Given a URL and a key/val pair, remove an item in the query parameters of the URL, and return the new URL. """ - (scheme, netloc, path, query, fragment) = urlparse.urlsplit(force_str(url)) - query_dict = urlparse.parse_qs(query, keep_blank_values=True) + (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url)) + query_dict = parse.parse_qs(query, keep_blank_values=True) query_dict.pop(key, None) - query = urlparse.urlencode(sorted(list(query_dict.items())), doseq=True) - return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) + query = parse.urlencode(sorted(list(query_dict.items())), doseq=True) + return parse.urlunsplit((scheme, netloc, path, query, fragment)) diff --git a/rest_framework/validators.py b/rest_framework/validators.py index 2ea3e5ac15..a5222fbc66 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -6,12 +6,9 @@ object creation, and makes it possible to switch between using the implicit `ModelSerializer` class and an equivalent explicit `Serializer` class. """ -from __future__ import unicode_literals - from django.db import DataError from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import unicode_to_repr from rest_framework.exceptions import ValidationError from rest_framework.utils.representation import smart_repr @@ -33,7 +30,7 @@ def qs_filter(queryset, **kwargs): return queryset.none() -class UniqueValidator(object): +class UniqueValidator: """ Validator that corresponds to `unique=True` on a model field. @@ -82,13 +79,13 @@ def __call__(self, value): raise ValidationError(self.message, code='unique') def __repr__(self): - return unicode_to_repr('<%s(queryset=%s)>' % ( + return '<%s(queryset=%s)>' % ( self.__class__.__name__, smart_repr(self.queryset) - )) + ) -class UniqueTogetherValidator(object): +class UniqueTogetherValidator: """ Validator that corresponds to `unique_together = (...)` on a model class. @@ -170,14 +167,14 @@ def __call__(self, attrs): raise ValidationError(message, code='unique') def __repr__(self): - return unicode_to_repr('<%s(queryset=%s, fields=%s)>' % ( + return '<%s(queryset=%s, fields=%s)>' % ( self.__class__.__name__, smart_repr(self.queryset), smart_repr(self.fields) - )) + ) -class BaseUniqueForValidator(object): +class BaseUniqueForValidator: message = None missing_message = _('This field is required.') @@ -236,12 +233,12 @@ def __call__(self, attrs): }, code='unique') def __repr__(self): - return unicode_to_repr('<%s(queryset=%s, field=%s, date_field=%s)>' % ( + return '<%s(queryset=%s, field=%s, date_field=%s)>' % ( self.__class__.__name__, smart_repr(self.queryset), smart_repr(self.field), smart_repr(self.date_field) - )) + ) class UniqueForDateValidator(BaseUniqueForValidator): diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py index 206ff6c2ec..0631a75c97 100644 --- a/rest_framework/versioning.py +++ b/rest_framework/versioning.py @@ -1,6 +1,3 @@ -# coding: utf-8 -from __future__ import unicode_literals - import re from django.utils.translation import ugettext_lazy as _ @@ -13,7 +10,7 @@ from rest_framework.utils.mediatypes import _MediaType -class BaseVersioning(object): +class BaseVersioning: default_version = api_settings.DEFAULT_VERSION allowed_versions = api_settings.ALLOWED_VERSIONS version_param = api_settings.VERSION_PARAM @@ -87,7 +84,7 @@ def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, * kwargs = {} if (kwargs is None) else kwargs kwargs[self.version_param] = request.version - return super(URLPathVersioning, self).reverse( + return super().reverse( viewname, args, kwargs, request, format, **extra ) @@ -133,7 +130,7 @@ def determine_version(self, request, *args, **kwargs): def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): if request.version is not None: viewname = self.get_versioned_viewname(viewname, request) - return super(NamespaceVersioning, self).reverse( + return super().reverse( viewname, args, kwargs, request, format, **extra ) @@ -179,7 +176,7 @@ def determine_version(self, request, *args, **kwargs): return version def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): - url = super(QueryParameterVersioning, self).reverse( + url = super().reverse( viewname, args, kwargs, request, format, **extra ) if request.version is not None: diff --git a/rest_framework/views.py b/rest_framework/views.py index 9d5d959e9d..6ef7021d4a 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -1,8 +1,6 @@ """ Provides an APIView class that is the base of all views in REST framework. """ -from __future__ import unicode_literals - from django.conf import settings from django.core.exceptions import PermissionDenied from django.db import connection, models, transaction @@ -137,7 +135,7 @@ def force_evaluation(): ) cls.queryset._fetch_all = force_evaluation - view = super(APIView, cls).as_view(**initkwargs) + view = super().as_view(**initkwargs) view.cls = cls view.initkwargs = initkwargs diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index 7146828d2f..ad5633854f 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -16,8 +16,6 @@ router.register(r'users', UserViewSet, 'user') urlpatterns = router.urls """ -from __future__ import unicode_literals - from collections import OrderedDict from functools import update_wrapper from inspect import getmembers @@ -34,7 +32,7 @@ def _is_extra_action(attr): return hasattr(attr, 'mapping') -class ViewSetMixin(object): +class ViewSetMixin: """ This is the magic. @@ -134,7 +132,7 @@ def initialize_request(self, request, *args, **kwargs): """ Set the `.action` attribute on the view, depending on the request method. """ - request = super(ViewSetMixin, self).initialize_request(request, *args, **kwargs) + request = super().initialize_request(request, *args, **kwargs) method = request.method.lower() if method == 'options': # This is a special case as we always provide handling for the diff --git a/runtests.py b/runtests.py index 16b47ce2a4..a32dde96c5 100755 --- a/runtests.py +++ b/runtests.py @@ -1,6 +1,4 @@ -#! /usr/bin/env python -from __future__ import print_function - +#! /usr/bin/env python3 import subprocess import sys diff --git a/setup.cfg b/setup.cfg index c95134600d..b4dee68044 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,3 @@ -[bdist_wheel] -universal = 1 - [metadata] license_file = LICENSE.md diff --git a/setup.py b/setup.py index cb850a3aee..632c7dfd3b 100755 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +#!/usr/bin/env python3 import os import re import shutil @@ -8,6 +7,34 @@ from setuptools import find_packages, setup +CURRENT_PYTHON = sys.version_info[:2] +REQUIRED_PYTHON = (3, 4) + +# This check and everything above must remain compatible with Python 2.7. +if CURRENT_PYTHON < REQUIRED_PYTHON: + sys.stderr.write(""" +========================== +Unsupported Python version +========================== + +This version of Django REST Framework requires Python {}.{}, but you're trying +to install it on Python {}.{}. + +This may be because you are using a version of pip that doesn't +understand the python_requires classifier. Make sure you +have pip >= 9.0 and setuptools >= 24.2, then try again: + + $ python -m pip install --upgrade pip setuptools + $ python -m pip install djangorestframework + +This will install the latest version of Django REST Framework which works on +your version of Python. If you can't upgrade your pip (or Python), request +an older version of Django REST Framework: + + $ python -m pip install "django<3.10" +""".format(*(REQUIRED_PYTHON + CURRENT_PYTHON))) + sys.exit(1) + def read(f): return open(f, 'r', encoding='utf-8').read() @@ -52,7 +79,7 @@ def get_version(package): packages=find_packages(exclude=['tests*']), include_package_data=True, install_requires=[], - python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + python_requires=">=3.4", zip_safe=False, classifiers=[ 'Development Status :: 5 - Production/Stable', @@ -66,13 +93,12 @@ def get_version(package): 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3 :: Only', 'Topic :: Internet :: WWW/HTTP', ] ) diff --git a/tests/authentication/migrations/0001_initial.py b/tests/authentication/migrations/0001_initial.py index cfc8872400..548b3576bb 100644 --- a/tests/authentication/migrations/0001_initial.py +++ b/tests/authentication/migrations/0001_initial.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf import settings from django.db import migrations, models diff --git a/tests/authentication/models.py b/tests/authentication/models.py index b8d1fd5a6b..1a721de4d3 100644 --- a/tests/authentication/models.py +++ b/tests/authentication/models.py @@ -1,6 +1,3 @@ -# coding: utf-8 -from __future__ import unicode_literals - from django.conf import settings from django.db import models diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py index 7937735424..f7e9fcf18a 100644 --- a/tests/authentication/test_authentication.py +++ b/tests/authentication/test_authentication.py @@ -1,7 +1,3 @@ -# coding: utf-8 - -from __future__ import unicode_literals - import base64 import pytest @@ -10,7 +6,6 @@ from django.contrib.auth.models import User from django.http import HttpResponse from django.test import TestCase, override_settings -from django.utils import six from rest_framework import ( HTTP_HEADER_ENCODING, exceptions, permissions, renderers, status @@ -253,7 +248,7 @@ def test_post_form_session_auth_failing(self): assert response.status_code == status.HTTP_403_FORBIDDEN -class BaseTokenAuthTests(object): +class BaseTokenAuthTests: """Token authentication""" model = None path = None @@ -381,7 +376,7 @@ def test_generate_key_returns_string(self): """Ensure generate_key returns a string""" token = self.model() key = token.generate_key() - assert isinstance(key, six.string_types) + assert isinstance(key, str) def test_token_login_json(self): """Ensure token login view using JSON POST works.""" @@ -534,7 +529,7 @@ def test_basic_authentication_raises_error_if_user_not_found(self): def test_basic_authentication_raises_error_if_user_not_active(self): from rest_framework import authentication - class MockUser(object): + class MockUser: is_active = False old_authenticate = authentication.authenticate authentication.authenticate = lambda **kwargs: MockUser() diff --git a/tests/browsable_api/auth_urls.py b/tests/browsable_api/auth_urls.py index 0e93797172..7530c5e408 100644 --- a/tests/browsable_api/auth_urls.py +++ b/tests/browsable_api/auth_urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import include, url from .views import MockView diff --git a/tests/browsable_api/no_auth_urls.py b/tests/browsable_api/no_auth_urls.py index 5fc95c7276..348bfe1c0c 100644 --- a/tests/browsable_api/no_auth_urls.py +++ b/tests/browsable_api/no_auth_urls.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from .views import MockView diff --git a/tests/browsable_api/test_browsable_api.py b/tests/browsable_api/test_browsable_api.py index 684d7ae143..81090e2235 100644 --- a/tests/browsable_api/test_browsable_api.py +++ b/tests/browsable_api/test_browsable_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.contrib.auth.models import User from django.test import TestCase, override_settings diff --git a/tests/browsable_api/test_browsable_nested_api.py b/tests/browsable_api/test_browsable_nested_api.py index 8f38b3c4e5..3fef74023d 100644 --- a/tests/browsable_api/test_browsable_nested_api.py +++ b/tests/browsable_api/test_browsable_nested_api.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from django.test import TestCase from django.test.utils import override_settings diff --git a/tests/browsable_api/views.py b/tests/browsable_api/views.py index 03758f10b3..e1cf13a1ec 100644 --- a/tests/browsable_api/views.py +++ b/tests/browsable_api/views.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from rest_framework import authentication, renderers from rest_framework.response import Response from rest_framework.views import APIView diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py index 55bc243cbd..20df3e4a2d 100644 --- a/tests/generic_relations/models.py +++ b/tests/generic_relations/models.py @@ -1,14 +1,10 @@ -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. @@ -22,7 +18,6 @@ def __str__(self): return self.tag -@python_2_unicode_compatible class Bookmark(models.Model): """ A URL bookmark that may have multiple tags attached. @@ -34,7 +29,6 @@ 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. diff --git a/tests/generic_relations/test_generic_relations.py b/tests/generic_relations/test_generic_relations.py index c8de332e1d..33f8ea1d01 100644 --- a/tests/generic_relations/test_generic_relations.py +++ b/tests/generic_relations/test_generic_relations.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import TestCase from rest_framework import serializers diff --git a/tests/models.py b/tests/models.py index 17bf23cda4..f389a51a92 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import uuid from django.db import models diff --git a/tests/test_api_client.py b/tests/test_api_client.py index e4354ec603..74a3579e2f 100644 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import os import tempfile import unittest diff --git a/tests/test_atomic_requests.py b/tests/test_atomic_requests.py index bddd480a5a..de04d2c069 100644 --- a/tests/test_atomic_requests.py +++ b/tests/test_atomic_requests.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import unittest from django.conf.urls import url @@ -38,7 +36,7 @@ def post(self, request, *args, **kwargs): class NonAtomicAPIExceptionView(APIView): @transaction.non_atomic_requests def dispatch(self, *args, **kwargs): - return super(NonAtomicAPIExceptionView, self).dispatch(*args, **kwargs) + return super().dispatch(*args, **kwargs) def get(self, request, *args, **kwargs): BasicModel.objects.all() diff --git a/tests/test_authtoken.py b/tests/test_authtoken.py index c8957f9785..036e317efd 100644 --- a/tests/test_authtoken.py +++ b/tests/test_authtoken.py @@ -1,9 +1,10 @@ +from io import StringIO + import pytest from django.contrib.admin import site from django.contrib.auth.models import User from django.core.management import CommandError, call_command from django.test import TestCase -from django.utils.six import StringIO from rest_framework.authtoken.admin import TokenAdmin from rest_framework.authtoken.management.commands.drf_create_token import \ diff --git a/tests/test_bound_fields.py b/tests/test_bound_fields.py index e588ae6239..dc5ab542ff 100644 --- a/tests/test_bound_fields.py +++ b/tests/test_bound_fields.py @@ -28,7 +28,7 @@ class ExampleSerializer(serializers.Serializer): assert serializer['text'].value == 'abc' assert serializer['text'].errors is None assert serializer['text'].name == 'text' - assert serializer['amount'].value is 123 + assert serializer['amount'].value == 123 assert serializer['amount'].errors is None assert serializer['amount'].name == 'amount' @@ -43,7 +43,7 @@ class ExampleSerializer(serializers.Serializer): assert serializer['text'].value == 'x' * 1000 assert serializer['text'].errors == ['Ensure this field has no more than 100 characters.'] assert serializer['text'].name == 'text' - assert serializer['amount'].value is 123 + assert serializer['amount'].value == 123 assert serializer['amount'].errors is None assert serializer['amount'].name == 'amount' diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 13dd41ff3a..3f24e7ef03 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import pytest from django.test import TestCase diff --git a/tests/test_description.py b/tests/test_description.py index 702e56332f..ae00fe4a97 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -1,9 +1,4 @@ -# -- coding: utf-8 -- - -from __future__ import unicode_literals - from django.test import TestCase -from django.utils.encoding import python_2_unicode_compatible from rest_framework.compat import apply_markdown from rest_framework.utils.formatting import dedent @@ -157,8 +152,8 @@ class that can be converted to a string. """ # use a mock object instead of gettext_lazy to ensure that we can't end # up with a test case string in our l10n catalog - @python_2_unicode_compatible - class MockLazyStr(object): + + class MockLazyStr: def __init__(self, string): self.s = string diff --git a/tests/test_encoders.py b/tests/test_encoders.py index 12eca8105d..c66954b807 100644 --- a/tests/test_encoders.py +++ b/tests/test_encoders.py @@ -10,7 +10,7 @@ from rest_framework.utils.encoders import JSONEncoder -class MockList(object): +class MockList: def tolist(self): return [1, 2, 3] diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index ce0ed8514f..13b1b47571 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,8 +1,5 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.test import RequestFactory, TestCase -from django.utils import six, translation +from django.utils import translation from django.utils.translation import ugettext_lazy as _ from rest_framework.exceptions import ( @@ -46,12 +43,12 @@ def test_get_full_details_with_throttling(self): exception = Throttled(wait=2) assert exception.get_full_details() == { - 'message': 'Request was throttled. Expected available in {} seconds.'.format(2 if six.PY3 else 2.), + 'message': 'Request was throttled. Expected available in {} seconds.'.format(2), 'code': 'throttled'} exception = Throttled(wait=2, detail='Slow down!') assert exception.get_full_details() == { - 'message': 'Slow down! Expected available in {} seconds.'.format(2 if six.PY3 else 2.), + 'message': 'Slow down! Expected available in {} seconds.'.format(2), 'code': 'throttled'} @@ -92,7 +89,7 @@ class TranslationTests(TestCase): def test_message(self): # this test largely acts as a sanity test to ensure the translation files are present. self.assertEqual(_('A server error occurred.'), 'Une erreur du serveur est survenue.') - self.assertEqual(six.text_type(APIException()), 'Une erreur du serveur est survenue.') + self.assertEqual(str(APIException()), 'Une erreur du serveur est survenue.') def test_server_error(): diff --git a/tests/test_fields.py b/tests/test_fields.py index 42adedfed9..e0833564b4 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -10,7 +10,6 @@ from django.core.exceptions import ValidationError as DjangoValidationError from django.http import QueryDict from django.test import TestCase, override_settings -from django.utils import six from django.utils.timezone import activate, deactivate, override, utc import rest_framework @@ -167,7 +166,7 @@ def test_default(self): """ field = serializers.IntegerField(default=123) output = field.run_validation() - assert output is 123 + assert output == 123 class TestSource: @@ -193,7 +192,7 @@ def test_callable_source(self): class ExampleSerializer(serializers.Serializer): example_field = serializers.CharField(source='example_callable') - class ExampleInstance(object): + class ExampleInstance: def example_callable(self): return 'example callable value' @@ -204,7 +203,7 @@ def test_callable_source_raises(self): class ExampleSerializer(serializers.Serializer): example_field = serializers.CharField(source='example_callable', read_only=True) - class ExampleInstance(object): + class ExampleInstance: def example_callable(self): raise AttributeError('method call failed') @@ -754,7 +753,7 @@ def test_iterable_validators(self): def raise_exception(value): raise exceptions.ValidationError('Raised error') - for validators in ([raise_exception], (raise_exception,), set([raise_exception])): + for validators in ([raise_exception], (raise_exception,), {raise_exception}): field = serializers.CharField(validators=validators) with pytest.raises(serializers.ValidationError) as exc_info: field.run_validation(value) @@ -822,7 +821,7 @@ def test_allow_unicode_true(self): validation_error = False try: - field.run_validation(u'slug-99-\u0420') + field.run_validation('slug-99-\u0420') except serializers.ValidationError: validation_error = True @@ -1148,7 +1147,7 @@ def test_to_representation(self): def test_localize_forces_coerce_to_string(self): field = serializers.DecimalField(max_digits=2, decimal_places=1, coerce_to_string=False, localize=True) - assert isinstance(field.to_representation(Decimal('1.1')), six.string_types) + assert isinstance(field.to_representation(Decimal('1.1')), str) class TestQuantizedValueForDecimal(TestCase): @@ -1219,7 +1218,7 @@ class TestDateField(FieldValues): outputs = { datetime.date(2001, 1, 1): '2001-01-01', '2001-01-01': '2001-01-01', - six.text_type('2016-01-10'): '2016-01-10', + str('2016-01-10'): '2016-01-10', None: None, '': None, } @@ -1286,7 +1285,7 @@ class TestDateTimeField(FieldValues): datetime.datetime(2001, 1, 1, 13, 00): '2001-01-01T13:00:00Z', datetime.datetime(2001, 1, 1, 13, 00, tzinfo=utc): '2001-01-01T13:00:00Z', '2001-01-01T00:00:00': '2001-01-01T00:00:00', - six.text_type('2016-01-10T00:00:00'): '2016-01-10T00:00:00', + str('2016-01-10T00:00:00'): '2016-01-10T00:00:00', None: None, '': None, } @@ -1628,7 +1627,7 @@ def test_edit_choices(self): ] ) field.choices = [1] - assert field.run_validation(1) is 1 + assert field.run_validation(1) == 1 with pytest.raises(serializers.ValidationError) as exc_info: field.run_validation(2) assert exc_info.value.detail == ['"2" is not a valid choice.'] diff --git a/tests/test_filters.py b/tests/test_filters.py index a53fa192a1..a52f40103f 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1,6 +1,5 @@ -from __future__ import unicode_literals - import datetime +from importlib import reload as reload_module import pytest from django.core.exceptions import ImproperlyConfigured @@ -8,7 +7,6 @@ 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 from rest_framework import filters, generics, serializers from rest_framework.compat import coreschema @@ -163,7 +161,7 @@ class CustomSearchFilter(filters.SearchFilter): 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) + return super().get_search_fields(view, request) class SearchListView(generics.ListAPIView): queryset = SearchFilterModel.objects.all() diff --git a/tests/test_generateschema.py b/tests/test_generateschema.py index 915c6ea059..a6a1f2bedb 100644 --- a/tests/test_generateschema.py +++ b/tests/test_generateschema.py @@ -1,11 +1,10 @@ -from __future__ import unicode_literals +import io 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 @@ -28,9 +27,8 @@ class GenerateSchemaTests(TestCase): """Tests for management command generateschema.""" def setUp(self): - self.out = six.StringIO() + self.out = io.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 diff --git a/tests/test_generics.py b/tests/test_generics.py index c0ff1c5c4e..f41ebe6da7 100644 --- a/tests/test_generics.py +++ b/tests/test_generics.py @@ -1,11 +1,8 @@ -from __future__ import unicode_literals - import pytest from django.db import models from django.http import Http404 from django.shortcuts import get_object_or_404 from django.test import TestCase -from django.utils import six from rest_framework import generics, renderers, serializers, status from rest_framework.response import Response @@ -245,7 +242,7 @@ def test_delete_instance_view(self): with self.assertNumQueries(2): response = self.view(request, pk=1).render() assert response.status_code == status.HTTP_204_NO_CONTENT - assert response.content == six.b('') + assert response.content == b'' ids = [obj.id for obj in self.objects.all()] assert ids == [2, 3] @@ -291,7 +288,7 @@ def test_put_to_filtered_out_instance(self): """ data = {'text': 'foo'} filtered_out_pk = BasicModel.objects.filter(text='filtered out')[0].pk - request = factory.put('/{0}'.format(filtered_out_pk), data, format='json') + request = factory.put('/{}'.format(filtered_out_pk), data, format='json') response = self.view(request, pk=filtered_out_pk).render() assert response.status_code == status.HTTP_404_NOT_FOUND @@ -446,12 +443,12 @@ def test_m2m_in_browsable_api(self): assert response.status_code == status.HTTP_200_OK -class InclusiveFilterBackend(object): +class InclusiveFilterBackend: def filter_queryset(self, request, queryset, view): return queryset.filter(text='foo') -class ExclusiveFilterBackend(object): +class ExclusiveFilterBackend: def filter_queryset(self, request, queryset, view): return queryset.filter(text='other') @@ -653,7 +650,7 @@ def destroy(self, request, *args, **kwargs): class GetObjectOr404Tests(TestCase): def setUp(self): - super(GetObjectOr404Tests, self).setUp() + super().setUp() self.uuid_object = UUIDForeignKeyTarget.objects.create(name='bar') def test_get_object_or_404_with_valid_uuid(self): diff --git a/tests/test_htmlrenderer.py b/tests/test_htmlrenderer.py index decd25a3fe..e31a9ced52 100644 --- a/tests/test_htmlrenderer.py +++ b/tests/test_htmlrenderer.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import django.template.loader import pytest from django.conf.urls import url @@ -7,7 +5,6 @@ from django.http import Http404 from django.template import TemplateDoesNotExist, engines from django.test import TestCase, override_settings -from django.utils import six from rest_framework import status from rest_framework.decorators import api_view, renderer_classes @@ -47,7 +44,7 @@ def not_found(request): @override_settings(ROOT_URLCONF='tests.test_htmlrenderer') class TemplateHTMLRendererTests(TestCase): def setUp(self): - class MockResponse(object): + class MockResponse: template_name = None self.mock_response = MockResponse() self._monkey_patch_get_template() @@ -85,13 +82,13 @@ def test_simple_html_view(self): def test_not_found_html_view(self): response = self.client.get('/not_found') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - self.assertEqual(response.content, six.b("404 Not Found")) + self.assertEqual(response.content, b"404 Not Found") self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') def test_permission_denied_html_view(self): response = self.client.get('/permission_denied') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertEqual(response.content, six.b("403 Forbidden")) + self.assertEqual(response.content, b"403 Forbidden") self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') # 2 tests below are based on order of if statements in corresponding method @@ -105,14 +102,14 @@ def test_get_template_names_returns_own_template_name(self): def test_get_template_names_returns_view_template_name(self): renderer = TemplateHTMLRenderer() - class MockResponse(object): + class MockResponse: template_name = None - class MockView(object): + class MockView: def get_template_names(self): return ['template from get_template_names method'] - class MockView2(object): + class MockView2: template_name = 'template from template_name attribute' template_name = renderer.get_template_names(self.mock_response, @@ -156,12 +153,11 @@ def test_not_found_html_view_with_template(self): response = self.client.get('/not_found') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) self.assertTrue(response.content in ( - six.b("404: Not found"), six.b("404 Not Found"))) + b"404: Not found", b"404 Not Found")) self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') def test_permission_denied_html_view_with_template(self): response = self.client.get('/permission_denied') self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - self.assertTrue(response.content in ( - six.b("403: Permission denied"), six.b("403 Forbidden"))) + self.assertTrue(response.content in (b"403: Permission denied", b"403 Forbidden")) self.assertEqual(response['Content-Type'], 'text/html; charset=utf-8') diff --git a/tests/test_metadata.py b/tests/test_metadata.py index fe4ea4b428..e1a1fd3528 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import pytest from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 9df7d8e3e6..28a5e558a1 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -22,7 +22,7 @@ def post(self, request): ] -class RequestUserMiddleware(object): +class RequestUserMiddleware: def __init__(self, get_response): self.get_response = get_response @@ -34,7 +34,7 @@ def __call__(self, request): return response -class RequestPOSTMiddleware(object): +class RequestPOSTMiddleware: def __init__(self, get_response): self.get_response = get_response diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 898c859a4f..413d7885d3 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -5,8 +5,6 @@ These tests deal with ensuring that we correctly map the model fields onto an appropriate set of serializer fields for each case. """ -from __future__ import unicode_literals - import datetime import decimal import sys @@ -20,10 +18,9 @@ ) from django.db import models from django.test import TestCase -from django.utils import six from rest_framework import serializers -from rest_framework.compat import postgres_fields, unicode_repr +from rest_framework.compat import postgres_fields from .models import NestedForeignKeySource @@ -193,7 +190,7 @@ class Meta: file_path_field = FilePathField(path='/tmp/') """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_field_options(self): class TestSerializer(serializers.ModelSerializer): @@ -212,14 +209,7 @@ class Meta: descriptive_field = IntegerField(help_text='Some help text', label='A label') choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green'))) """) - if six.PY2: - # This particular case is too awkward to resolve fully across - # both py2 and py3. - expected = expected.replace( - "('red', 'Red'), ('blue', 'Blue'), ('green', 'Green')", - "(u'red', u'Red'), (u'blue', u'Blue'), (u'green', u'Green')" - ) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) # merge this into test_regular_fields / RegularFieldsModel when # Django 2.1 is the minimum supported version @@ -238,7 +228,7 @@ class Meta: field = BooleanField(allow_null=True, required=False) """) - self.assertEqual(unicode_repr(NullableBooleanSerializer()), expected) + self.assertEqual(repr(NullableBooleanSerializer()), expected) def test_method_field(self): """ @@ -382,7 +372,7 @@ class Meta: id = IntegerField(label='ID', read_only=True) duration_field = DurationField() """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_duration_field_with_validators(self): class ValidatedDurationFieldModel(models.Model): @@ -407,7 +397,7 @@ class Meta: id = IntegerField(label='ID', read_only=True) duration_field = DurationField(max_value=datetime.timedelta(days=3), min_value=datetime.timedelta(days=1)) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) class TestGenericIPAddressFieldValidation(TestCase): @@ -424,7 +414,7 @@ class Meta: self.assertFalse(s.is_valid()) self.assertEqual(1, len(s.errors['address']), 'Unexpected number of validation errors: ' - '{0}'.format(s.errors)) + '{}'.format(s.errors)) @pytest.mark.skipif('not postgres_fields') @@ -442,7 +432,7 @@ class Meta: TestSerializer(): hstore_field = HStoreField() """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_array_field(self): class ArrayFieldModel(models.Model): @@ -457,7 +447,7 @@ class Meta: TestSerializer(): array_field = ListField(child=CharField(label='Array field', validators=[])) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_json_field(self): class JSONFieldModel(models.Model): @@ -472,7 +462,7 @@ class Meta: TestSerializer(): json_field = JSONField(style={'base_template': 'textarea.html'}) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) # Tests for relational field mappings. @@ -530,7 +520,7 @@ class Meta: many_to_many = PrimaryKeyRelatedField(allow_empty=False, many=True, queryset=ManyToManyTargetModel.objects.all()) through = PrimaryKeyRelatedField(many=True, read_only=True) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_nested_relations(self): class TestSerializer(serializers.ModelSerializer): @@ -555,7 +545,7 @@ class Meta: id = IntegerField(label='ID', read_only=True) name = CharField(max_length=100) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_hyperlinked_relations(self): class TestSerializer(serializers.HyperlinkedModelSerializer): @@ -571,7 +561,7 @@ class Meta: many_to_many = HyperlinkedRelatedField(allow_empty=False, many=True, queryset=ManyToManyTargetModel.objects.all(), view_name='manytomanytargetmodel-detail') through = HyperlinkedRelatedField(many=True, read_only=True, view_name='throughtargetmodel-detail') """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_nested_hyperlinked_relations(self): class TestSerializer(serializers.HyperlinkedModelSerializer): @@ -596,7 +586,7 @@ class Meta: url = HyperlinkedIdentityField(view_name='throughtargetmodel-detail') name = CharField(max_length=100) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_nested_hyperlinked_relations_starred_source(self): class TestSerializer(serializers.HyperlinkedModelSerializer): @@ -627,7 +617,7 @@ class Meta: name = CharField(max_length=100) """) self.maxDiff = None - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_nested_unique_together_relations(self): class TestSerializer(serializers.HyperlinkedModelSerializer): @@ -646,14 +636,7 @@ class Meta: url = HyperlinkedIdentityField(view_name='onetoonetargetmodel-detail') name = CharField(max_length=100) """) - if six.PY2: - # This case is also too awkward to resolve fully across both py2 - # and py3. (See above) - expected = expected.replace( - "('foreign_key', 'one_to_one')", - "(u'foreign_key', u'one_to_one')" - ) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_pk_reverse_foreign_key(self): class TestSerializer(serializers.ModelSerializer): @@ -667,7 +650,7 @@ class Meta: name = CharField(max_length=100) reverse_foreign_key = PrimaryKeyRelatedField(many=True, queryset=RelationalModel.objects.all()) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_pk_reverse_one_to_one(self): class TestSerializer(serializers.ModelSerializer): @@ -681,7 +664,7 @@ class Meta: name = CharField(max_length=100) reverse_one_to_one = PrimaryKeyRelatedField(queryset=RelationalModel.objects.all()) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_pk_reverse_many_to_many(self): class TestSerializer(serializers.ModelSerializer): @@ -695,7 +678,7 @@ class Meta: name = CharField(max_length=100) reverse_many_to_many = PrimaryKeyRelatedField(many=True, queryset=RelationalModel.objects.all()) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) def test_pk_reverse_through(self): class TestSerializer(serializers.ModelSerializer): @@ -709,7 +692,7 @@ class Meta: name = CharField(max_length=100) reverse_through = PrimaryKeyRelatedField(many=True, read_only=True) """) - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) class DisplayValueTargetModel(models.Model): @@ -1078,9 +1061,9 @@ class Meta(TestSerializer.Meta): char_field = CharField(max_length=100) non_model_field = CharField() """) - self.assertEqual(unicode_repr(ChildSerializer()), child_expected) - self.assertEqual(unicode_repr(TestSerializer()), test_expected) - self.assertEqual(unicode_repr(ChildSerializer()), child_expected) + self.assertEqual(repr(ChildSerializer()), child_expected) + self.assertEqual(repr(TestSerializer()), test_expected) + self.assertEqual(repr(ChildSerializer()), child_expected) class OneToOneTargetTestModel(models.Model): @@ -1149,14 +1132,14 @@ class Meta: title = CharField(max_length=64) children = PrimaryKeyRelatedField(many=True, queryset=TestChildModel.objects.all()) """) - self.assertEqual(unicode_repr(TestParentModelSerializer()), parent_expected) + self.assertEqual(repr(TestParentModelSerializer()), parent_expected) child_expected = dedent(""" TestChildModelSerializer(): value = CharField(max_length=64, validators=[]) parent = PrimaryKeyRelatedField(queryset=TestParentModel.objects.all()) """) - self.assertEqual(unicode_repr(TestChildModelSerializer()), child_expected) + self.assertEqual(repr(TestChildModelSerializer()), child_expected) def test_nonID_PK_foreignkey_model_serializer(self): @@ -1248,7 +1231,7 @@ class Meta: number_field = IntegerField(source='integer_field') """) self.maxDiff = None - self.assertEqual(unicode_repr(TestSerializer()), expected) + self.assertEqual(repr(TestSerializer()), expected) class Issue6110TestModel(models.Model): diff --git a/tests/test_multitable_inheritance.py b/tests/test_multitable_inheritance.py index 2ddd37ebba..1e8ab34485 100644 --- a/tests/test_multitable_inheritance.py +++ b/tests/test_multitable_inheritance.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import models from django.test import TestCase diff --git a/tests/test_negotiation.py b/tests/test_negotiation.py index 7ce3f92a9b..089a86c624 100644 --- a/tests/test_negotiation.py +++ b/tests/test_negotiation.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import pytest from django.http import Http404 from django.test import TestCase @@ -80,7 +78,7 @@ def test_mediatype_string_representation(self): assert str(mediatype) == 'test/*; foo=bar' def test_raise_error_if_no_suitable_renderers_found(self): - class MockRenderer(object): + class MockRenderer: format = 'xml' renderers = [MockRenderer()] with pytest.raises(Http404): diff --git a/tests/test_one_to_one_with_inheritance.py b/tests/test_one_to_one_with_inheritance.py index 789c7fcb97..40793d7ca3 100644 --- a/tests/test_one_to_one_with_inheritance.py +++ b/tests/test_one_to_one_with_inheritance.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.db import models from django.test import TestCase diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 6d940fe2b0..3c581ddfbd 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -1,11 +1,7 @@ -# coding: utf-8 -from __future__ import unicode_literals - import pytest from django.core.paginator import Paginator as DjangoPaginator from django.db import models from django.test import TestCase -from django.utils import six from rest_framework import ( exceptions, filters, generics, pagination, serializers, status @@ -208,7 +204,7 @@ def test_no_page_number(self): ] } assert self.pagination.display_page_controls - assert isinstance(self.pagination.to_html(), six.text_type) + assert isinstance(self.pagination.to_html(), str) def test_second_page(self): request = Request(factory.get('/', {'page': 2})) @@ -314,7 +310,7 @@ def test_no_page_number(self): ] } assert not self.pagination.display_page_controls - assert isinstance(self.pagination.to_html(), six.text_type) + assert isinstance(self.pagination.to_html(), str) def test_invalid_page(self): request = Request(factory.get('/', {'page': 'invalid'})) @@ -369,7 +365,7 @@ def test_no_offset(self): ] } assert self.pagination.display_page_controls - assert isinstance(self.pagination.to_html(), six.text_type) + assert isinstance(self.pagination.to_html(), str) def test_pagination_not_applied_if_limit_or_default_limit_not_set(self): class MockPagination(pagination.LimitOffsetPagination): @@ -503,7 +499,7 @@ def test_invalid_limit(self): content = self.get_paginated_content(queryset) next_limit = self.pagination.default_limit next_offset = self.pagination.default_limit - next_url = 'http://testserver/?limit={0}&offset={1}'.format(next_limit, next_offset) + next_url = 'http://testserver/?limit={}&offset={}'.format(next_limit, next_offset) assert queryset == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] assert content.get('next') == next_url @@ -516,7 +512,7 @@ def test_zero_limit(self): content = self.get_paginated_content(queryset) next_limit = self.pagination.default_limit next_offset = self.pagination.default_limit - next_url = 'http://testserver/?limit={0}&offset={1}'.format(next_limit, next_offset) + next_url = 'http://testserver/?limit={}&offset={}'.format(next_limit, next_offset) assert queryset == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] assert content.get('next') == next_url @@ -532,9 +528,9 @@ def test_max_limit(self): max_limit = self.pagination.max_limit next_offset = offset + max_limit prev_offset = offset - max_limit - base_url = 'http://testserver/?limit={0}'.format(max_limit) - next_url = base_url + '&offset={0}'.format(next_offset) - prev_url = base_url + '&offset={0}'.format(prev_offset) + base_url = 'http://testserver/?limit={}'.format(max_limit) + next_url = base_url + '&offset={}'.format(next_offset) + prev_url = base_url + '&offset={}'.format(prev_offset) assert queryset == list(range(51, 66)) assert content.get('next') == next_url assert content.get('previous') == prev_url @@ -632,7 +628,7 @@ def test_cursor_pagination(self): assert current == [1, 1, 1, 1, 1] assert next == [1, 2, 3, 4, 4] - assert isinstance(self.pagination.to_html(), six.text_type) + assert isinstance(self.pagination.to_html(), str) def test_cursor_pagination_with_page_size(self): (previous, current, next, previous_url, next_url) = self.get_pages('/?page_size=20') @@ -799,11 +795,11 @@ class TestCursorPagination(CursorPaginationTestsMixin): """ def setup(self): - class MockObject(object): + class MockObject: def __init__(self, idx): self.created = idx - class MockQuerySet(object): + class MockQuerySet: def __init__(self, items): self.items = items diff --git a/tests/test_parsers.py b/tests/test_parsers.py index e793948e37..7cf0c938a9 100644 --- a/tests/test_parsers.py +++ b/tests/test_parsers.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import io import math @@ -11,7 +8,6 @@ ) from django.http.request import RawPostDataException from django.test import TestCase -from django.utils.six import StringIO from rest_framework.exceptions import ParseError from rest_framework.parsers import ( @@ -34,7 +30,7 @@ def test_parse(self): """ Make sure the `QueryDict` works OK """ parser = FormParser() - stream = StringIO(self.string) + stream = io.StringIO(self.string) data = parser.parse(stream) assert Form(data).is_valid() is True @@ -42,7 +38,7 @@ def test_parse(self): class TestFileUploadParser(TestCase): def setUp(self): - class MockRequest(object): + class MockRequest: pass self.stream = io.BytesIO( "Test text file".encode('utf-8') diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 2fabdfa05c..9c93006949 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -1,8 +1,7 @@ -from __future__ import unicode_literals - import base64 import unittest import warnings +from unittest import mock import django import pytest @@ -15,7 +14,7 @@ HTTP_HEADER_ENCODING, RemovedInDRF310Warning, authentication, generics, permissions, serializers, status, views ) -from rest_framework.compat import PY36, is_guardian_installed, mock +from rest_framework.compat import PY36, is_guardian_installed from rest_framework.filters import DjangoObjectPermissionsFilter from rest_framework.routers import DefaultRouter from rest_framework.test import APIRequestFactory @@ -331,14 +330,14 @@ def setUp(self): everyone = Group.objects.create(name='everyone') model_name = BasicPermModel._meta.model_name app_label = BasicPermModel._meta.app_label - f = '{0}_{1}'.format + f = '{}_{}'.format perms = { 'view': f('view', model_name), 'change': f('change', model_name), 'delete': f('delete', model_name) } for perm in perms.values(): - perm = '{0}.{1}'.format(app_label, perm) + perm = '{}.{}'.format(app_label, perm) assign_perm(perm, everyone) everyone.user_set.add(*users.values()) diff --git a/tests/test_relations.py b/tests/test_relations.py index 3c4b7d90b9..3281b7ea22 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -26,7 +26,7 @@ def test_string_related_representation(self): assert representation == '' -class MockApiSettings(object): +class MockApiSettings: def __init__(self, cutoff, cutoff_text): self.HTML_SELECT_CUTOFF = cutoff self.HTML_SELECT_CUTOFF_TEXT = cutoff_text diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index 887a6f423a..5ad0e31ff8 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from django.test import TestCase, override_settings diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 2cffb62e6b..0da9da890a 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -1,7 +1,4 @@ -from __future__ import unicode_literals - from django.test import TestCase -from django.utils import six from rest_framework import serializers from tests.models import ( @@ -263,7 +260,7 @@ def test_foreign_key_update_incorrect_type(self): instance = ForeignKeySource.objects.get(pk=1) serializer = ForeignKeySourceSerializer(instance, data=data) assert not serializer.is_valid() - assert serializer.errors == {'target': ['Incorrect type. Expected pk value, received %s.' % six.text_type.__name__]} + assert serializer.errors == {'target': ['Incorrect type. Expected pk value, received str.']} def test_reverse_foreign_key_update(self): data = {'id': 2, 'name': 'target-2', 'sources': [1, 3]} @@ -562,7 +559,7 @@ def test_one_to_one_when_primary_key_no_duplicates(self): # When: Trying to create a second object second_source = OneToOnePKSourceSerializer(data=data) self.assertFalse(second_source.is_valid()) - expected = {'target': [u'one to one pk source with this target already exists.']} + expected = {'target': ['one to one pk source with this target already exists.']} self.assertDictEqual(second_source.errors, expected) def test_one_to_one_when_primary_key_does_not_exist(self): diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 60a0c0307d..54d1cb231e 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - import re from collections import OrderedDict @@ -11,7 +8,6 @@ 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 _ @@ -175,7 +171,7 @@ def test_head_method_serializes_no_content(self): resp = self.client.head('/') self.assertEqual(resp.status_code, DUMMYSTATUS) self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8') - self.assertEqual(resp.content, six.b('')) + self.assertEqual(resp.content, b'') def test_default_renderer_serializes_content_on_accept_any(self): """If the Accept header is set to */* the default renderer should serialize the response.""" @@ -348,7 +344,7 @@ def keys(self): self.assertEqual(data, {'key': 'string value', '2': 3}) def test_render_obj_with_getitem(self): - class DictLike(object): + class DictLike: def __init__(self): self._dict = {} @@ -647,7 +643,7 @@ def test_get_description_returns_empty_string_for_401_and_403_statuses(self): assert self.renderer.get_description({}, status_code=403) == '' def test_get_filter_form_returns_none_if_data_is_not_list_instance(self): - class DummyView(object): + class DummyView: get_queryset = None filter_backends = None diff --git a/tests/test_request.py b/tests/test_request.py index 83d295a128..0f682deb01 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -1,8 +1,6 @@ """ Tests for content parsing, and form-overloaded content parsing. """ -from __future__ import unicode_literals - import os.path import tempfile @@ -15,7 +13,6 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.http.request import RawPostDataException from django.test import TestCase, override_settings -from django.utils import six from rest_framework import status from rest_framework.authentication import SessionAuthentication @@ -82,7 +79,7 @@ def test_request_DATA_with_text_content(self): Ensure request.data returns content for POST request with non-form content. """ - content = six.b('qwerty') + content = b'qwerty' content_type = 'text/plain' request = Request(factory.post('/', content, content_type=content_type)) request.parsers = (PlainTextParser(),) @@ -121,7 +118,7 @@ def test_standard_behaviour_determines_non_form_content_PUT(self): Ensure request.data returns content for PUT request with non-form content. """ - content = six.b('qwerty') + content = b'qwerty' content_type = 'text/plain' request = Request(factory.put('/', content, content_type=content_type)) request.parsers = (PlainTextParser(), ) @@ -235,7 +232,7 @@ def test_calling_user_fails_when_attribute_error_is_raised(self): This proves that when an AttributeError is raised inside of the request.user property, that we can handle this and report the true, underlying error. """ - class AuthRaisesAttributeError(object): + class AuthRaisesAttributeError: def authenticate(self, request): self.MISSPELLED_NAME_THAT_DOESNT_EXIST @@ -249,10 +246,6 @@ def authenticate(self, request): with pytest.raises(WrappedAttributeError, match=expected): request.user - # python 2 hasattr fails for *any* exception, not just AttributeError - if six.PY2: - return - with pytest.raises(WrappedAttributeError, match=expected): hasattr(request, 'user') diff --git a/tests/test_requests_client.py b/tests/test_requests_client.py index 161429f73e..59b388c5a6 100644 --- a/tests/test_requests_client.py +++ b/tests/test_requests_client.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import unittest from django.conf.urls import url diff --git a/tests/test_response.py b/tests/test_response.py index e92bf54c16..d3a56d01b8 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,8 +1,5 @@ -from __future__ import unicode_literals - from django.conf.urls import include, url from django.test import TestCase, override_settings -from django.utils import six from rest_framework import generics, routers, serializers, status, viewsets from rest_framework.parsers import JSONParser @@ -150,7 +147,7 @@ def test_head_method_serializes_no_content(self): resp = self.client.head('/') self.assertEqual(resp.status_code, DUMMYSTATUS) self.assertEqual(resp['Content-Type'], RendererA.media_type + '; charset=utf-8') - self.assertEqual(resp.content, six.b('')) + self.assertEqual(resp.content, b'') def test_default_renderer_serializes_content_on_accept_any(self): """If the Accept header is set to */* the default renderer should serialize the response.""" @@ -260,7 +257,7 @@ def test_does_not_append_charset_by_default(self): """ headers = {"HTTP_ACCEPT": RendererA.media_type} resp = self.client.get('/', **headers) - expected = "{0}; charset={1}".format(RendererA.media_type, 'utf-8') + expected = "{}; charset={}".format(RendererA.media_type, 'utf-8') self.assertEqual(expected, resp['Content-Type']) def test_if_there_is_charset_specified_on_renderer_it_gets_appended(self): @@ -270,7 +267,7 @@ def test_if_there_is_charset_specified_on_renderer_it_gets_appended(self): """ headers = {"HTTP_ACCEPT": RendererC.media_type} resp = self.client.get('/', **headers) - expected = "{0}; charset={1}".format(RendererC.media_type, RendererC.charset) + expected = "{}; charset={}".format(RendererC.media_type, RendererC.charset) self.assertEqual(expected, resp['Content-Type']) def test_content_type_set_explicitly_on_response(self): diff --git a/tests/test_reverse.py b/tests/test_reverse.py index 145b1a54f3..9ab1667c52 100644 --- a/tests/test_reverse.py +++ b/tests/test_reverse.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.conf.urls import url from django.test import TestCase, override_settings from django.urls import NoReverseMatch @@ -19,7 +17,7 @@ def null_view(request): ] -class MockVersioningScheme(object): +class MockVersioningScheme: def __init__(self, raise_error=False): self.raise_error = raise_error diff --git a/tests/test_routers.py b/tests/test_routers.py index cca2ea7122..adcec8bd62 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import warnings from collections import namedtuple diff --git a/tests/test_schemas.py b/tests/test_schemas.py index 3cb9e0cda8..1aad5d1de0 100644 --- a/tests/test_schemas.py +++ b/tests/test_schemas.py @@ -29,7 +29,7 @@ factory = APIRequestFactory() -class MockUser(object): +class MockUser: def is_authenticated(self): return True @@ -112,7 +112,7 @@ def excluded_action(self, request): def get_serializer(self, *args, **kwargs): assert self.request assert self.action - return super(ExampleViewSet, self).get_serializer(*args, **kwargs) + return super().get_serializer(*args, **kwargs) @action(methods=['get', 'post'], detail=False) def documented_custom_action(self, request): @@ -1303,7 +1303,7 @@ def custom_action(self, request, pk): @pytest.mark.skipif(not coreapi, reason='coreapi is not installed') -class TestAutoSchemaAllowsFilters(object): +class TestAutoSchemaAllowsFilters: class MockAPIView(APIView): filter_backends = [filters.OrderingFilter] diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 0f1e81965a..8f4d9bf63a 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1,16 +1,13 @@ -# coding: utf-8 -from __future__ import unicode_literals - import inspect import pickle import re -import unittest +from collections import ChainMap import pytest from django.db import models from rest_framework import exceptions, fields, relations, serializers -from rest_framework.compat import Mapping, unicode_repr +from rest_framework.compat import Mapping from rest_framework.fields import Field from .models import ( @@ -18,15 +15,9 @@ ) from .utils import MockObject -try: - from collections import ChainMap -except ImportError: - ChainMap = False - # Test serializer fields imports. # ------------------------------- - class TestFieldImports: def is_field(self, name, value): return ( @@ -130,7 +121,6 @@ def test_validate_none_data(self): assert not serializer.is_valid() assert serializer.errors == {'non_field_errors': ['No data provided']} - @unittest.skipUnless(ChainMap, 'requires python 3.3') def test_serialize_chainmap(self): data = ChainMap({'char': 'abc'}, {'integer': 123}) serializer = self.Serializer(data=data) @@ -160,7 +150,7 @@ def test_custom_to_internal_value(self): to_internal_value() is expected to return a dict, but subclasses may return application specific type. """ - class Point(object): + class Point: def __init__(self, srid, x, y): self.srid = srid self.coords = (x, y) @@ -171,7 +161,7 @@ class NestedPointSerializer(serializers.Serializer): latitude = serializers.FloatField(source='y') def to_internal_value(self, data): - kwargs = super(NestedPointSerializer, self).to_internal_value(data) + kwargs = super().to_internal_value(data) return Point(srid=4326, **kwargs) serializer = NestedPointSerializer(data={'longitude': 6.958307, 'latitude': 50.941357}) @@ -201,7 +191,7 @@ class ExampleSerializer(serializers.Serializer): def raise_exception(value): raise exceptions.ValidationError('Raised error') - for validators in ([raise_exception], (raise_exception,), set([raise_exception])): + for validators in ([raise_exception], (raise_exception,), {raise_exception}): class ExampleSerializer(serializers.Serializer): char = serializers.CharField(validators=validators) integer = serializers.IntegerField() @@ -397,7 +387,7 @@ def __init__(self): class TestUnicodeRepr: - def test_unicode_repr(self): + def test_repr(self): class ExampleSerializer(serializers.Serializer): example = serializers.CharField() @@ -406,7 +396,7 @@ def __init__(self): self.example = '한국' def __repr__(self): - return unicode_repr(self.example) + return repr(self.example) instance = ExampleObject() serializer = ExampleSerializer(instance) @@ -609,7 +599,7 @@ class Test2555Regression: def test_serializer_context(self): class NestedSerializer(serializers.Serializer): def __init__(self, *args, **kwargs): - super(NestedSerializer, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) # .context should not cache self.context diff --git a/tests/test_serializer_bulk_update.py b/tests/test_serializer_bulk_update.py index d9e5d79782..0465578bb6 100644 --- a/tests/test_serializer_bulk_update.py +++ b/tests/test_serializer_bulk_update.py @@ -1,10 +1,7 @@ """ Tests to cover bulk create and update using serializers. """ -from __future__ import unicode_literals - from django.test import TestCase -from django.utils import six from rest_framework import serializers @@ -87,8 +84,7 @@ def test_invalid_list_datatype(self): serializer = self.BookSerializer(data=data, many=True) assert serializer.is_valid() is False - text_type_string = six.text_type.__name__ - message = 'Invalid data. Expected a dictionary, but got %s.' % text_type_string + message = 'Invalid data. Expected a dictionary, but got str.' expected_errors = [ {'non_field_errors': [message]}, {'non_field_errors': [message]}, diff --git a/tests/test_settings.py b/tests/test_settings.py index 51e9751b25..b78125ff95 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import TestCase, override_settings from rest_framework.settings import APISettings, api_settings diff --git a/tests/test_status.py b/tests/test_status.py index 1cd6e229e9..07d893bee9 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from django.test import TestCase from rest_framework.status import ( diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index 45bfd4aeb7..128160888a 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -1,6 +1,3 @@ -# encoding: utf-8 -from __future__ import unicode_literals - import unittest from django.template import Context, Template @@ -225,7 +222,7 @@ def test_as_string_with_none(self): assert result == '' def test_get_pagination_html(self): - class MockPager(object): + class MockPager: def __init__(self): self.called = False @@ -340,7 +337,7 @@ def test_schema_with_empty_links(self): ) section = schema['users'] flat_links = schema_links(section) - assert len(flat_links) is 0 + assert len(flat_links) == 0 def test_single_action(self): schema = coreapi.Document( @@ -358,7 +355,7 @@ def test_single_action(self): ) section = schema['users'] flat_links = schema_links(section) - assert len(flat_links) is 1 + assert len(flat_links) == 1 assert 'list' in flat_links def test_default_actions(self): @@ -396,7 +393,7 @@ def test_default_actions(self): ) section = schema['users'] flat_links = schema_links(section) - assert len(flat_links) is 4 + assert len(flat_links) == 4 assert 'list' in flat_links assert 'create' in flat_links assert 'read' in flat_links @@ -444,7 +441,7 @@ def test_default_actions_and_single_custom_action(self): ) section = schema['users'] flat_links = schema_links(section) - assert len(flat_links) is 5 + assert len(flat_links) == 5 assert 'list' in flat_links assert 'create' in flat_links assert 'read' in flat_links @@ -502,7 +499,7 @@ def test_default_actions_and_single_custom_action_two_methods(self): ) section = schema['users'] flat_links = schema_links(section) - assert len(flat_links) is 6 + assert len(flat_links) == 6 assert 'list' in flat_links assert 'create' in flat_links assert 'read' in flat_links @@ -553,7 +550,7 @@ def test_multiple_nested_routes(self): ) section = schema['animals'] flat_links = schema_links(section) - assert len(flat_links) is 4 + assert len(flat_links) == 4 assert 'cat > create' in flat_links assert 'cat > list' in flat_links assert 'dog > read' in flat_links @@ -622,7 +619,7 @@ def test_multiple_resources_with_multiple_nested_routes(self): ) section = schema['animals'] flat_links = schema_links(section) - assert len(flat_links) is 4 + assert len(flat_links) == 4 assert 'cat > create' in flat_links assert 'cat > list' in flat_links assert 'dog > read' in flat_links @@ -630,6 +627,6 @@ def test_multiple_resources_with_multiple_nested_routes(self): section = schema['farmers'] flat_links = schema_links(section) - assert len(flat_links) is 2 + assert len(flat_links) == 2 assert 'silo > list' in flat_links assert 'silo > soy > list' in flat_links diff --git a/tests/test_testing.py b/tests/test_testing.py index 7868f724c1..8094bfd8d2 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,6 +1,3 @@ -# encoding: utf-8 -from __future__ import unicode_literals - from io import BytesIO from django.conf.urls import url @@ -293,13 +290,13 @@ class TestUrlPatternTestCase(URLPatternsTestCase): @classmethod def setUpClass(cls): assert urlpatterns is not cls.urlpatterns - super(TestUrlPatternTestCase, cls).setUpClass() + super().setUpClass() assert urlpatterns is cls.urlpatterns @classmethod def tearDownClass(cls): assert urlpatterns is cls.urlpatterns - super(TestUrlPatternTestCase, cls).tearDownClass() + super().tearDownClass() assert urlpatterns is not cls.urlpatterns def test_urlpatterns(self): diff --git a/tests/test_throttling.py b/tests/test_throttling.py index b220a33a6f..b20b6a809c 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -1,7 +1,6 @@ """ Tests for the throttling implementations in the permissions module. """ -from __future__ import unicode_literals import pytest from django.contrib.auth.models import User @@ -296,7 +295,7 @@ def test_unscoped_view_not_throttled(self): assert response.status_code == 200 def test_get_cache_key_returns_correct_key_if_user_is_authenticated(self): - class DummyView(object): + class DummyView: throttle_scope = 'user' request = Request(HttpRequest()) diff --git a/tests/test_urlpatterns.py b/tests/test_urlpatterns.py index 59ba395d29..25cc0032ee 100644 --- a/tests/test_urlpatterns.py +++ b/tests/test_urlpatterns.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - import unittest from collections import namedtuple diff --git a/tests/test_utils.py b/tests/test_utils.py index 28b06b1735..a6f8b9d160 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from django.conf.urls import url from django.test import TestCase, override_settings diff --git a/tests/test_validation.py b/tests/test_validation.py index 4132a7b00f..6e00b48c2e 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -1,11 +1,8 @@ -from __future__ import unicode_literals - import re from django.core.validators import MaxValueValidator, RegexValidator from django.db import models from django.test import TestCase -from django.utils import six from rest_framework import generics, serializers, status from rest_framework.test import APIRequestFactory @@ -112,7 +109,7 @@ def test_serializer_errors_has_only_invalid_data_error(self): assert not serializer.is_valid() assert serializer.errors == { 'non_field_errors': [ - 'Invalid data. Expected a dictionary, but got %s.' % six.text_type.__name__ + 'Invalid data. Expected a dictionary, but got str.', ] } @@ -151,14 +148,14 @@ def test_max_value_validation_serializer_fails(self): def test_max_value_validation_success(self): obj = ValidationMaxValueValidatorModel.objects.create(number_value=100) - request = factory.patch('/{0}'.format(obj.pk), {'number_value': 98}, format='json') + request = factory.patch('/{}'.format(obj.pk), {'number_value': 98}, format='json') view = UpdateMaxValueValidationModel().as_view() response = view(request, pk=obj.pk).render() assert response.status_code == status.HTTP_200_OK def test_max_value_validation_fail(self): obj = ValidationMaxValueValidatorModel.objects.create(number_value=100) - request = factory.patch('/{0}'.format(obj.pk), {'number_value': 101}, format='json') + request = factory.patch('/{}'.format(obj.pk), {'number_value': 101}, format='json') view = UpdateMaxValueValidationModel().as_view() response = view(request, pk=obj.pk).render() assert response.content == b'{"number_value":["Ensure this value is less than or equal to 100."]}' diff --git a/tests/test_validators.py b/tests/test_validators.py index 4bbddb64ba..fe31ba2357 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -353,7 +353,7 @@ def test_filter_queryset_do_not_skip_existing_attribute(self): filter_queryset should add value from existing instance attribute if it is not provided in attributes dict """ - class MockQueryset(object): + class MockQueryset: def filter(self, **kwargs): self.called_with = kwargs @@ -558,19 +558,19 @@ class Meta: class ValidatorsTests(TestCase): def test_qs_exists_handles_type_error(self): - class TypeErrorQueryset(object): + class TypeErrorQueryset: def exists(self): raise TypeError assert qs_exists(TypeErrorQueryset()) is False def test_qs_exists_handles_value_error(self): - class ValueErrorQueryset(object): + class ValueErrorQueryset: def exists(self): raise ValueError assert qs_exists(ValueErrorQueryset()) is False def test_qs_exists_handles_data_error(self): - class DataErrorQueryset(object): + class DataErrorQueryset: def exists(self): raise DataError assert qs_exists(DataErrorQueryset()) is False diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 7e650e2752..d4e269df30 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -319,9 +319,9 @@ class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase): ] def setUp(self): - super(TestHyperlinkedRelatedField, self).setUp() + super().setUp() - class MockQueryset(object): + class MockQueryset: def get(self, pk): return 'object %s' % pk diff --git a/tests/test_views.py b/tests/test_views.py index f0919e8461..2648c9fb38 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -1,7 +1,4 @@ -from __future__ import unicode_literals - import copy -import sys from django.test import TestCase @@ -14,10 +11,7 @@ factory = APIRequestFactory() -if sys.version_info[:2] >= (3, 4): - JSON_ERROR = 'JSON parse error - Expecting value:' -else: - JSON_ERROR = 'JSON parse error - No JSON object could be decoded' +JSON_ERROR = 'JSON parse error - Expecting value:' class BasicView(APIView): diff --git a/tests/utils.py b/tests/utils.py index 509e6a1027..06e5b9abe6 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,7 +2,7 @@ from django.urls import NoReverseMatch -class MockObject(object): +class MockObject: def __init__(self, **kwargs): self._kwargs = kwargs for key, val in kwargs.items(): @@ -16,7 +16,7 @@ def __str__(self): return '' % kwargs_str -class MockQueryset(object): +class MockQueryset: def __init__(self, iterable): self.items = iterable @@ -33,7 +33,7 @@ def get(self, **lookup): raise ObjectDoesNotExist() -class BadType(object): +class BadType: """ When used as a lookup with a `MockQueryset`, these objects will raise a `TypeError`, as occurs in Django when making diff --git a/tox.ini b/tox.ini index 5d7a4987e3..fcd32f88af 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - {py27,py34,py35,py36}-django111, + {py34,py35,py36}-django111, {py34,py35,py36,py37}-django20, {py35,py36,py37}-django21 {py35,py36,py37}-django22 @@ -44,7 +44,7 @@ deps = -rrequirements/requirements-optionals.txt [testenv:lint] -basepython = python2.7 +basepython = python3.7 commands = ./runtests.py --lintonly deps = -rrequirements/requirements-codestyle.txt @@ -52,6 +52,7 @@ deps = [testenv:docs] basepython = python2.7 +skip_install = true commands = mkdocs build deps = -rrequirements/requirements-testing.txt From ff86f09f74a0f60b657576abc8cf805c308a3974 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 30 Apr 2019 22:44:33 -0700 Subject: [PATCH 028/907] Remove unnecessary compatibility shims from rest_framework/compat.py (#6631) For Python 3, collections.abc.Mapping and collections.abc.MutableMapping are always available from the stdlib. --- rest_framework/compat.py | 1 - rest_framework/fields.py | 3 ++- rest_framework/serializers.py | 3 ++- rest_framework/utils/serializer_helpers.py | 2 +- tests/test_renderers.py | 3 ++- tests/test_serializer.py | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index aad44e3421..3068665a82 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -3,7 +3,6 @@ versions of Django/Python, and compatibility wrappers around optional packages. """ import sys -from collections.abc import Mapping, MutableMapping # noqa from django.conf import settings from django.core import validators diff --git a/rest_framework/fields.py b/rest_framework/fields.py index ad9611e056..1cffdcc2d1 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -6,6 +6,7 @@ import re import uuid from collections import OrderedDict +from collections.abc import Mapping from django.conf import settings from django.core.exceptions import ObjectDoesNotExist @@ -30,7 +31,7 @@ from rest_framework import ISO_8601 from rest_framework.compat import ( - Mapping, MaxLengthValidator, MaxValueValidator, MinLengthValidator, + MaxLengthValidator, MaxValueValidator, MinLengthValidator, MinValueValidator, ProhibitNullCharactersValidator ) from rest_framework.exceptions import ErrorDetail, ValidationError diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 90b31e068f..651ca81cf5 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -14,6 +14,7 @@ import inspect import traceback from collections import OrderedDict +from collections.abc import Mapping from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ValidationError as DjangoValidationError @@ -25,7 +26,7 @@ from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import Mapping, postgres_fields +from rest_framework.compat import postgres_fields from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.fields import get_error_detail, set_value from rest_framework.settings import api_settings diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index 8709352f17..80aea27d35 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -1,8 +1,8 @@ from collections import OrderedDict +from collections.abc import MutableMapping from django.utils.encoding import force_text -from rest_framework.compat import MutableMapping from rest_framework.utils import json diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 54d1cb231e..bc775547d3 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -1,5 +1,6 @@ import re from collections import OrderedDict +from collections.abc import MutableMapping import pytest from django.conf.urls import include, url @@ -12,7 +13,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import permissions, serializers, status -from rest_framework.compat import MutableMapping, coreapi +from rest_framework.compat import coreapi from rest_framework.decorators import action from rest_framework.renderers import ( AdminRenderer, BaseRenderer, BrowsableAPIRenderer, DocumentationRenderer, diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 8f4d9bf63a..33cc0b60c9 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -2,12 +2,12 @@ import pickle import re from collections import ChainMap +from collections.abc import Mapping import pytest from django.db import models from rest_framework import exceptions, fields, relations, serializers -from rest_framework.compat import Mapping from rest_framework.fields import Field from .models import ( From b4e80ac721958f8cc2931b0e2b4d022946f6ad88 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 30 Apr 2019 22:45:16 -0700 Subject: [PATCH 029/907] Remove unnecessary coerce to str() in test_decorators.py (#6637) Was added only for Python 2 compatibility. --- tests/test_decorators.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 3f24e7ef03..bd30449e55 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -202,8 +202,7 @@ def test_action(): def method(): raise NotImplementedError - # Python 2.x compatibility - cast __name__ to str - method.__name__ = str(name) + method.__name__ = name getattr(test_action.mapping, name)(method) # ensure the mapping returns the correct method name From 734ca7ca8c2ba6f0ca83ede015652720b2a7246d Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 30 Apr 2019 22:46:30 -0700 Subject: [PATCH 030/907] Remove unneeded repo() test (#6632) --- tests/test_serializer.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 33cc0b60c9..e0acf368b3 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -386,23 +386,6 @@ def __init__(self): ) -class TestUnicodeRepr: - def test_repr(self): - class ExampleSerializer(serializers.Serializer): - example = serializers.CharField() - - class ExampleObject: - def __init__(self): - self.example = '한국' - - def __repr__(self): - return repr(self.example) - - instance = ExampleObject() - serializer = ExampleSerializer(instance) - repr(serializer) # Should not error. - - class TestNotRequiredOutput: def test_not_required_output_for_dict(self): """ From 513a49d63b6332e373c89fb0737a0745c1f0a734 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 30 Apr 2019 22:49:17 -0700 Subject: [PATCH 031/907] Drop default 'utf-8' to .encode()/.decode() (#6633) A Python 3 cleanup that allows for less noise in the code. https://docs.python.org/3/library/stdtypes.html#bytes.decode https://docs.python.org/3/library/stdtypes.html#str.encode --- rest_framework/fields.py | 7 ++---- .../management/commands/generateschema.py | 2 +- rest_framework/parsers.py | 2 +- rest_framework/renderers.py | 23 +++++++------------ rest_framework/utils/encoders.py | 2 +- tests/authentication/test_authentication.py | 2 +- tests/browsable_api/test_browsable_api.py | 12 +++++----- .../test_browsable_nested_api.py | 2 +- tests/test_generics.py | 6 ++--- tests/test_parsers.py | 6 ++--- tests/test_renderers.py | 22 +++++++++--------- tests/test_routers.py | 10 ++++---- tests/test_templates.py | 4 ++-- 13 files changed, 44 insertions(+), 56 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 1cffdcc2d1..a41934ac1e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1754,7 +1754,7 @@ def to_internal_value(self, data): try: if self.binary or getattr(data, 'is_json_string', False): if isinstance(data, bytes): - data = data.decode('utf-8') + data = data.decode() return json.loads(data) else: json.dumps(data) @@ -1765,10 +1765,7 @@ def to_internal_value(self, data): def to_representation(self, value): if self.binary: value = json.dumps(value) - # On python 2.x the return type for json.dumps() is underspecified. - # On python 3.x json.dumps() returns unicode strings. - if isinstance(value, str): - value = bytes(value.encode('utf-8')) + value = value.encode() return value diff --git a/rest_framework/management/commands/generateschema.py b/rest_framework/management/commands/generateschema.py index 591073ba03..40909bd045 100644 --- a/rest_framework/management/commands/generateschema.py +++ b/rest_framework/management/commands/generateschema.py @@ -29,7 +29,7 @@ def handle(self, *args, **options): renderer = self.get_renderer(options['format']) output = renderer.render(schema, renderer_context={}) - self.stdout.write(output.decode('utf-8')) + self.stdout.write(output.decode()) def get_renderer(self, format): renderer_cls = { diff --git a/rest_framework/parsers.py b/rest_framework/parsers.py index 5b5e3f1581..a48c316317 100644 --- a/rest_framework/parsers.py +++ b/rest_framework/parsers.py @@ -202,7 +202,7 @@ def get_filename(self, stream, media_type, parser_context): try: meta = parser_context['request'].META - disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode('utf-8')) + disposition = parse_header(meta['HTTP_CONTENT_DISPOSITION'].encode()) filename_parm = disposition[1] if 'filename*' in filename_parm: return self.get_encoded_filename(filename_parm) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index eb5da008b3..623702966e 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -104,18 +104,11 @@ def render(self, data, accepted_media_type=None, renderer_context=None): allow_nan=not self.strict, separators=separators ) - # On python 2.x json.dumps() returns bytestrings if ensure_ascii=True, - # but if ensure_ascii=False, the return type is underspecified, - # and may (or may not) be unicode. - # On python 3.x json.dumps() returns unicode strings. - if isinstance(ret, str): - # We always fully escape \u2028 and \u2029 to ensure we output JSON - # that is a strict javascript subset. If bytes were returned - # by json.dumps() then we don't have these characters in any case. - # See: http://timelessrepo.com/json-isnt-a-javascript-subset - ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029') - return bytes(ret.encode('utf-8')) - return ret + # We always fully escape \u2028 and \u2029 to ensure we output JSON + # that is a strict javascript subset. + # See: http://timelessrepo.com/json-isnt-a-javascript-subset + ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029') + return ret.encode() class TemplateHTMLRenderer(BaseRenderer): @@ -574,7 +567,7 @@ def get_raw_data_form(self, data, view, method, request): data.pop(name, None) content = renderer.render(data, accepted, context) # Renders returns bytes, but CharField expects a str. - content = content.decode('utf-8') + content = content.decode() else: content = None @@ -1032,7 +1025,7 @@ def __init__(self): def render(self, data, media_type=None, renderer_context=None): structure = self.get_structure(data) - return yaml.dump(structure, default_flow_style=False).encode('utf-8') + return yaml.dump(structure, default_flow_style=False).encode() class JSONOpenAPIRenderer(_BaseOpenAPIRenderer): @@ -1045,4 +1038,4 @@ def __init__(self): def render(self, data, media_type=None, renderer_context=None): structure = self.get_structure(data) - return json.dumps(structure, indent=4).encode('utf-8') + return json.dumps(structure, indent=4).encode() diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index dee2f942e8..a7875a8681 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -47,7 +47,7 @@ def default(self, obj): return tuple(obj) elif isinstance(obj, bytes): # Best-effort for binary blobs. See #4187. - return obj.decode('utf-8') + return obj.decode() elif hasattr(obj, 'tolist'): # Numpy arrays and array scalars. return obj.tolist() diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py index f7e9fcf18a..9279890284 100644 --- a/tests/authentication/test_authentication.py +++ b/tests/authentication/test_authentication.py @@ -183,7 +183,7 @@ def test_login_view_renders_on_get(self): cf. [#1810](https://github.com/encode/django-rest-framework/pull/1810) """ response = self.csrf_client.get('/auth/login/') - content = response.content.decode('utf8') + content = response.content.decode() assert '' in content def test_post_form_session_auth_failing_csrf(self): diff --git a/tests/browsable_api/test_browsable_api.py b/tests/browsable_api/test_browsable_api.py index 81090e2235..17644c2ac7 100644 --- a/tests/browsable_api/test_browsable_api.py +++ b/tests/browsable_api/test_browsable_api.py @@ -24,18 +24,18 @@ def tearDown(self): def test_name_shown_when_logged_in(self): self.client.login(username=self.username, password=self.password) response = self.client.get('/') - content = response.content.decode('utf8') + content = response.content.decode() assert 'john' in content def test_logout_shown_when_logged_in(self): self.client.login(username=self.username, password=self.password) response = self.client.get('/') - content = response.content.decode('utf8') + content = response.content.decode() assert '>Log out<' in content def test_login_shown_when_logged_out(self): response = self.client.get('/') - content = response.content.decode('utf8') + content = response.content.decode() assert '>Log in<' in content @@ -59,16 +59,16 @@ def tearDown(self): def test_name_shown_when_logged_in(self): self.client.login(username=self.username, password=self.password) response = self.client.get('/') - content = response.content.decode('utf8') + content = response.content.decode() assert 'john' in content def test_dropdown_not_shown_when_logged_in(self): self.client.login(username=self.username, password=self.password) response = self.client.get('/') - content = response.content.decode('utf8') + content = response.content.decode() assert ' {% else %}
  • - {{ nav_item.title }} + {{ nav_item.title }}
  • {% endif %} {% endfor %} From 908516f2bdec997238be912918b74ae2891ff948 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 15 Jul 2019 20:14:59 +0100 Subject: [PATCH 126/907] Fix pagination docs meta information --- docs/api-guide/pagination.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 47be37f335..8d9eb22881 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -1,4 +1,7 @@ -source: pagination.py +--- +source: + - pagination.py +--- # Pagination From a4c2d4f0d59c547c01935e8f5855ee8bca52c4c8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 16 Jul 2019 09:31:29 +0100 Subject: [PATCH 127/907] Fix side nav in project docs --- docs_theme/main.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs_theme/main.html b/docs_theme/main.html index 07570d6e4d..4a1b0f23cc 100644 --- a/docs_theme/main.html +++ b/docs_theme/main.html @@ -91,12 +91,12 @@

    Documentation search

    {% for toc_item in page.toc %}
  • - {{ toc_item.title }} + {{ toc_item.title }}
  • {% for toc_item in toc_item.children %}
  • - {{ toc_item.title }} + {{ toc_item.title }}
  • {% endfor %} {% endfor %} From 0cb252392712ea1f2489fbc1283a62112d17d0a3 Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Tue, 16 Jul 2019 13:00:45 -0700 Subject: [PATCH 128/907] Update docs search plugin (#6810) --- docs_theme/js/theme.js | 6 ------ docs_theme/main.html | 9 ++++++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/docs_theme/js/theme.js b/docs_theme/js/theme.js index ddbd9c9053..0918ae85dd 100644 --- a/docs_theme/js/theme.js +++ b/docs_theme/js/theme.js @@ -9,11 +9,6 @@ var getSearchTerm = function() { } }; -var initilizeSearch = function() { - require.config({ baseUrl: '/mkdocs/js' }); - require(['search']); -}; - $(function() { var searchTerm = getSearchTerm(), $searchModal = $('#mkdocs_search_modal'), @@ -30,6 +25,5 @@ $(function() { $searchModal.on('shown', function() { $searchQuery.focus(); - initilizeSearch(); }); }); diff --git a/docs_theme/main.html b/docs_theme/main.html index 4a1b0f23cc..21e9171a2a 100644 --- a/docs_theme/main.html +++ b/docs_theme/main.html @@ -138,14 +138,17 @@

    Documentation search

    + - - - + + {% for path in config.extra_javascript %} + + {% endfor %} + From 165da5be0c8bd775156d6d1ac1fceb7eb325cbd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20=E2=9A=A1?= <1678423+Alex-CodeLab@users.noreply.github.com> Date: Fri, 3 Jan 2020 15:42:29 +0100 Subject: [PATCH 229/907] Documentation: make codeblocks easier to read. (#6896) --- docs_theme/css/default.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs_theme/css/default.css b/docs_theme/css/default.css index bb17a3a115..e9d7f23bfb 100644 --- a/docs_theme/css/default.css +++ b/docs_theme/css/default.css @@ -74,6 +74,12 @@ pre { white-space: pre; } +code, pre { + font-family: Consolas,Menlo,Monaco,Lucida Console,Liberation Mono,DejaVu Sans Mono,Bitstream Vera Sans Mono,Courier New,monospace,sans-serif; + font-size: 13px; +} + + /* Preserve the spacing of the navbar across different screen sizes. */ .navbar-inner { /*padding: 5px 0;*/ @@ -432,3 +438,4 @@ ul.sponsor { margin: 0 !important; display: inline-block !important; } + From 373e521f3685c48eb22e6fbb1d7079f161a4a67b Mon Sep 17 00:00:00 2001 From: Sebastian Pipping Date: Mon, 6 Jan 2020 15:12:21 +0100 Subject: [PATCH 230/907] Make CharField prohibit surrogate characters (#7026) (#7067) * CharField: Detect and prohibit surrogate characters * CharField: Cover handling of surrogate characters --- rest_framework/fields.py | 2 ++ rest_framework/validators.py | 11 +++++++++++ tests/test_fields.py | 15 +++++++++++++++ 3 files changed, 28 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 3df7888a0a..958bebeef8 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -36,6 +36,7 @@ from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, json, representation from rest_framework.utils.formatting import lazy_format +from rest_framework.validators import ProhibitSurrogateCharactersValidator class empty: @@ -818,6 +819,7 @@ def __init__(self, **kwargs): # ProhibitNullCharactersValidator is None on Django < 2.0 if ProhibitNullCharactersValidator is not None: self.validators.append(ProhibitNullCharactersValidator()) + self.validators.append(ProhibitSurrogateCharactersValidator()) def run_validation(self, data=empty): # Test for the empty string here so that it does not get validated, diff --git a/rest_framework/validators.py b/rest_framework/validators.py index 4681d4fb13..a5cb75a84a 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -167,6 +167,17 @@ def __repr__(self): ) +class ProhibitSurrogateCharactersValidator: + message = _('Surrogate characters are not allowed: U+{code_point:X}.') + code = 'surrogate_characters_not_allowed' + + def __call__(self, value): + for surrogate_character in (ch for ch in str(value) + if 0xD800 <= ord(ch) <= 0xDFFF): + message = self.message.format(code_point=ord(surrogate_character)) + raise ValidationError(message, code=self.code) + + class BaseUniqueForValidator: message = None missing_message = _('This field is required.') diff --git a/tests/test_fields.py b/tests/test_fields.py index 0be1b1a7a0..a4b78fd513 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -758,6 +758,21 @@ def test_null_bytes(self): 'Null characters are not allowed.' ] + def test_surrogate_characters(self): + field = serializers.CharField() + + for code_point, expected_message in ( + (0xD800, 'Surrogate characters are not allowed: U+D800.'), + (0xDFFF, 'Surrogate characters are not allowed: U+DFFF.'), + ): + with pytest.raises(serializers.ValidationError) as exc_info: + field.run_validation(chr(code_point)) + assert exc_info.value.detail[0].code == 'surrogate_characters_not_allowed' + assert str(exc_info.value.detail[0]) == expected_message + + for code_point in (0xD800 - 1, 0xDFFF + 1): + field.run_validation(chr(code_point)) + def test_iterable_validators(self): """ Ensure `validators` parameter is compatible with reasonable iterables. From 442a20650254bd24165ddd69654b6f855df8f386 Mon Sep 17 00:00:00 2001 From: Danny Date: Sat, 11 Jan 2020 00:38:29 -0600 Subject: [PATCH 231/907] Fix full-text search docs (#7133) --- docs/api-guide/filtering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 1bdb6c52ba..bad57b4414 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -212,7 +212,7 @@ The search behavior may be restricted by prepending various characters to the `s * '^' Starts-with search. * '=' Exact matches. -* '@' Full-text search. (Currently only supported Django's MySQL backend.) +* '@' Full-text search. (Currently only supported Django's [PostgreSQL backend](https://docs.djangoproject.com/en/dev/ref/contrib/postgres/search/).) * '$' Regex search. For example: From 5f3f2ef10636e4083433289ee007e12cd61d8e8b Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Wed, 15 Jan 2020 02:52:29 -0800 Subject: [PATCH 232/907] Add note that APISettings is an internal class (#7144) --- rest_framework/settings.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rest_framework/settings.py b/rest_framework/settings.py index c4c0e79396..9eb4c5653b 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -182,14 +182,19 @@ def import_from_string(val, setting_name): class APISettings: """ - A settings object, that allows API settings to be accessed as properties. - For example: + A settings object that allows REST Framework settings to be accessed as + properties. For example: from rest_framework.settings import api_settings print(api_settings.DEFAULT_RENDERER_CLASSES) Any setting with string import paths will be automatically resolved and return the class, rather than the string literal. + + Note: + This is an internal class that is only compatible with settings namespaced + under the REST_FRAMEWORK name. It is not intended to be used by 3rd-party + apps, and test helpers like `override_settings` may not work as expected. """ def __init__(self, user_settings=None, defaults=None, import_strings=None): if user_settings: From 62193e037859498bd8c87139ed63ebfd2cf8c324 Mon Sep 17 00:00:00 2001 From: Jonathan Longe Date: Wed, 15 Jan 2020 11:58:31 -0800 Subject: [PATCH 233/907] Add permissions to quickstart tutorial (#7113) --- docs/tutorial/quickstart.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index ee54816dc4..505f7f91d5 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -85,6 +85,7 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a from django.contrib.auth.models import User, Group from rest_framework import viewsets + from rest_framework import permissions from tutorial.quickstart.serializers import UserSerializer, GroupSerializer @@ -94,6 +95,7 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a """ queryset = User.objects.all().order_by('-date_joined') serializer_class = UserSerializer + permission_classes = [permissions.IsAuthenticated] class GroupViewSet(viewsets.ModelViewSet): @@ -102,6 +104,7 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a """ queryset = Group.objects.all() serializer_class = GroupSerializer + permission_classes = [permissions.IsAuthenticated] Rather than write multiple views we're grouping together all the common behavior into classes called `ViewSets`. From 7bd730124c93f9309c6603ccfb56316d3f6934f0 Mon Sep 17 00:00:00 2001 From: David Runge Date: Wed, 15 Jan 2020 21:18:25 +0100 Subject: [PATCH 234/907] MANIFEST.in: Adding tests to sdist tarball. (#7145) --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 6f7cb8f13e..262e3dc917 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include README.md include LICENSE.md +recursive-include tests/* * recursive-include rest_framework/static *.js *.css *.png *.ico *.eot *.svg *.ttf *.woff *.woff2 recursive-include rest_framework/templates *.html schema.js recursive-include rest_framework/locale *.mo From d0b957760549710b90300b9a3a373d175238884c Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 8 Jan 2020 20:37:23 +0100 Subject: [PATCH 235/907] Return valid OpenAPI schema even when empty. --- rest_framework/schemas/openapi.py | 10 +--------- tests/schemas/test_openapi.py | 9 +++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 58788bc234..aaeb2914c1 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -35,12 +35,7 @@ def get_info(self): def get_paths(self, request=None): result = {} - paths, view_endpoints = self._get_paths_and_endpoints(request) - - # Only generate the path prefix for paths that will be included - if not paths: - return None - + _, view_endpoints = self._get_paths_and_endpoints(request) for path, method, view in view_endpoints: if not self.has_view_permissions(path, method, view): continue @@ -62,9 +57,6 @@ def get_schema(self, request=None, public=False): self._initialise_endpoints() paths = self.get_paths(None if public else request) - if not paths: - return None - schema = { 'openapi': '3.0.2', 'info': self.get_info(), diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 03eb9de7a9..83473be4b3 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -707,6 +707,15 @@ def test_schema_construction(self): assert 'openapi' in schema assert 'paths' in schema + def test_schema_with_no_paths(self): + patterns = [] + generator = SchemaGenerator(patterns=patterns) + + request = create_request('/') + schema = generator.get_schema(request=request) + + assert schema['paths'] == {} + def test_schema_information(self): """Construction of the top level dictionary.""" patterns = [ From 3b88312c33118467c53d7628346b2ae348b4ca6e Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 8 Jan 2020 20:42:59 +0100 Subject: [PATCH 236/907] Call get_schema(), rather than sub-method in schema tests. --- tests/schemas/test_openapi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 83473be4b3..8a723b85d9 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -659,7 +659,7 @@ def test_paths_construction(self): generator = SchemaGenerator(patterns=patterns) generator._initialise_endpoints() - paths = generator.get_paths() + paths = generator.get_schema()["paths"] assert '/example/' in paths example_operations = paths['/example/'] @@ -676,7 +676,7 @@ def test_prefixed_paths_construction(self): generator = SchemaGenerator(patterns=patterns) generator._initialise_endpoints() - paths = generator.get_paths() + paths = generator.get_schema()["paths"] assert '/v1/example/' in paths assert '/v1/example/{id}/' in paths @@ -689,7 +689,7 @@ def test_mount_url_prefixed_to_paths(self): generator = SchemaGenerator(patterns=patterns, url='/api') generator._initialise_endpoints() - paths = generator.get_paths() + paths = generator.get_schema()["paths"] assert '/api/example/' in paths assert '/api/example/{id}/' in paths From 496947be3a8ac6e21b862dc7697d96f403b18ad6 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 8 Jan 2020 21:12:26 +0100 Subject: [PATCH 237/907] Inline unnecessary method in OpenAPI schema generator. --- rest_framework/schemas/openapi.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index aaeb2914c1..1df132ce32 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -32,31 +32,30 @@ def get_info(self): return info - def get_paths(self, request=None): - result = {} + def get_schema(self, request=None, public=False): + """ + Generate a OpenAPI schema. + """ + self._initialise_endpoints() - _, view_endpoints = self._get_paths_and_endpoints(request) + # Iterate endpoints generating per method path operations. + # TODO: …and reference components. + paths = {} + _, view_endpoints = self._get_paths_and_endpoints(None if public else request) for path, method, view in view_endpoints: if not self.has_view_permissions(path, method, view): continue + operation = view.schema.get_operation(path, method) # Normalise path for any provided mount url. if path.startswith('/'): path = path[1:] path = urljoin(self.url or '/', path) - result.setdefault(path, {}) - result[path][method.lower()] = operation - - return result - - def get_schema(self, request=None, public=False): - """ - Generate a OpenAPI schema. - """ - self._initialise_endpoints() + paths.setdefault(path, {}) + paths[path][method.lower()] = operation - paths = self.get_paths(None if public else request) + # Compile final schema. schema = { 'openapi': '3.0.2', 'info': self.get_info(), From b1048984a7a839234ca604d199edbc9985c8a059 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Thu, 9 Jan 2020 21:07:52 -0500 Subject: [PATCH 238/907] Add failing test for `ListField` schema generation The `ListField` was generating a schema that contained `type=None` when a `ChoiceField` was the child, since we are not currently able to introspect the type of a `ChoiceField`. --- tests/schemas/test_openapi.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 8a723b85d9..6ad47359e1 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -52,6 +52,8 @@ def test_list_field_mapping(self): (serializers.ListField(child=serializers.CharField()), {'items': {'type': 'string'}, 'type': 'array'}), (serializers.ListField(child=serializers.IntegerField(max_value=4294967295)), {'items': {'type': 'integer', 'format': 'int64'}, 'type': 'array'}), + (serializers.ListField(child=serializers.ChoiceField(choices=[('a', 'Choice A'), ('b', 'Choice B')])), + {'items': {'enum': ['a', 'b']}, 'type': 'array'}), (serializers.IntegerField(min_value=2147483648), {'type': 'integer', 'minimum': 2147483648, 'format': 'int64'}), ] From 98c8af5291ac366b3030c4091284091ca63943ac Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Thu, 9 Jan 2020 20:43:44 -0500 Subject: [PATCH 239/907] `ListField` mapping should map all options for the child Previously it was only mapping the `type` and `format`, even though for some field types (like a `MultipleChoiceField`) we map more than just these. And for some fields (like a `ChoiceField`) we do not map the `type` at all. --- rest_framework/schemas/openapi.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 1df132ce32..cb5ea95a33 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -259,13 +259,7 @@ def _map_field(self, field): 'items': {}, } if not isinstance(field.child, _UnvalidatedField): - map_field = self._map_field(field.child) - items = { - "type": map_field.get('type') - } - if 'format' in map_field: - items['format'] = map_field.get('format') - mapping['items'] = items + mapping['items'] = self._map_field(field.child) return mapping # DateField and DateTimeField type is string From f8f8b3a1f1c1463d1836dc2e9f6614460d03fed4 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Thu, 9 Jan 2020 21:31:54 -0500 Subject: [PATCH 240/907] Adjust test for ListField(IntegerField) The `maximum` is valid here within the schema but it was not previously being included because we were not copying over the entire schema for the generated `IntegerField` previously. --- tests/schemas/test_openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 6ad47359e1..f734fd169d 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -51,7 +51,7 @@ def test_list_field_mapping(self): (serializers.ListField(child=serializers.FloatField()), {'items': {'type': 'number'}, 'type': 'array'}), (serializers.ListField(child=serializers.CharField()), {'items': {'type': 'string'}, 'type': 'array'}), (serializers.ListField(child=serializers.IntegerField(max_value=4294967295)), - {'items': {'type': 'integer', 'format': 'int64'}, 'type': 'array'}), + {'items': {'type': 'integer', 'maximum': 4294967295, 'format': 'int64'}, 'type': 'array'}), (serializers.ListField(child=serializers.ChoiceField(choices=[('a', 'Choice A'), ('b', 'Choice B')])), {'items': {'enum': ['a', 'b']}, 'type': 'array'}), (serializers.IntegerField(min_value=2147483648), From e4a26ad58a0e3d13e7cb788b724398592b1543b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Domingues?= Date: Thu, 23 Jan 2020 14:53:47 +0000 Subject: [PATCH 241/907] Corrected _get_serializer() argument order. (#7156) --- rest_framework/schemas/openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index cb5ea95a33..3a7eb29a72 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -449,7 +449,7 @@ def map_renderers(self, path, method): media_types.append(renderer.media_type) return media_types - def _get_serializer(self, method, path): + def _get_serializer(self, path, method): view = self.view if not hasattr(view, 'get_serializer'): From d22daf4e05bc670f4ff96d97da5d2a9cf83df6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Domingues?= Date: Sun, 26 Jan 2020 18:13:08 +0000 Subject: [PATCH 242/907] OpenAPI: Use _get_serializer() in _get_operation_id() (#7160) --- rest_framework/schemas/openapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 3a7eb29a72..351174aac0 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -117,8 +117,8 @@ def _get_operation_id(self, path, method): name = model.__name__ # Try with the serializer class name - elif hasattr(self.view, 'get_serializer_class'): - name = self.view.get_serializer_class().__name__ + elif self._get_serializer(path, method) is not None: + name = self._get_serializer(path, method).__class__.__name__ if name.endswith('Serializer'): name = name[:-10] From d21b8eb084488e9a5998a679d88031801eef5fe4 Mon Sep 17 00:00:00 2001 From: "William S. Vincent" Date: Tue, 28 Jan 2020 09:05:32 -0500 Subject: [PATCH 243/907] update Django for APIs book to 3.0 edition (#7164) --- docs/community/tutorials-and-resources.md | 4 ++-- docs/img/books/dfa-cover.jpg | Bin 0 -> 50099 bytes docs/img/books/rad-cover.png | Bin 14060 -> 0 bytes 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 docs/img/books/dfa-cover.jpg delete mode 100644 docs/img/books/rad-cover.png diff --git a/docs/community/tutorials-and-resources.md b/docs/community/tutorials-and-resources.md index 7993f54fb1..6fdac6004b 100644 --- a/docs/community/tutorials-and-resources.md +++ b/docs/community/tutorials-and-resources.md @@ -11,8 +11,8 @@ There are a wide range of resources available for learning and using Django REST - - + + diff --git a/docs/img/books/dfa-cover.jpg b/docs/img/books/dfa-cover.jpg new file mode 100644 index 0000000000000000000000000000000000000000..09ed268f2f9a3eb94cef28117b5c2ccc54e2c5e9 GIT binary patch literal 50099 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 literal 0 HcmV?d00001 diff --git a/docs/img/books/rad-cover.png b/docs/img/books/rad-cover.png deleted file mode 100644 index 75b19df64a683f3a9e1681bf14c58da65026d007..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14060 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 From 160f912a60754c22b2e351bbd9e4b3b4879450f8 Mon Sep 17 00:00:00 2001 From: Stella Date: Wed, 29 Jan 2020 10:20:51 +0100 Subject: [PATCH 244/907] Schemas: Handle default=false for boolean fields (#7165) --- rest_framework/schemas/openapi.py | 2 +- tests/schemas/test_openapi.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 351174aac0..6f3e854b0a 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -378,7 +378,7 @@ def _map_serializer(self, serializer): schema['writeOnly'] = True if field.allow_null: schema['nullable'] = True - if field.default and field.default != empty and not callable(field.default): + if field.default is not None and field.default != empty and not callable(field.default): schema['default'] = field.default if field.help_text: schema['description'] = str(field.help_text) diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index f734fd169d..dd3e87c7e1 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -70,6 +70,19 @@ class Serializer(serializers.Serializer): data = inspector._map_serializer(Serializer()) assert isinstance(data['properties']['text']['description'], str), "description must be str" + def test_boolean_default_field(self): + class Serializer(serializers.Serializer): + default_true = serializers.BooleanField(default=True) + default_false = serializers.BooleanField(default=False) + without_default = serializers.BooleanField() + + inspector = AutoSchema() + + data = inspector._map_serializer(Serializer()) + assert data['properties']['default_true']['default'] is True, "default must be true" + assert data['properties']['default_false']['default'] is False, "default must be false" + assert 'default' not in data['properties']['without_default'], "default must not be defined" + @pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.') class TestOperationIntrospection(TestCase): From bc4d52558bbf3d8a1311c23194123ea7517b2697 Mon Sep 17 00:00:00 2001 From: Dhaval Mehta Date: Wed, 29 Jan 2020 23:45:56 +0530 Subject: [PATCH 245/907] Schemas: Add mapping of type for ChoiceField. (#7161) --- rest_framework/schemas/openapi.py | 38 ++++++++++++++++++++++++++----- tests/schemas/test_openapi.py | 24 ++++++++++++++++++- 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 6f3e854b0a..fa887d63a6 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -1,4 +1,6 @@ import warnings +from collections import OrderedDict +from decimal import Decimal from operator import attrgetter from urllib.parse import urljoin @@ -209,6 +211,34 @@ def _get_pagination_parameters(self, path, method): return paginator.get_schema_operation_parameters(view) + def _map_choicefield(self, field): + choices = list(OrderedDict.fromkeys(field.choices)) # preserve order and remove duplicates + if all(isinstance(choice, bool) for choice in choices): + type = 'boolean' + elif all(isinstance(choice, int) for choice in choices): + type = 'integer' + elif all(isinstance(choice, (int, float, Decimal)) for choice in choices): # `number` includes `integer` + # Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.21 + type = 'number' + elif all(isinstance(choice, str) for choice in choices): + type = 'string' + else: + type = None + + mapping = { + # The value of `enum` keyword MUST be an array and SHOULD be unique. + # Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.20 + 'enum': choices + } + + # If We figured out `type` then and only then we should set it. It must be a string. + # Ref: https://swagger.io/docs/specification/data-models/data-types/#mixed-type + # It is optional but it can not be null. + # Ref: https://tools.ietf.org/html/draft-wright-json-schema-validation-00#section-5.21 + if type: + mapping['type'] = type + return mapping + def _map_field(self, field): # Nested Serializers, `many` or not. @@ -242,15 +272,11 @@ def _map_field(self, field): if isinstance(field, serializers.MultipleChoiceField): return { 'type': 'array', - 'items': { - 'enum': list(field.choices) - }, + 'items': self._map_choicefield(field) } if isinstance(field, serializers.ChoiceField): - return { - 'enum': list(field.choices), - } + return self._map_choicefield(field) # ListField. if isinstance(field, serializers.ListField): diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index dd3e87c7e1..b4cb2823f3 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -1,3 +1,5 @@ +import uuid + import pytest from django.conf.urls import url from django.test import RequestFactory, TestCase, override_settings @@ -44,6 +46,8 @@ def test_pagination(self): class TestFieldMapping(TestCase): def test_list_field_mapping(self): + uuid1 = uuid.uuid4() + uuid2 = uuid.uuid4() inspector = AutoSchema() cases = [ (serializers.ListField(), {'items': {}, 'type': 'array'}), @@ -53,7 +57,25 @@ def test_list_field_mapping(self): (serializers.ListField(child=serializers.IntegerField(max_value=4294967295)), {'items': {'type': 'integer', 'maximum': 4294967295, 'format': 'int64'}, 'type': 'array'}), (serializers.ListField(child=serializers.ChoiceField(choices=[('a', 'Choice A'), ('b', 'Choice B')])), - {'items': {'enum': ['a', 'b']}, 'type': 'array'}), + {'items': {'enum': ['a', 'b'], 'type': 'string'}, 'type': 'array'}), + (serializers.ListField(child=serializers.ChoiceField(choices=[(1, 'One'), (2, 'Two')])), + {'items': {'enum': [1, 2], 'type': 'integer'}, 'type': 'array'}), + (serializers.ListField(child=serializers.ChoiceField(choices=[(1.1, 'First'), (2.2, 'Second')])), + {'items': {'enum': [1.1, 2.2], 'type': 'number'}, 'type': 'array'}), + (serializers.ListField(child=serializers.ChoiceField(choices=[(True, 'true'), (False, 'false')])), + {'items': {'enum': [True, False], 'type': 'boolean'}, 'type': 'array'}), + (serializers.ListField(child=serializers.ChoiceField(choices=[(uuid1, 'uuid1'), (uuid2, 'uuid2')])), + {'items': {'enum': [uuid1, uuid2]}, 'type': 'array'}), + (serializers.ListField(child=serializers.ChoiceField(choices=[(1, 'One'), ('a', 'Choice A')])), + {'items': {'enum': [1, 'a']}, 'type': 'array'}), + (serializers.ListField(child=serializers.ChoiceField(choices=[ + (1, 'One'), ('a', 'Choice A'), (1.1, 'First'), (1.1, 'First'), (1, 'One'), ('a', 'Choice A'), (1, 'One') + ])), + {'items': {'enum': [1, 'a', 1.1]}, 'type': 'array'}), + (serializers.ListField(child=serializers.ChoiceField(choices=[ + (1, 'One'), (2, 'Two'), (3, 'Three'), (2, 'Two'), (3, 'Three'), (1, 'One'), + ])), + {'items': {'enum': [1, 2, 3], 'type': 'integer'}, 'type': 'array'}), (serializers.IntegerField(min_value=2147483648), {'type': 'integer', 'minimum': 2147483648, 'format': 'int64'}), ] From 79d37bce4cdc14c9642b206ad5690f2efe514d1a Mon Sep 17 00:00:00 2001 From: Kentalot Date: Thu, 30 Jan 2020 03:14:17 -0800 Subject: [PATCH 246/907] OpenAPI: Include type key in schema object properties dict. (#7169) --- rest_framework/schemas/openapi.py | 1 + tests/schemas/test_openapi.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index fa887d63a6..9c6610eafa 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -413,6 +413,7 @@ def _map_serializer(self, serializer): properties[field.field_name] = schema result = { + 'type': 'object', 'properties': properties } if required: diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index b4cb2823f3..0bee0a167b 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -335,6 +335,7 @@ class View(generics.GenericAPIView): 'schema': { 'type': 'array', 'items': { + 'type': 'object', 'properties': { 'text': { 'type': 'string', @@ -386,6 +387,7 @@ class View(generics.GenericAPIView): 'item': { 'type': 'array', 'items': { + 'type': 'object', 'properties': { 'text': { 'type': 'string', @@ -532,6 +534,7 @@ class View(generics.GenericAPIView): 'content': { 'application/json': { 'schema': { + 'type': 'object', 'properties': { 'text': { 'type': 'string', From 4137ef41efa77bd404bdc078159081740abdf930 Mon Sep 17 00:00:00 2001 From: Thorsten <51322849+tfranzel-cashlink@users.noreply.github.com> Date: Mon, 3 Feb 2020 14:41:47 +0100 Subject: [PATCH 247/907] Disable yaml aliases for schema generation. (#7131) --- rest_framework/renderers.py | 6 +++++- tests/schemas/test_openapi.py | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 29ac90ea8e..a96fa6e65d 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -1053,7 +1053,11 @@ def __init__(self): assert yaml, 'Using OpenAPIRenderer, but `pyyaml` is not installed.' def render(self, data, media_type=None, renderer_context=None): - return yaml.dump(data, default_flow_style=False, sort_keys=False).encode('utf-8') + # disable yaml advanced feature 'alias' for clean, portable, and readable output + class Dumper(yaml.Dumper): + def ignore_aliases(self, data): + return True + return yaml.dump(data, default_flow_style=False, sort_keys=False, Dumper=Dumper).encode('utf-8') class JSONOpenAPIRenderer(BaseRenderer): diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 0bee0a167b..cfa2e89ef8 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -8,7 +8,7 @@ from rest_framework import filters, generics, pagination, routers, serializers from rest_framework.compat import uritemplate from rest_framework.parsers import JSONParser, MultiPartParser -from rest_framework.renderers import JSONRenderer +from rest_framework.renderers import JSONRenderer, OpenAPIRenderer from rest_framework.request import Request from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator @@ -473,6 +473,19 @@ class View(generics.CreateAPIView): assert len(success_response['content'].keys()) == 1 assert 'application/json' in success_response['content'] + def test_openapi_yaml_rendering_without_aliases(self): + renderer = OpenAPIRenderer() + + reused_object = {'test': 'test'} + data = { + 'o1': reused_object, + 'o2': reused_object, + } + assert ( + renderer.render(data) == b'o1:\n test: test\no2:\n test: test\n' or + renderer.render(data) == b'o2:\n test: test\no1:\n test: test\n' # py <= 3.5 + ) + def test_serializer_filefield(self): path = '/{id}/' method = 'POST' From f81ca786427db40b648b5bcc0e67044163215457 Mon Sep 17 00:00:00 2001 From: Thorsten <51322849+tfranzel-cashlink@users.noreply.github.com> Date: Wed, 12 Feb 2020 20:35:54 +0100 Subject: [PATCH 248/907] Add file option to generateschema (#7130) --- docs/api-guide/schemas.md | 2 +- .../management/commands/generateschema.py | 8 +++++++- tests/schemas/test_managementcommand.py | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index e33a2a6112..e63fd83e6f 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -30,7 +30,7 @@ into the commonly used YAML-based OpenAPI format. If your schema is static, you can use the `generateschema` management command: ```bash -./manage.py generateschema > openapi-schema.yml +./manage.py generateschema --file openapi-schema.yml ``` Once you've generated a schema in this way you can annotate it with any diff --git a/rest_framework/management/commands/generateschema.py b/rest_framework/management/commands/generateschema.py index a7763492c5..024306b65f 100644 --- a/rest_framework/management/commands/generateschema.py +++ b/rest_framework/management/commands/generateschema.py @@ -25,6 +25,7 @@ def add_arguments(self, parser): parser.add_argument('--format', dest="format", choices=['openapi', 'openapi-json'], default='openapi', type=str) parser.add_argument('--urlconf', dest="urlconf", default=None, type=str) parser.add_argument('--generator_class', dest="generator_class", default=None, type=str) + parser.add_argument('--file', dest="file", default=None, type=str) def handle(self, *args, **options): if options['generator_class']: @@ -40,7 +41,12 @@ def handle(self, *args, **options): schema = generator.get_schema(request=None, public=True) renderer = self.get_renderer(options['format']) output = renderer.render(schema, renderer_context={}) - self.stdout.write(output.decode()) + + if options['file']: + with open(options['file'], 'wb') as f: + f.write(output) + else: + self.stdout.write(output.decode()) def get_renderer(self, format): if self.get_mode() == COREAPI_MODE: diff --git a/tests/schemas/test_managementcommand.py b/tests/schemas/test_managementcommand.py index 6cdf7f8b16..115f871e55 100644 --- a/tests/schemas/test_managementcommand.py +++ b/tests/schemas/test_managementcommand.py @@ -1,4 +1,6 @@ import io +import os +import tempfile import pytest from django.conf.urls import url @@ -73,6 +75,21 @@ def test_accepts_custom_schema_generator(self): out_json = yaml.safe_load(self.out.getvalue()) assert out_json == CustomSchemaGenerator.SCHEMA + def test_writes_schema_to_file_on_parameter(self): + fd, path = tempfile.mkstemp() + try: + call_command('generateschema', '--file={}'.format(path), stdout=self.out) + # nothing on stdout + assert not self.out.getvalue() + + call_command('generateschema', stdout=self.out) + expected_out = self.out.getvalue() + # file output identical to stdout output + with os.fdopen(fd) as fh: + assert expected_out and fh.read() == expected_out + finally: + os.remove(path) + @pytest.mark.skipif(yaml is None, reason='PyYAML is required.') @override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) def test_coreapi_renders_default_schema_with_custom_title_url_and_description(self): From d7b218f5eba78a99ae202e10c5d4b4d186735f7d Mon Sep 17 00:00:00 2001 From: Kevin Kennell Date: Mon, 17 Feb 2020 17:10:52 +0100 Subject: [PATCH 249/907] decode base64 credentials as utf8; adjust tests (#7193) * decode base64 credentials as utf8; adjust tests * basicauth: add dedicated test for utf8 credentials * basicauth: add fallback to latin-1 encoding if utf-8 fails --- rest_framework/authentication.py | 6 +++++- tests/authentication/test_authentication.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index 1e30728d34..a2ba53480e 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -74,7 +74,11 @@ def authenticate(self, request): raise exceptions.AuthenticationFailed(msg) try: - auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':') + try: + auth_decoded = base64.b64decode(auth[1]).decode('utf-8') + except UnicodeDecodeError: + auth_decoded = base64.b64decode(auth[1]).decode('latin-1') + auth_parts = auth_decoded.partition(':') except (TypeError, UnicodeDecodeError, binascii.Error): msg = _('Invalid basic header. Credentials not correctly base64 encoded.') raise exceptions.AuthenticationFailed(msg) diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py index 37e265e176..4760ea319c 100644 --- a/tests/authentication/test_authentication.py +++ b/tests/authentication/test_authentication.py @@ -159,6 +159,25 @@ def test_fail_post_if_credentials_contain_spaces(self): ) assert response.status_code == status.HTTP_401_UNAUTHORIZED + def test_decoding_of_utf8_credentials(self): + username = 'walterwhité' + email = 'walterwhite@example.com' + password = 'pässwörd' + User.objects.create_user( + username, email, password + ) + credentials = ('%s:%s' % (username, password)) + base64_credentials = base64.b64encode( + credentials.encode('utf-8') + ).decode(HTTP_HEADER_ENCODING) + auth = 'Basic %s' % base64_credentials + response = self.csrf_client.post( + '/basic/', + {'example': 'example'}, + HTTP_AUTHORIZATION=auth + ) + assert response.status_code == status.HTTP_200_OK + @override_settings(ROOT_URLCONF=__name__) class SessionAuthTests(TestCase): From 39dd34f161792146b7d33bf062366f54c2bde3f1 Mon Sep 17 00:00:00 2001 From: Dalei Date: Wed, 19 Feb 2020 19:56:12 +0800 Subject: [PATCH 250/907] Update docs for OpenAPI (#6814) (#7191) --- docs/api-guide/schemas.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index e63fd83e6f..6c228d448a 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -18,12 +18,12 @@ Django REST Framework provides support for automatic generation of ## Generating an OpenAPI Schema -### Install `pyyaml` +### Install dependencies -You'll need to install `pyyaml`, so that you can render your generated schema -into the commonly used YAML-based OpenAPI format. + pip install pyyaml uritemplate - pip install pyyaml +* `pyyaml` is used to generate schema into YAML-based OpenAPI format. +* `uritemplate` is used internally to get parameters in path. ### Generating a static schema with the `generateschema` management command From 4faa67419633d5440d8290e0b2bc6fb5e6433d03 Mon Sep 17 00:00:00 2001 From: Yoo In Keun Date: Thu, 20 Feb 2020 04:16:42 +0900 Subject: [PATCH 251/907] Fixed docs typo. (#7188) --- docs/api-guide/schemas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 6c228d448a..91c2bbabfb 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -122,7 +122,7 @@ on a per-view basis. ### Schema Level Customization -In order to customize the top-level schema sublass +In order to customize the top-level schema subclass `rest_framework.schemas.openapi.SchemaGenerator` and provide it as an argument to the `generateschema` command or `get_schema_view()` helper function. From 92a4a5d42346c9c89f38683cf2d6cbeb976ee9ab Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Thu, 20 Feb 2020 02:23:06 -0800 Subject: [PATCH 252/907] Fix docs 404 (#7197) * Use 'site_url' instead of hardcoding DRF homepage * Use 'url' template filter instead of 'base_url' This fixes static file loading for the 404 page. * Only insert funding
    if toc is present * Link quickstart to valid API guide page * Fix 404 search modal link * Use 'base_url' instead of 'site_url' on 404 page --- docs/tutorial/quickstart.md | 6 +++--- docs_theme/404.html | 2 +- docs_theme/main.html | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 505f7f91d5..546144670c 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -137,12 +137,12 @@ Finally, we're including default login and logout views for use with the browsab ## Pagination 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', 'PAGE_SIZE': 10 } - + ## Settings Add `'rest_framework'` to `INSTALLED_APPS`. The settings module will be in `tutorial/settings.py` @@ -224,5 +224,5 @@ If you want to get a more in depth understanding of how REST framework fits toge [image]: ../img/quickstart.png [tutorial]: 1-serialization.md -[guide]: ../#api-guide +[guide]: ../api-guide/requests.md [httpie]: https://github.com/jakubroztocil/httpie#installation diff --git a/docs_theme/404.html b/docs_theme/404.html index a89c0a418d..bbb6b70ffc 100644 --- a/docs_theme/404.html +++ b/docs_theme/404.html @@ -4,6 +4,6 @@

    404

    Page not found

    -
    +

    Try the homepage, or search the documentation.

    {% endblock %} diff --git a/docs_theme/main.html b/docs_theme/main.html index 21e9171a2a..c2a29e1aeb 100644 --- a/docs_theme/main.html +++ b/docs_theme/main.html @@ -5,17 +5,17 @@ {% if page.title %}{{ page.title }} - {% endif %}{{ config.site_name }} - + - - - - + + + + - - - - + + + + {% for path in config.extra_javascript %} From 764dabd29e127a0b1a07794f8268a1b1535d9507 Mon Sep 17 00:00:00 2001 From: Prayash Mohapatra Date: Thu, 20 Feb 2020 10:55:13 +0000 Subject: [PATCH 253/907] Update writeable nested serializer doc (#7198) --- docs/api-guide/serializers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 4679b1ed16..96a0e02225 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -333,7 +333,7 @@ Here's an example for an `.update()` method on our previous `UserSerializer` cla def update(self, instance, validated_data): profile_data = validated_data.pop('profile') # Unless the application properly enforces that this field is - # always set, the follow could raise a `DoesNotExist`, which + # always set, the following could raise a `DoesNotExist`, which # would need to be handled. profile = instance.profile From e32ffbb12b43cd64d9476e674dc27b140b1e3658 Mon Sep 17 00:00:00 2001 From: Samuel Dion-Girardeau Date: Mon, 24 Feb 2020 19:33:00 -0500 Subject: [PATCH 254/907] Fix docs code example (#7201) --- docs/api-guide/serializers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 96a0e02225..5cf949f979 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -382,8 +382,8 @@ This manager class now more nicely encapsulates that user instances and profile def create(self, validated_data): return User.objects.create( username=validated_data['username'], - email=validated_data['email'] - is_premium_member=validated_data['profile']['is_premium_member'] + email=validated_data['email'], + is_premium_member=validated_data['profile']['is_premium_member'], has_support_contract=validated_data['profile']['has_support_contract'] ) From 2a5c2f3f701cca6f531c5e12790a2d63b0d6b4fd Mon Sep 17 00:00:00 2001 From: Dhaval Mehta <20968146+dhaval-mehta@users.noreply.github.com> Date: Fri, 28 Feb 2020 16:36:03 +0530 Subject: [PATCH 255/907] Added OpenAPI tags to schemas. (#7184) --- docs/api-guide/schemas.md | 75 +++++++++++++++++++++++++++++++ rest_framework/schemas/openapi.py | 20 +++++++++ tests/schemas/test_openapi.py | 51 +++++++++++++++++++++ 3 files changed, 146 insertions(+) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 91c2bbabfb..2e5ffc79b8 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -215,6 +215,81 @@ This also applies to extra actions for `ViewSet`s: If you wish to provide a base `AutoSchema` subclass to be used throughout your project you may adjust `settings.DEFAULT_SCHEMA_CLASS` appropriately. + +### Grouping Operations With Tags + +Tags can be used to group logical operations. Each tag name in the list MUST be unique. + +--- +#### Django REST Framework generates tags automatically with the following logic: + +Tag name will be first element from the path. Also, any `_` in path name will be replaced by a `-`. +Consider below examples. + +Example 1: Consider a user management system. The following table will illustrate the tag generation logic. +Here first element from the paths is: `users`. Hence tag wil be `users` + +Http Method | Path | Tags +-------------------------------------|-------------------|------------- +PUT, PATCH, GET(Retrieve), DELETE | /users/{id}/ | ['users'] +POST, GET(List) | /users/ | ['users'] + +Example 2: Consider a restaurant management system. The System has restaurants. Each restaurant has branches. +Consider REST APIs to deal with a branch of a particular restaurant. +Here first element from the paths is: `restaurants`. Hence tag wil be `restaurants`. + +Http Method | Path | Tags +-------------------------------------|----------------------------------------------------|------------------- +PUT, PATCH, GET(Retrieve), DELETE: | /restaurants/{restaurant_id}/branches/{branch_id} | ['restaurants'] +POST, GET(List): | /restaurants/{restaurant_id}/branches/ | ['restaurants'] + +Example 3: Consider Order items for an e commerce company. + +Http Method | Path | Tags +-------------------------------------|-------------------------|------------- +PUT, PATCH, GET(Retrieve), DELETE | /order_items/{id}/ | ['order-items'] +POST, GET(List) | /order_items/ | ['order-items'] + + +--- +#### Overriding auto generated tags: +You can override auto-generated tags by passing `tags` argument to the constructor of `AutoSchema`. `tags` argument must be a list or tuple of string. +```python +from rest_framework.schemas.openapi import AutoSchema +from rest_framework.views import APIView + +class MyView(APIView): + schema = AutoSchema(tags=['tag1', 'tag2']) + ... +``` + +If you need more customization, you can override the `get_tags` method of `AutoSchema` class. Consider the following example: + +```python +from rest_framework.schemas.openapi import AutoSchema +from rest_framework.views import APIView + +class MySchema(AutoSchema): + ... + def get_tags(self, path, method): + if method == 'POST': + tags = ['tag1', 'tag2'] + elif method == 'GET': + tags = ['tag2', 'tag3'] + elif path == '/example/path/': + tags = ['tag3', 'tag4'] + else: + tags = ['tag5', 'tag6', 'tag7'] + + return tags + +class MyView(APIView): + schema = MySchema() + ... +``` + + [openapi]: https://github.com/OAI/OpenAPI-Specification [openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification-extensions [openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject +[openapi-tags]: https://swagger.io/specification/#tagObject diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 9c6610eafa..5277f17a61 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -71,6 +71,12 @@ def get_schema(self, request=None, public=False): class AutoSchema(ViewInspector): + def __init__(self, tags=None): + if tags and not all(isinstance(tag, str) for tag in tags): + raise ValueError('tags must be a list or tuple of string.') + self._tags = tags + super().__init__() + request_media_types = [] response_media_types = [] @@ -98,6 +104,7 @@ def get_operation(self, path, method): if request_body: operation['requestBody'] = request_body operation['responses'] = self._get_responses(path, method) + operation['tags'] = self.get_tags(path, method) return operation @@ -564,3 +571,16 @@ def _get_responses(self, path, method): 'description': "" } } + + def get_tags(self, path, method): + # If user have specified tags, use them. + if self._tags: + return self._tags + + # First element of a specific path could be valid tag. This is a fallback solution. + # PUT, PATCH, GET(Retrieve), DELETE: /user_profile/{id}/ tags = [user-profile] + # POST, GET(List): /user_profile/ tags = [user-profile] + if path.startswith('/'): + path = path[1:] + + return [path.split('/')[0].replace('_', '-')] diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index cfa2e89ef8..7f73c8c300 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -126,6 +126,7 @@ def test_path_without_parameters(self): 'operationId': 'listDocStringExamples', 'description': 'A description of my GET operation.', 'parameters': [], + 'tags': ['example'], 'responses': { '200': { 'description': '', @@ -166,6 +167,7 @@ def test_path_with_id_parameter(self): 'type': 'string', }, }], + 'tags': ['example'], 'responses': { '200': { 'description': '', @@ -696,6 +698,55 @@ def test_serializer_validators(self): assert properties['ip']['type'] == 'string' assert 'format' not in properties['ip'] + def test_overridden_tags(self): + class ExampleStringTagsViewSet(views.ExampleGenericAPIView): + schema = AutoSchema(tags=['example1', 'example2']) + + url_patterns = [ + url(r'^test/?$', ExampleStringTagsViewSet.as_view()), + ] + generator = SchemaGenerator(patterns=url_patterns) + schema = generator.get_schema(request=create_request('/')) + assert schema['paths']['/test/']['get']['tags'] == ['example1', 'example2'] + + def test_overridden_get_tags_method(self): + class MySchema(AutoSchema): + def get_tags(self, path, method): + if path.endswith('/new/'): + tags = ['tag1', 'tag2'] + elif path.endswith('/old/'): + tags = ['tag2', 'tag3'] + else: + tags = ['tag4', 'tag5'] + + return tags + + class ExampleStringTagsViewSet(views.ExampleGenericViewSet): + schema = MySchema() + + router = routers.SimpleRouter() + router.register('example', ExampleStringTagsViewSet, basename="example") + generator = SchemaGenerator(patterns=router.urls) + schema = generator.get_schema(request=create_request('/')) + assert schema['paths']['/example/new/']['get']['tags'] == ['tag1', 'tag2'] + assert schema['paths']['/example/old/']['get']['tags'] == ['tag2', 'tag3'] + + def test_auto_generated_apiview_tags(self): + class RestaurantAPIView(views.ExampleGenericAPIView): + pass + + class BranchAPIView(views.ExampleGenericAPIView): + pass + + url_patterns = [ + url(r'^any-dash_underscore/?$', RestaurantAPIView.as_view()), + url(r'^restaurants/branches/?$', BranchAPIView.as_view()) + ] + generator = SchemaGenerator(patterns=url_patterns) + schema = generator.get_schema(request=create_request('/')) + assert schema['paths']['/any-dash_underscore/']['get']['tags'] == ['any-dash-underscore'] + assert schema['paths']['/restaurants/branches/']['get']['tags'] == ['restaurants'] + @pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.') @override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema'}) From 94a09149b62496b5434a690de84b5972a5d5b554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Legi=C4=99cki?= Date: Mon, 2 Mar 2020 16:32:26 +0100 Subject: [PATCH 256/907] OpenAPI: Use 201 status code for POST requests. (#7206) --- rest_framework/schemas/openapi.py | 4 ++-- tests/schemas/test_openapi.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 5277f17a61..cb0407b62b 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -558,9 +558,9 @@ def _get_responses(self, path, method): response_schema = paginator.get_paginated_response_schema(response_schema) else: response_schema = item_schema - + status_code = '201' if method == 'POST' else '200' return { - '200': { + status_code: { 'content': { ct: {'schema': response_schema} for ct in self.response_media_types diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 7f73c8c300..6f5f42dac4 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -275,9 +275,10 @@ class View(generics.GenericAPIView): inspector.view = view responses = inspector._get_responses(path, method) - assert responses['200']['content']['application/json']['schema']['required'] == ['text'] - assert list(responses['200']['content']['application/json']['schema']['properties'].keys()) == ['text'] - assert 'description' in responses['200'] + assert '201' in responses + assert responses['201']['content']['application/json']['schema']['required'] == ['text'] + assert list(responses['201']['content']['application/json']['schema']['properties'].keys()) == ['text'] + assert 'description' in responses['201'] def test_response_body_nested_serializer(self): path = '/' @@ -302,7 +303,7 @@ class View(generics.GenericAPIView): inspector.view = view responses = inspector._get_responses(path, method) - schema = responses['200']['content']['application/json']['schema'] + schema = responses['201']['content']['application/json']['schema'] assert sorted(schema['required']) == ['nested', 'text'] assert sorted(list(schema['properties'].keys())) == ['nested', 'text'] assert schema['properties']['nested']['type'] == 'object' From 5b16a1724202d6f4ef58d22caa492893ee5f3aa8 Mon Sep 17 00:00:00 2001 From: Martin Desrumaux <9059840+gnuletik@users.noreply.github.com> Date: Mon, 2 Mar 2020 16:40:18 +0100 Subject: [PATCH 257/907] OpenAPI: Allow customizing operation name. (#7190) --- docs/api-guide/schemas.md | 33 +++++++++++++++ rest_framework/schemas/openapi.py | 42 +++++++++++++------ tests/schemas/test_openapi.py | 68 ++++++++++++++++++++++++++++++- 3 files changed, 129 insertions(+), 14 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 2e5ffc79b8..5766a6a61c 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -288,8 +288,41 @@ class MyView(APIView): ... ``` +### OperationId + +The schema generator generates an [operationid](openapi-operationid) for each operation. This `operationId` is deduced from the model name, serializer name or view name. The operationId may looks like "ListItems", "RetrieveItem", "UpdateItem", etc.. + +If you have several views with the same model, the generator may generate duplicate operationId. +In order to work around this, you can override the second part of the operationId: operation name. + +```python +from rest_framework.schemas.openapi import AutoSchema + +class ExampleView(APIView): + """APIView subclass with custom schema introspection.""" + schema = AutoSchema(operation_id_base="Custom") +``` + +The previous example will generate the following operationId: "ListCustoms", "RetrieveCustom", "UpdateCustom", "PartialUpdateCustom", "DestroyCustom". +You need to provide the singular form of he operation name. For the list operation, a "s" will be appended at the end of the operation. + +If you need more configuration over the `operationId` field, you can override the `get_operation_id_base` and `get_operation_id` methods from the `AutoSchema` class: + +```python +class CustomSchema(AutoSchema): + def get_operation_id_base(self, path, method, action): + pass + + def get_operation_id(self, path, method): + pass + +class CustomView(APIView): + """APIView subclass with custom schema introspection.""" + schema = CustomSchema() +``` [openapi]: https://github.com/OAI/OpenAPI-Specification [openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification-extensions [openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject [openapi-tags]: https://swagger.io/specification/#tagObject +[openapi-operationid]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#fixed-fields-17 diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index cb0407b62b..293f7c2a40 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -71,10 +71,14 @@ def get_schema(self, request=None, public=False): class AutoSchema(ViewInspector): - def __init__(self, tags=None): + def __init__(self, operation_id_base=None, tags=None): + """ + :param operation_id_base: user-defined name in operationId. If empty, it will be deducted from the Model/Serializer/View name. + """ if tags and not all(isinstance(tag, str) for tag in tags): raise ValueError('tags must be a list or tuple of string.') self._tags = tags + self.operation_id_base = operation_id_base super().__init__() request_media_types = [] @@ -91,7 +95,7 @@ def __init__(self, tags=None): def get_operation(self, path, method): operation = {} - operation['operationId'] = self._get_operation_id(path, method) + operation['operationId'] = self.get_operation_id(path, method) operation['description'] = self.get_description(path, method) parameters = [] @@ -108,21 +112,17 @@ def get_operation(self, path, method): return operation - def _get_operation_id(self, path, method): + def get_operation_id_base(self, path, method, action): """ - Compute an operation ID from the model, serializer or view name. + Compute the base part for operation ID from the model, serializer or view name. """ - method_name = getattr(self.view, 'action', method.lower()) - if is_list_view(path, method, self.view): - action = 'list' - elif method_name not in self.method_mapping: - action = method_name - else: - action = self.method_mapping[method.lower()] + model = getattr(getattr(self.view, 'queryset', None), 'model', None) + + if self.operation_id_base is not None: + name = self.operation_id_base # Try to deduce the ID from the view's model - model = getattr(getattr(self.view, 'queryset', None), 'model', None) - if model is not None: + elif model is not None: name = model.__name__ # Try with the serializer class name @@ -147,6 +147,22 @@ def _get_operation_id(self, path, method): if action == 'list' and not name.endswith('s'): # listThings instead of listThing name += 's' + return name + + def get_operation_id(self, path, method): + """ + Compute an operation ID from the view type and get_operation_id_base method. + """ + method_name = getattr(self.view, 'action', method.lower()) + if is_list_view(path, method, self.view): + action = 'list' + elif method_name not in self.method_mapping: + action = method_name + else: + action = self.method_mapping[method.lower()] + + name = self.get_operation_id_base(path, method, action) + return action + name def _get_path_parameters(self, path, method): diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 6f5f42dac4..05f1ccfed4 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -575,9 +575,75 @@ def test_operation_id_generation(self): inspector = AutoSchema() inspector.view = view - operationId = inspector._get_operation_id(path, method) + operationId = inspector.get_operation_id(path, method) assert operationId == 'listExamples' + def test_operation_id_custom_operation_id_base(self): + path = '/' + method = 'GET' + + view = create_view( + views.ExampleGenericAPIView, + method, + create_request(path), + ) + inspector = AutoSchema(operation_id_base="Ulysse") + inspector.view = view + + operationId = inspector.get_operation_id(path, method) + assert operationId == 'listUlysses' + + def test_operation_id_custom_name(self): + path = '/' + method = 'GET' + + view = create_view( + views.ExampleGenericAPIView, + method, + create_request(path), + ) + inspector = AutoSchema(operation_id_base='Ulysse') + inspector.view = view + + operationId = inspector.get_operation_id(path, method) + assert operationId == 'listUlysses' + + def test_operation_id_override_get(self): + class CustomSchema(AutoSchema): + def get_operation_id(self, path, method): + return 'myCustomOperationId' + + path = '/' + method = 'GET' + view = create_view( + views.ExampleGenericAPIView, + method, + create_request(path), + ) + inspector = CustomSchema() + inspector.view = view + + operationId = inspector.get_operation_id(path, method) + assert operationId == 'myCustomOperationId' + + def test_operation_id_override_base(self): + class CustomSchema(AutoSchema): + def get_operation_id_base(self, path, method, action): + return 'Item' + + path = '/' + method = 'GET' + view = create_view( + views.ExampleGenericAPIView, + method, + create_request(path), + ) + inspector = CustomSchema() + inspector.view = view + + operationId = inspector.get_operation_id(path, method) + assert operationId == 'listItem' + def test_repeat_operation_ids(self): router = routers.SimpleRouter() router.register('account', views.ExampleGenericViewSet, basename="account") From 797518af6d996308781e283601057ff82ed8684c Mon Sep 17 00:00:00 2001 From: Martin Desrumaux <9059840+gnuletik@users.noreply.github.com> Date: Mon, 2 Mar 2020 16:44:06 +0100 Subject: [PATCH 258/907] OpenAPI: Warn user about duplicate operationIds. (#7207) --- rest_framework/schemas/openapi.py | 28 ++++++++++++++++++++++++++++ tests/schemas/test_openapi.py | 19 +++++++++++++++++++ tests/schemas/views.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 293f7c2a40..d3a373aaa8 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -34,6 +34,32 @@ def get_info(self): return info + def check_duplicate_operation_id(self, paths): + ids = {} + for route in paths: + for method in paths[route]: + if 'operationId' not in paths[route][method]: + continue + operation_id = paths[route][method]['operationId'] + if operation_id in ids: + warnings.warn( + 'You have a duplicated operationId in your OpenAPI schema: {operation_id}\n' + '\tRoute: {route1}, Method: {method1}\n' + '\tRoute: {route2}, Method: {method2}\n' + '\tAn operationId has to be unique accros your schema. Your schema may not work in other tools.' + .format( + route1=ids[operation_id]['route'], + method1=ids[operation_id]['method'], + route2=route, + method2=method, + operation_id=operation_id + ) + ) + ids[operation_id] = { + 'route': route, + 'method': method + } + def get_schema(self, request=None, public=False): """ Generate a OpenAPI schema. @@ -57,6 +83,8 @@ def get_schema(self, request=None, public=False): paths.setdefault(path, {}) paths[path][method.lower()] = operation + self.check_duplicate_operation_id(paths) + # Compile final schema. schema = { 'openapi': '3.0.2', diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 05f1ccfed4..b3f30b258b 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -1,4 +1,5 @@ import uuid +import warnings import pytest from django.conf.urls import url @@ -659,6 +660,24 @@ def test_repeat_operation_ids(self): assert schema_str.count("newExample") == 1 assert schema_str.count("oldExample") == 1 + def test_duplicate_operation_id(self): + patterns = [ + url(r'^duplicate1/?$', views.ExampleOperationIdDuplicate1.as_view()), + url(r'^duplicate2/?$', views.ExampleOperationIdDuplicate2.as_view()), + ] + + generator = SchemaGenerator(patterns=patterns) + request = create_request('/') + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + generator.get_schema(request=request) + + assert len(w) == 1 + assert issubclass(w[-1].category, UserWarning) + print(str(w[-1].message)) + assert 'You have a duplicated operationId' in str(w[-1].message) + def test_serializer_datefield(self): path = '/' method = 'GET' diff --git a/tests/schemas/views.py b/tests/schemas/views.py index e8307ccbd0..5835a55727 100644 --- a/tests/schemas/views.py +++ b/tests/schemas/views.py @@ -4,6 +4,7 @@ DecimalValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator, MinValueValidator, RegexValidator ) +from django.db import models from rest_framework import generics, permissions, serializers from rest_framework.decorators import action @@ -137,3 +138,32 @@ def get(self, *args, **kwargs): url='http://localhost', uuid=uuid.uuid4(), ip4='127.0.0.1', ip6='::1', ip='192.168.1.1') return Response(serializer.data) + + +# Serializer with model. +class OpenAPIExample(models.Model): + first_name = models.CharField(max_length=30) + + +class ExampleSerializerModel(serializers.Serializer): + date = serializers.DateField() + datetime = serializers.DateTimeField() + hstore = serializers.HStoreField() + uuid_field = serializers.UUIDField(default=uuid.uuid4) + + class Meta: + model = OpenAPIExample + + +class ExampleOperationIdDuplicate1(generics.GenericAPIView): + serializer_class = ExampleSerializerModel + + def get(self, *args, **kwargs): + pass + + +class ExampleOperationIdDuplicate2(generics.GenericAPIView): + serializer_class = ExampleSerializerModel + + def get(self, *args, **kwargs): + pass From 8aa8be7653cf441e81cabd9be945f809cb617192 Mon Sep 17 00:00:00 2001 From: Martin Desrumaux <9059840+gnuletik@users.noreply.github.com> Date: Mon, 2 Mar 2020 19:35:27 +0100 Subject: [PATCH 259/907] Implement OpenAPI Components (#7124) --- docs/api-guide/schemas.md | 63 +++++++++ rest_framework/schemas/openapi.py | 94 ++++++++---- tests/schemas/test_openapi.py | 228 ++++++++++++++++++++++-------- tests/schemas/views.py | 48 +++++++ 4 files changed, 347 insertions(+), 86 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 5766a6a61c..1d1e09b46a 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -316,6 +316,65 @@ class CustomSchema(AutoSchema): def get_operation_id(self, path, method): pass +class MyView(APIView): + schema = AutoSchema(component_name="Ulysses") +``` + +### Components + +Since DRF 3.12, Schema uses the [OpenAPI Components](openapi-components). This method defines components in the schema and [references them](openapi-reference) inside request and response objects. By default, the component's name is deduced from the Serializer's name. + +Using OpenAPI's components provides the following advantages: +* The schema is more readable and lightweight. +* If you use the schema to generate an SDK (using [openapi-generator](openapi-generator) or [swagger-codegen](swagger-codegen)). The generator can name your SDK's models. + +### Handling component's schema errors + +You may get the following error while generating the schema: +``` +"Serializer" is an invalid class name for schema generation. +Serializer's class name should be unique and explicit. e.g. "ItemSerializer". +``` + +This error occurs when the Serializer name is "Serializer". You should choose a component's name unique across your schema and different than "Serializer". + +You may also get the following warning: +``` +Schema component "ComponentName" has been overriden with a different value. +``` + +This warning occurs when different components have the same name in one schema. Your component name should be unique across your project. This is likely an error that may lead to an invalid schema. + +You have two ways to solve the previous issues: +* You can rename your serializer with a unique name and another name than "Serializer". +* You can set the `component_name` kwarg parameter of the AutoSchema constructor (see below). +* You can override the `get_component_name` method of the AutoSchema class (see below). + +#### Set a custom component's name for your view + +To override the component's name in your view, you can use the `component_name` parameter of the AutoSchema constructor: + +```python +from rest_framework.schemas.openapi import AutoSchema + +class MyView(APIView): + schema = AutoSchema(component_name="Ulysses") +``` + +#### Override the default implementation + +If you want to have more control and customization about how the schema's components are generated, you can override the `get_component_name` and `get_components` method from the AutoSchema class. + +```python +from rest_framework.schemas.openapi import AutoSchema + +class CustomSchema(AutoSchema): + def get_components(self, path, method): + # Implement your custom implementation + + def get_component_name(self, serializer): + # Implement your custom implementation + class CustomView(APIView): """APIView subclass with custom schema introspection.""" schema = CustomSchema() @@ -326,3 +385,7 @@ class CustomView(APIView): [openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject [openapi-tags]: https://swagger.io/specification/#tagObject [openapi-operationid]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#fixed-fields-17 +[openapi-components]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#componentsObject +[openapi-reference]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#referenceObject +[openapi-generator]: https://github.com/OpenAPITools/openapi-generator +[swagger-codegen]: https://github.com/swagger-api/swagger-codegen diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index d3a373aaa8..6bed120922 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -1,3 +1,4 @@ +import re import warnings from collections import OrderedDict from decimal import Decimal @@ -65,9 +66,9 @@ def get_schema(self, request=None, public=False): Generate a OpenAPI schema. """ self._initialise_endpoints() + components_schemas = {} # Iterate endpoints generating per method path operations. - # TODO: …and reference components. paths = {} _, view_endpoints = self._get_paths_and_endpoints(None if public else request) for path, method, view in view_endpoints: @@ -75,6 +76,16 @@ def get_schema(self, request=None, public=False): continue operation = view.schema.get_operation(path, method) + components = view.schema.get_components(path, method) + for k in components.keys(): + if k not in components_schemas: + continue + if components_schemas[k] == components[k]: + continue + warnings.warn('Schema component "{}" has been overriden with a different value.'.format(k)) + + components_schemas.update(components) + # Normalise path for any provided mount url. if path.startswith('/'): path = path[1:] @@ -92,6 +103,11 @@ def get_schema(self, request=None, public=False): 'paths': paths, } + if len(components_schemas) > 0: + schema['components'] = { + 'schemas': components_schemas + } + return schema # View Inspectors @@ -99,14 +115,16 @@ def get_schema(self, request=None, public=False): class AutoSchema(ViewInspector): - def __init__(self, operation_id_base=None, tags=None): + def __init__(self, tags=None, operation_id_base=None, component_name=None): """ :param operation_id_base: user-defined name in operationId. If empty, it will be deducted from the Model/Serializer/View name. + :param component_name: user-defined component's name. If empty, it will be deducted from the Serializer's class name. """ if tags and not all(isinstance(tag, str) for tag in tags): raise ValueError('tags must be a list or tuple of string.') self._tags = tags self.operation_id_base = operation_id_base + self.component_name = component_name super().__init__() request_media_types = [] @@ -140,6 +158,43 @@ def get_operation(self, path, method): return operation + def get_component_name(self, serializer): + """ + Compute the component's name from the serializer. + Raise an exception if the serializer's class name is "Serializer" (case-insensitive). + """ + if self.component_name is not None: + return self.component_name + + # use the serializer's class name as the component name. + component_name = serializer.__class__.__name__ + # We remove the "serializer" string from the class name. + pattern = re.compile("serializer", re.IGNORECASE) + component_name = pattern.sub("", component_name) + + if component_name == "": + raise Exception( + '"{}" is an invalid class name for schema generation. ' + 'Serializer\'s class name should be unique and explicit. e.g. "ItemSerializer"' + .format(serializer.__class__.__name__) + ) + + return component_name + + def get_components(self, path, method): + """ + Return components with their properties from the serializer. + """ + serializer = self._get_serializer(path, method) + + if not isinstance(serializer, serializers.Serializer): + return {} + + component_name = self.get_component_name(serializer) + + content = self._map_serializer(serializer) + return {component_name: content} + def get_operation_id_base(self, path, method, action): """ Compute the base part for operation ID from the model, serializer or view name. @@ -434,10 +489,6 @@ def _map_min_max(self, field, content): def _map_serializer(self, serializer): # Assuming we have a valid serializer instance. - # TODO: - # - field is Nested or List serializer. - # - Handle read_only/write_only for request/response differences. - # - could do this with readOnly/writeOnly and then filter dict. required = [] properties = {} @@ -542,6 +593,9 @@ def _get_serializer(self, path, method): .format(view.__class__.__name__, method, path)) return None + def _get_reference(self, serializer): + return {'$ref': '#/components/schemas/{}'.format(self.get_component_name(serializer))} + def _get_request_body(self, path, method): if method not in ('PUT', 'PATCH', 'POST'): return {} @@ -551,20 +605,13 @@ def _get_request_body(self, path, method): serializer = self._get_serializer(path, method) if not isinstance(serializer, serializers.Serializer): - return {} - - content = self._map_serializer(serializer) - # No required fields for PATCH - if method == 'PATCH': - content.pop('required', None) - # No read_only fields for request. - for name, schema in content['properties'].copy().items(): - if 'readOnly' in schema: - del content['properties'][name] + item_schema = {} + else: + item_schema = self._get_reference(serializer) return { 'content': { - ct: {'schema': content} + ct: {'schema': item_schema} for ct in self.request_media_types } } @@ -580,17 +627,12 @@ def _get_responses(self, path, method): self.response_media_types = self.map_renderers(path, method) - item_schema = {} serializer = self._get_serializer(path, method) - if isinstance(serializer, serializers.Serializer): - item_schema = self._map_serializer(serializer) - # No write_only fields for response. - for name, schema in item_schema['properties'].copy().items(): - if 'writeOnly' in schema: - del item_schema['properties'][name] - if 'required' in item_schema: - item_schema['required'] = [f for f in item_schema['required'] if f != name] + if not isinstance(serializer, serializers.Serializer): + item_schema = {} + else: + item_schema = self._get_reference(serializer) if is_list_view(path, method, self.view): response_schema = { diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index b3f30b258b..95101403a3 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -85,12 +85,12 @@ def test_list_field_mapping(self): assert inspector._map_field(field) == mapping def test_lazy_string_field(self): - class Serializer(serializers.Serializer): + class ItemSerializer(serializers.Serializer): text = serializers.CharField(help_text=_('lazy string')) inspector = AutoSchema() - data = inspector._map_serializer(Serializer()) + data = inspector._map_serializer(ItemSerializer()) assert isinstance(data['properties']['text']['description'], str), "description must be str" def test_boolean_default_field(self): @@ -186,6 +186,33 @@ def test_request_body(self): path = '/' method = 'POST' + class ItemSerializer(serializers.Serializer): + text = serializers.CharField() + read_only = serializers.CharField(read_only=True) + + class View(generics.GenericAPIView): + serializer_class = ItemSerializer + + view = create_view( + View, + method, + create_request(path) + ) + inspector = AutoSchema() + inspector.view = view + + request_body = inspector._get_request_body(path, method) + print(request_body) + assert request_body['content']['application/json']['schema']['$ref'] == '#/components/schemas/Item' + + components = inspector.get_components(path, method) + assert components['Item']['required'] == ['text'] + assert sorted(list(components['Item']['properties'].keys())) == ['read_only', 'text'] + + def test_invalid_serializer_class_name(self): + path = '/' + method = 'POST' + class Serializer(serializers.Serializer): text = serializers.CharField() read_only = serializers.CharField(read_only=True) @@ -201,20 +228,22 @@ class View(generics.GenericAPIView): inspector = AutoSchema() inspector.view = view - request_body = inspector._get_request_body(path, method) - assert request_body['content']['application/json']['schema']['required'] == ['text'] - assert list(request_body['content']['application/json']['schema']['properties'].keys()) == ['text'] + serializer = inspector._get_serializer(path, method) + + with pytest.raises(Exception) as exc: + inspector.get_component_name(serializer) + assert "is an invalid class name for schema generation" in str(exc.value) def test_empty_required(self): path = '/' method = 'POST' - class Serializer(serializers.Serializer): + class ItemSerializer(serializers.Serializer): read_only = serializers.CharField(read_only=True) write_only = serializers.CharField(write_only=True, required=False) class View(generics.GenericAPIView): - serializer_class = Serializer + serializer_class = ItemSerializer view = create_view( View, @@ -224,23 +253,24 @@ class View(generics.GenericAPIView): inspector = AutoSchema() inspector.view = view - request_body = inspector._get_request_body(path, method) + components = inspector.get_components(path, method) + component = components['Item'] # there should be no empty 'required' property, see #6834 - assert 'required' not in request_body['content']['application/json']['schema'] + assert 'required' not in component for response in inspector._get_responses(path, method).values(): - assert 'required' not in response['content']['application/json']['schema'] + assert 'required' not in component def test_empty_required_with_patch_method(self): path = '/' method = 'PATCH' - class Serializer(serializers.Serializer): + class ItemSerializer(serializers.Serializer): read_only = serializers.CharField(read_only=True) write_only = serializers.CharField(write_only=True, required=False) class View(generics.GenericAPIView): - serializer_class = Serializer + serializer_class = ItemSerializer view = create_view( View, @@ -250,22 +280,23 @@ class View(generics.GenericAPIView): inspector = AutoSchema() inspector.view = view - request_body = inspector._get_request_body(path, method) + components = inspector.get_components(path, method) + component = components['Item'] # there should be no empty 'required' property, see #6834 - assert 'required' not in request_body['content']['application/json']['schema'] + assert 'required' not in component for response in inspector._get_responses(path, method).values(): - assert 'required' not in response['content']['application/json']['schema'] + assert 'required' not in component def test_response_body_generation(self): path = '/' method = 'POST' - class Serializer(serializers.Serializer): + class ItemSerializer(serializers.Serializer): text = serializers.CharField() write_only = serializers.CharField(write_only=True) class View(generics.GenericAPIView): - serializer_class = Serializer + serializer_class = ItemSerializer view = create_view( View, @@ -276,9 +307,11 @@ class View(generics.GenericAPIView): inspector.view = view responses = inspector._get_responses(path, method) - assert '201' in responses - assert responses['201']['content']['application/json']['schema']['required'] == ['text'] - assert list(responses['201']['content']['application/json']['schema']['properties'].keys()) == ['text'] + assert responses['201']['content']['application/json']['schema']['$ref'] == '#/components/schemas/Item' + + components = inspector.get_components(path, method) + assert sorted(components['Item']['required']) == ['text', 'write_only'] + assert sorted(list(components['Item']['properties'].keys())) == ['text', 'write_only'] assert 'description' in responses['201'] def test_response_body_nested_serializer(self): @@ -288,12 +321,12 @@ def test_response_body_nested_serializer(self): class NestedSerializer(serializers.Serializer): number = serializers.IntegerField() - class Serializer(serializers.Serializer): + class ItemSerializer(serializers.Serializer): text = serializers.CharField() nested = NestedSerializer() class View(generics.GenericAPIView): - serializer_class = Serializer + serializer_class = ItemSerializer view = create_view( View, @@ -304,7 +337,11 @@ class View(generics.GenericAPIView): inspector.view = view responses = inspector._get_responses(path, method) - schema = responses['201']['content']['application/json']['schema'] + assert responses['201']['content']['application/json']['schema']['$ref'] == '#/components/schemas/Item' + components = inspector.get_components(path, method) + assert components['Item'] + + schema = components['Item'] assert sorted(schema['required']) == ['nested', 'text'] assert sorted(list(schema['properties'].keys())) == ['nested', 'text'] assert schema['properties']['nested']['type'] == 'object' @@ -339,19 +376,25 @@ class View(generics.GenericAPIView): 'schema': { 'type': 'array', 'items': { - 'type': 'object', - 'properties': { - 'text': { - 'type': 'string', - }, - }, - 'required': ['text'], + '$ref': '#/components/schemas/Item' }, }, }, }, }, } + components = inspector.get_components(path, method) + assert components == { + 'Item': { + 'type': 'object', + 'properties': { + 'text': { + 'type': 'string', + }, + }, + 'required': ['text'], + } + } def test_paginated_list_response_body_generation(self): """Test that pagination properties are added for a paginated list view.""" @@ -391,13 +434,7 @@ class View(generics.GenericAPIView): 'item': { 'type': 'array', 'items': { - 'type': 'object', - 'properties': { - 'text': { - 'type': 'string', - }, - }, - 'required': ['text'], + '$ref': '#/components/schemas/Item' }, }, }, @@ -405,6 +442,18 @@ class View(generics.GenericAPIView): }, }, } + components = inspector.get_components(path, method) + assert components == { + 'Item': { + 'type': 'object', + 'properties': { + 'text': { + 'type': 'string', + }, + }, + 'required': ['text'], + } + } def test_delete_response_body_generation(self): """Test that a view's delete method generates a proper response body schema.""" @@ -508,10 +557,10 @@ class View(generics.CreateAPIView): inspector = AutoSchema() inspector.view = view - request_body = inspector._get_request_body(path, method) - mp_media = request_body['content']['multipart/form-data'] - attachment = mp_media['schema']['properties']['attachment'] - assert attachment['format'] == 'binary' + components = inspector.get_components(path, method) + component = components['Item'] + properties = component['properties'] + assert properties['attachment']['format'] == 'binary' def test_retrieve_response_body_generation(self): """ @@ -551,19 +600,26 @@ class View(generics.GenericAPIView): 'content': { 'application/json': { 'schema': { - 'type': 'object', - 'properties': { - 'text': { - 'type': 'string', - }, - }, - 'required': ['text'], + '$ref': '#/components/schemas/Item' }, }, }, }, } + components = inspector.get_components(path, method) + assert components == { + 'Item': { + 'type': 'object', + 'properties': { + 'text': { + 'type': 'string', + }, + }, + 'required': ['text'], + } + } + def test_operation_id_generation(self): path = '/' method = 'GET' @@ -689,9 +745,9 @@ def test_serializer_datefield(self): inspector = AutoSchema() inspector.view = view - responses = inspector._get_responses(path, method) - response_schema = responses['200']['content']['application/json']['schema'] - properties = response_schema['items']['properties'] + components = inspector.get_components(path, method) + component = components['Example'] + properties = component['properties'] assert properties['date']['type'] == properties['datetime']['type'] == 'string' assert properties['date']['format'] == 'date' assert properties['datetime']['format'] == 'date-time' @@ -707,9 +763,9 @@ def test_serializer_hstorefield(self): inspector = AutoSchema() inspector.view = view - responses = inspector._get_responses(path, method) - response_schema = responses['200']['content']['application/json']['schema'] - properties = response_schema['items']['properties'] + components = inspector.get_components(path, method) + component = components['Example'] + properties = component['properties'] assert properties['hstore']['type'] == 'object' def test_serializer_callable_default(self): @@ -723,9 +779,9 @@ def test_serializer_callable_default(self): inspector = AutoSchema() inspector.view = view - responses = inspector._get_responses(path, method) - response_schema = responses['200']['content']['application/json']['schema'] - properties = response_schema['items']['properties'] + components = inspector.get_components(path, method) + component = components['Example'] + properties = component['properties'] assert 'default' not in properties['uuid_field'] def test_serializer_validators(self): @@ -739,9 +795,9 @@ def test_serializer_validators(self): inspector = AutoSchema() inspector.view = view - responses = inspector._get_responses(path, method) - response_schema = responses['200']['content']['application/json']['schema'] - properties = response_schema['items']['properties'] + components = inspector.get_components(path, method) + component = components['ExampleValidated'] + properties = component['properties'] assert properties['integer']['type'] == 'integer' assert properties['integer']['maximum'] == 99 @@ -819,6 +875,7 @@ class ExampleStringTagsViewSet(views.ExampleGenericViewSet): def test_auto_generated_apiview_tags(self): class RestaurantAPIView(views.ExampleGenericAPIView): + schema = AutoSchema(operation_id_base="restaurant") pass class BranchAPIView(views.ExampleGenericAPIView): @@ -932,3 +989,54 @@ def test_schema_information_empty(self): assert schema['info']['title'] == '' assert schema['info']['version'] == '' + + def test_serializer_model(self): + """Construction of the top level dictionary.""" + patterns = [ + url(r'^example/?$', views.ExampleGenericAPIViewModel.as_view()), + ] + generator = SchemaGenerator(patterns=patterns) + + request = create_request('/') + schema = generator.get_schema(request=request) + + print(schema) + assert 'components' in schema + assert 'schemas' in schema['components'] + assert 'ExampleModel' in schema['components']['schemas'] + + def test_component_name(self): + patterns = [ + url(r'^example/?$', views.ExampleAutoSchemaComponentName.as_view()), + ] + + generator = SchemaGenerator(patterns=patterns) + + request = create_request('/') + schema = generator.get_schema(request=request) + + print(schema) + assert 'components' in schema + assert 'schemas' in schema['components'] + assert 'Ulysses' in schema['components']['schemas'] + + def test_duplicate_component_name(self): + patterns = [ + url(r'^duplicate1/?$', views.ExampleAutoSchemaDuplicate1.as_view()), + url(r'^duplicate2/?$', views.ExampleAutoSchemaDuplicate2.as_view()), + ] + + generator = SchemaGenerator(patterns=patterns) + request = create_request('/') + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + schema = generator.get_schema(request=request) + + assert len(w) == 1 + assert issubclass(w[-1].category, UserWarning) + assert 'has been overriden with a different value.' in str(w[-1].message) + + assert 'components' in schema + assert 'schemas' in schema['components'] + assert 'Duplicate' in schema['components']['schemas'] diff --git a/tests/schemas/views.py b/tests/schemas/views.py index 5835a55727..1c8235b425 100644 --- a/tests/schemas/views.py +++ b/tests/schemas/views.py @@ -9,6 +9,7 @@ from rest_framework import generics, permissions, serializers from rest_framework.decorators import action from rest_framework.response import Response +from rest_framework.schemas.openapi import AutoSchema from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet @@ -167,3 +168,50 @@ class ExampleOperationIdDuplicate2(generics.GenericAPIView): def get(self, *args, **kwargs): pass + + +class ExampleGenericAPIViewModel(generics.GenericAPIView): + serializer_class = ExampleSerializerModel + + def get(self, *args, **kwargs): + from datetime import datetime + now = datetime.now() + + serializer = self.get_serializer(data=now.date(), datetime=now) + return Response(serializer.data) + + +class ExampleAutoSchemaComponentName(generics.GenericAPIView): + serializer_class = ExampleSerializerModel + schema = AutoSchema(component_name="Ulysses") + + def get(self, *args, **kwargs): + from datetime import datetime + now = datetime.now() + + serializer = self.get_serializer(data=now.date(), datetime=now) + return Response(serializer.data) + + +class ExampleAutoSchemaDuplicate1(generics.GenericAPIView): + serializer_class = ExampleValidatedSerializer + schema = AutoSchema(component_name="Duplicate") + + def get(self, *args, **kwargs): + from datetime import datetime + now = datetime.now() + + serializer = self.get_serializer(data=now.date(), datetime=now) + return Response(serializer.data) + + +class ExampleAutoSchemaDuplicate2(generics.GenericAPIView): + serializer_class = ExampleSerializerModel + schema = AutoSchema(component_name="Duplicate") + + def get(self, *args, **kwargs): + from datetime import datetime + now = datetime.now() + + serializer = self.get_serializer(data=now.date(), datetime=now) + return Response(serializer.data) From 609f708a27bd38496b912c44742287c57e7af912 Mon Sep 17 00:00:00 2001 From: Martin Desrumaux <9059840+gnuletik@users.noreply.github.com> Date: Tue, 3 Mar 2020 13:27:34 +0100 Subject: [PATCH 260/907] Fix schema generation for ObtainAuthToken view. (#7211) --- rest_framework/authtoken/serializers.py | 12 ++++++++-- rest_framework/authtoken/views.py | 18 ++++++++++++--- tests/schemas/test_openapi.py | 30 +++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/rest_framework/authtoken/serializers.py b/rest_framework/authtoken/serializers.py index bb552f3e5b..63e64d6683 100644 --- a/rest_framework/authtoken/serializers.py +++ b/rest_framework/authtoken/serializers.py @@ -5,11 +5,19 @@ class AuthTokenSerializer(serializers.Serializer): - username = serializers.CharField(label=_("Username")) + username = serializers.CharField( + label=_("Username"), + write_only=True + ) password = serializers.CharField( label=_("Password"), style={'input_type': 'password'}, - trim_whitespace=False + trim_whitespace=False, + write_only=True + ) + token = serializers.CharField( + label=_("Token"), + read_only=True ) def validate(self, attrs): diff --git a/rest_framework/authtoken/views.py b/rest_framework/authtoken/views.py index a8c751d51d..50f9acbd90 100644 --- a/rest_framework/authtoken/views.py +++ b/rest_framework/authtoken/views.py @@ -4,6 +4,7 @@ from rest_framework.compat import coreapi, coreschema from rest_framework.response import Response from rest_framework.schemas import ManualSchema +from rest_framework.schemas import coreapi as coreapi_schema from rest_framework.views import APIView @@ -13,7 +14,8 @@ class ObtainAuthToken(APIView): parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,) renderer_classes = (renderers.JSONRenderer,) serializer_class = AuthTokenSerializer - if coreapi is not None and coreschema is not None: + + if coreapi_schema.is_enabled(): schema = ManualSchema( fields=[ coreapi.Field( @@ -38,9 +40,19 @@ class ObtainAuthToken(APIView): encoding="application/json", ) + def get_serializer_context(self): + return { + 'request': self.request, + 'format': self.format_kwarg, + 'view': self + } + + def get_serializer(self, *args, **kwargs): + kwargs['context'] = self.get_serializer_context() + return self.serializer_class(*args, **kwargs) + def post(self, request, *args, **kwargs): - serializer = self.serializer_class(data=request.data, - context={'request': request}) + serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) user = serializer.validated_data['user'] token, created = Token.objects.get_or_create(user=user) diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 95101403a3..35d676d6c3 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import filters, generics, pagination, routers, serializers +from rest_framework.authtoken.views import obtain_auth_token from rest_framework.compat import uritemplate from rest_framework.parsers import JSONParser, MultiPartParser from rest_framework.renderers import JSONRenderer, OpenAPIRenderer @@ -995,16 +996,45 @@ def test_serializer_model(self): patterns = [ url(r'^example/?$', views.ExampleGenericAPIViewModel.as_view()), ] + generator = SchemaGenerator(patterns=patterns) request = create_request('/') schema = generator.get_schema(request=request) print(schema) + assert 'components' in schema assert 'schemas' in schema['components'] assert 'ExampleModel' in schema['components']['schemas'] + def test_authtoken_serializer(self): + patterns = [ + url(r'^api-token-auth/', obtain_auth_token) + ] + generator = SchemaGenerator(patterns=patterns) + + request = create_request('/') + schema = generator.get_schema(request=request) + + print(schema) + + route = schema['paths']['/api-token-auth/']['post'] + body_schema = route['requestBody']['content']['application/json']['schema'] + + assert body_schema == { + '$ref': '#/components/schemas/AuthToken' + } + assert schema['components']['schemas']['AuthToken'] == { + 'type': 'object', + 'properties': { + 'username': {'type': 'string', 'writeOnly': True}, + 'password': {'type': 'string', 'writeOnly': True}, + 'token': {'type': 'string', 'readOnly': True}, + }, + 'required': ['username', 'password'] + } + def test_component_name(self): patterns = [ url(r'^example/?$', views.ExampleAutoSchemaComponentName.as_view()), From 6a23fa06495c6010239dda926f55f2d6baa79cf3 Mon Sep 17 00:00:00 2001 From: Martin Desrumaux <9059840+gnuletik@users.noreply.github.com> Date: Tue, 3 Mar 2020 17:51:51 +0100 Subject: [PATCH 261/907] OpenAPI: Make operationId camelCase, matching spec examples. (#7208) --- docs/api-guide/schemas.md | 5 +++-- rest_framework/schemas/openapi.py | 18 ++++++++++++------ tests/schemas/test_openapi.py | 19 ++++++++++++++++++- tests/schemas/views.py | 24 +++++++++++++++++++++++- 4 files changed, 56 insertions(+), 10 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 1d1e09b46a..2d74882ade 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -290,7 +290,8 @@ class MyView(APIView): ### OperationId -The schema generator generates an [operationid](openapi-operationid) for each operation. This `operationId` is deduced from the model name, serializer name or view name. The operationId may looks like "ListItems", "RetrieveItem", "UpdateItem", etc.. +The schema generator generates an [operationid](openapi-operationid) for each operation. This `operationId` is deduced from the model name, serializer name or view name. The operationId may looks like "listItems", "retrieveItem", "updateItem", etc.. +The `operationId` is camelCase by convention. If you have several views with the same model, the generator may generate duplicate operationId. In order to work around this, you can override the second part of the operationId: operation name. @@ -303,7 +304,7 @@ class ExampleView(APIView): schema = AutoSchema(operation_id_base="Custom") ``` -The previous example will generate the following operationId: "ListCustoms", "RetrieveCustom", "UpdateCustom", "PartialUpdateCustom", "DestroyCustom". +The previous example will generate the following operationId: "listCustoms", "retrieveCustom", "updateCustom", "partialUpdateCustom", "destroyCustom". You need to provide the singular form of he operation name. For the list operation, a "s" will be appended at the end of the operation. If you need more configuration over the `operationId` field, you can override the `get_operation_id_base` and `get_operation_id` methods from the `AutoSchema` class: diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 6bed120922..1d0ec35d54 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -131,11 +131,11 @@ def __init__(self, tags=None, operation_id_base=None, component_name=None): response_media_types = [] method_mapping = { - 'get': 'Retrieve', - 'post': 'Create', - 'put': 'Update', - 'patch': 'PartialUpdate', - 'delete': 'Destroy', + 'get': 'retrieve', + 'post': 'create', + 'put': 'update', + 'patch': 'partialUpdate', + 'delete': 'destroy', } def get_operation(self, path, method): @@ -195,6 +195,12 @@ def get_components(self, path, method): content = self._map_serializer(serializer) return {component_name: content} + def _to_camel_case(self, snake_str): + components = snake_str.split('_') + # We capitalize the first letter of each component except the first one + # with the 'title' method and join them together. + return components[0] + ''.join(x.title() for x in components[1:]) + def get_operation_id_base(self, path, method, action): """ Compute the base part for operation ID from the model, serializer or view name. @@ -240,7 +246,7 @@ def get_operation_id(self, path, method): if is_list_view(path, method, self.view): action = 'list' elif method_name not in self.method_mapping: - action = method_name + action = self._to_camel_case(method_name) else: action = self.method_mapping[method.lower()] diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 35d676d6c3..c9f6d967ec 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -158,7 +158,7 @@ def test_path_with_id_parameter(self): operation = inspector.get_operation(path, method) assert operation == { - 'operationId': 'RetrieveDocStringExampleDetail', + 'operationId': 'retrieveDocStringExampleDetail', 'description': 'A description of my GET operation.', 'parameters': [{ 'description': '', @@ -735,6 +735,23 @@ def test_duplicate_operation_id(self): print(str(w[-1].message)) assert 'You have a duplicated operationId' in str(w[-1].message) + def test_operation_id_viewset(self): + router = routers.SimpleRouter() + router.register('account', views.ExampleViewSet, basename="account") + urlpatterns = router.urls + + generator = SchemaGenerator(patterns=urlpatterns) + + request = create_request('/') + schema = generator.get_schema(request=request) + print(schema) + assert schema['paths']['/account/']['get']['operationId'] == 'listExampleViewSets' + assert schema['paths']['/account/']['post']['operationId'] == 'createExampleViewSet' + assert schema['paths']['/account/{id}/']['get']['operationId'] == 'retrieveExampleViewSet' + assert schema['paths']['/account/{id}/']['put']['operationId'] == 'updateExampleViewSet' + assert schema['paths']['/account/{id}/']['patch']['operationId'] == 'partialUpdateExampleViewSet' + assert schema['paths']['/account/{id}/']['delete']['operationId'] == 'destroyExampleViewSet' + def test_serializer_datefield(self): path = '/' method = 'GET' diff --git a/tests/schemas/views.py b/tests/schemas/views.py index 1c8235b425..5645f59bf5 100644 --- a/tests/schemas/views.py +++ b/tests/schemas/views.py @@ -11,7 +11,7 @@ from rest_framework.response import Response from rest_framework.schemas.openapi import AutoSchema from rest_framework.views import APIView -from rest_framework.viewsets import GenericViewSet +from rest_framework.viewsets import GenericViewSet, ViewSet class ExampleListView(APIView): @@ -215,3 +215,25 @@ def get(self, *args, **kwargs): serializer = self.get_serializer(data=now.date(), datetime=now) return Response(serializer.data) + + +class ExampleViewSet(ViewSet): + serializer_class = ExampleSerializerModel + + def list(self, request): + pass + + def create(self, request): + pass + + def retrieve(self, request, pk=None): + pass + + def update(self, request, pk=None): + pass + + def partial_update(self, request, pk=None): + pass + + def destroy(self, request, pk=None): + pass From ddfb9672ae703ce15392072dd110415147b5171a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 4 Mar 2020 13:31:43 +0000 Subject: [PATCH 262/907] Release notes for 3.11.0 (#7214) --- docs/community/release-notes.md | 41 ++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 4be05d56b1..97ec774fe0 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -34,10 +34,32 @@ You can determine your currently installed version using `pip show`: --- +## 3.11.x series + +### 3.11.0 + +**Date**: 12th December 2019 + +* Drop `.set_context` API [in favour of a `requires_context` marker](../3.11-announcement#validator-default-context). +* Changed default widget for TextField with choices to select box. [#6892][gh6892] +* Supported nested writes on non-relational fields, such as JSONField. [#6916][gh6916] +* Include request/response media types in OpenAPI schemas, based on configured parsers/renderers. [#6865][gh6865] +* Include operation descriptions in OpenAPI schemas, based on the docstring on the view. [#6898][gh6898] +* Fix representation of serializers with all optional fields in OpenAPI schemas. [#6941][gh6941], [#6944][gh6944] +* Fix representation of `serializers.HStoreField` in OpenAPI schemas. [#6914][gh6914] +* Fix OpenAPI generation when title or version is not provided. [#6912][gh6912] +* Use `int64` representation for large integers in OpenAPI schemas. [#7018][gh7018] +* Improved error messages if no `.to_representation` implementation is provided on a field subclass. [#6996][gh6996] +* Fix for serializer classes that use multiple inheritance. [#6980][gh6980] +* Fix for reversing Hyperlinked URL fields with percent encoded components in the path. [#7059][gh7059] +* Update bootstrap to 3.4.1. [#6923][gh6923] + ## 3.10.x series ### 3.10.3 +**Date**: 4th September 2019 + * Include API version in OpenAPI schema generation, defaulting to empty string. * Add pagination properties to OpenAPI response schemas. * Add missing "description" property to OpenAPI response schemas. @@ -47,9 +69,7 @@ You can determine your currently installed version using `pip show`: * Use consistent `lowerInitialCamelCase` style in OpenAPI operation IDs. * Fix `minLength`/`maxLength`/`minItems`/`maxItems` properties in OpenAPI schemas. * Only call `FileField.url` once in serialization, for improved performance. -* Fix an edge case where throttling calcualtions could error after a configuration change. - -* TODO +* Fix an edge case where throttling calculations could error after a configuration change. ### 3.10.2 @@ -2175,3 +2195,18 @@ For older release notes, [please see the version 2.x documentation][old-release- [gh6680]: https://github.com/encode/django-rest-framework/issues/6680 [gh6317]: https://github.com/encode/django-rest-framework/issues/6317 + + +[gh6892]: https://github.com/encode/django-rest-framework/issues/6892 +[gh6916]: https://github.com/encode/django-rest-framework/issues/6916 +[gh6865]: https://github.com/encode/django-rest-framework/issues/6865 +[gh6898]: https://github.com/encode/django-rest-framework/issues/6898 +[gh6941]: https://github.com/encode/django-rest-framework/issues/6941 +[gh6944]: https://github.com/encode/django-rest-framework/issues/6944 +[gh6914]: https://github.com/encode/django-rest-framework/issues/6914 +[gh6912]: https://github.com/encode/django-rest-framework/issues/6912 +[gh7018]: https://github.com/encode/django-rest-framework/issues/7018 +[gh6996]: https://github.com/encode/django-rest-framework/issues/6996 +[gh6980]: https://github.com/encode/django-rest-framework/issues/6980 +[gh7059]: https://github.com/encode/django-rest-framework/issues/7059 +[gh6923]: https://github.com/encode/django-rest-framework/issues/6923 From 73f7bf49417dec903ec913105d2b5b1601e2f420 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 5 Mar 2020 10:18:22 +0000 Subject: [PATCH 263/907] Extra action detection is too permissive. Add failing test + fix (#7217) * Add failing test * Add failing test++ * Make get_extra_action less permissive --- rest_framework/viewsets.py | 3 ++- tests/test_viewsets.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index d94c81df4d..244c14d396 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -25,11 +25,12 @@ from django.views.decorators.csrf import csrf_exempt from rest_framework import generics, mixins, views +from rest_framework.decorators import MethodMapper from rest_framework.reverse import reverse def _is_extra_action(attr): - return hasattr(attr, 'mapping') + return hasattr(attr, 'mapping') and isinstance(attr.mapping, MethodMapper) class ViewSetMixin: diff --git a/tests/test_viewsets.py b/tests/test_viewsets.py index eac36f0959..921daf1865 100644 --- a/tests/test_viewsets.py +++ b/tests/test_viewsets.py @@ -81,10 +81,20 @@ def suffixed_action(self, request, *args, **kwargs): raise NotImplementedError +class ThingWithMapping: + def __init__(self): + self.mapping = {} + + +class ActionViewSetWithMapping(ActionViewSet): + mapper = ThingWithMapping() + + router = SimpleRouter() router.register(r'actions', ActionViewSet) router.register(r'actions-alt', ActionViewSet, basename='actions-alt') router.register(r'names', ActionNamesViewSet, basename='names') +router.register(r'mapping', ActionViewSetWithMapping, basename='mapping') urlpatterns = [ @@ -161,6 +171,18 @@ def test_extra_actions(self): self.assertEqual(actual, expected) + def test_should_only_return_decorated_methods(self): + view = ActionViewSetWithMapping() + actual = [action.__name__ for action in view.get_extra_actions()] + expected = [ + 'custom_detail_action', + 'custom_list_action', + 'detail_action', + 'list_action', + 'unresolvable_detail_action', + ] + self.assertEqual(actual, expected) + @override_settings(ROOT_URLCONF='tests.test_viewsets') class GetExtraActionUrlMapTests(TestCase): From 4a98533746db44c997882ba01d5515a29a61dcc3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 5 Mar 2020 13:18:48 +0000 Subject: [PATCH 264/907] Fix - run test_head_request_against_viewset method (#7219) --- tests/test_viewsets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_viewsets.py b/tests/test_viewsets.py index 921daf1865..f9468f4484 100644 --- a/tests/test_viewsets.py +++ b/tests/test_viewsets.py @@ -113,7 +113,7 @@ def test_initialize_view_set_with_actions(self): assert response.status_code == status.HTTP_200_OK assert response.data == {'ACTION': 'LIST'} - def testhead_request_against_viewset(self): + def test_head_request_against_viewset(self): request = factory.head('/', '', content_type='application/json') my_view = BasicViewSet.as_view(actions={ 'get': 'list', From 908f91d8ef13649b6d658981e28ff52296b19f9f Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Mon, 9 Mar 2020 02:43:02 -0700 Subject: [PATCH 265/907] Set action for HEAD requests (#7223) * Test viewset action attr * Add 'head' to viewset actions map --- rest_framework/viewsets.py | 7 ++++--- tests/test_viewsets.py | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index 244c14d396..cad032dd91 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -93,6 +93,10 @@ def as_view(cls, actions=None, **initkwargs): def view(request, *args, **kwargs): self = cls(**initkwargs) + + if 'get' in actions and 'head' not in actions: + actions['head'] = actions['get'] + # We also store the mapping of request methods to actions, # so that we can later set the action attribute. # eg. `self.action = 'list'` on an incoming GET request. @@ -104,9 +108,6 @@ def view(request, *args, **kwargs): handler = getattr(self, action) setattr(self, method, handler) - if hasattr(self, 'get') and not hasattr(self, 'head'): - self.head = self.get - self.request = request self.args = args self.kwargs = kwargs diff --git a/tests/test_viewsets.py b/tests/test_viewsets.py index f9468f4484..1a621c5183 100644 --- a/tests/test_viewsets.py +++ b/tests/test_viewsets.py @@ -37,14 +37,18 @@ class ActionViewSet(GenericViewSet): queryset = Action.objects.all() def list(self, request, *args, **kwargs): - return Response() + response = Response() + response.view = self + return response def retrieve(self, request, *args, **kwargs): return Response() @action(detail=False) def list_action(self, request, *args, **kwargs): - raise NotImplementedError + response = Response() + response.view = self + return response @action(detail=False, url_name='list-custom') def custom_list_action(self, request, *args, **kwargs): @@ -155,6 +159,22 @@ def test_args_kwargs_request_action_map_on_self(self): self.assertNotIn(attribute, dir(bare_view)) self.assertIn(attribute, dir(view)) + def test_viewset_action_attr(self): + view = ActionViewSet.as_view(actions={'get': 'list'}) + + get = view(factory.get('/')) + head = view(factory.head('/')) + assert get.view.action == 'list' + assert head.view.action == 'list' + + def test_viewset_action_attr_for_extra_action(self): + view = ActionViewSet.as_view(actions=dict(ActionViewSet.list_action.mapping)) + + get = view(factory.get('/')) + head = view(factory.head('/')) + assert get.view.action == 'list_action' + assert head.view.action == 'list_action' + class GetExtraActionsTests(TestCase): From 86aa549832b9468d14a9f66370cf15d6dbcb4bfa Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Wed, 11 Mar 2020 06:51:42 -0700 Subject: [PATCH 266/907] Drop Django 2.1 and below. (#7225) --- .travis.yml | 8 -------- README.md | 2 +- setup.py | 6 ++---- tox.ini | 11 +---------- 4 files changed, 4 insertions(+), 23 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7266df2d5e..2cdeee5b8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,20 +5,12 @@ matrix: fast_finish: true include: - - { 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=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=3.0 } - { python: "3.6", env: DJANGO=master } - - { 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=3.0 } - { python: "3.7", env: DJANGO=master } diff --git a/README.md b/README.md index 9591bdc17b..41a344fcf7 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements * Python (3.5, 3.6, 3.7, 3.8) -* Django (1.11, 2.0, 2.1, 2.2, 3.0) +* Django (2.2, 3.0) We **highly recommend** and only officially support the latest patch release of each Python and Django series. diff --git a/setup.py b/setup.py index 65536885a7..99826b4d0f 100755 --- a/setup.py +++ b/setup.py @@ -82,17 +82,15 @@ def get_version(package): author_email='tom@tomchristie.com', # SEE NOTE BELOW (*) packages=find_packages(exclude=['tests*']), include_package_data=True, - install_requires=["django>=1.11"], + install_requires=["django>=2.2"], python_requires=">=3.5", zip_safe=False, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Environment :: Web Environment', 'Framework :: Django', - 'Framework :: Django :: 1.11', - 'Framework :: Django :: 2.0', - 'Framework :: Django :: 2.1', 'Framework :: Django :: 2.2', + 'Framework :: Django :: 3.0', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', diff --git a/tox.ini b/tox.ini index 9b80691745..e5b8b64020 100644 --- a/tox.ini +++ b/tox.ini @@ -1,18 +1,12 @@ [tox] envlist = - {py35,py36}-django111, - {py35,py36,py37}-django20, - {py35,py36,py37}-django21 - {py35,py36,py37}-django22 + {py35,py36,py37}-django22, {py36,py37,py38}-django30, {py36,py37,py38}-djangomaster, base,dist,lint,docs, [travis:env] DJANGO = - 1.11: django111 - 2.0: django20 - 2.1: django21 2.2: django22 3.0: django30 master: djangomaster @@ -24,9 +18,6 @@ setenv = PYTHONDONTWRITEBYTECODE=1 PYTHONWARNINGS=once deps = - django111: Django>=1.11,<2.0 - django20: Django>=2.0,<2.1 - django21: Django>=2.1,<2.2 django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 djangomaster: https://github.com/django/django/archive/master.tar.gz From be96939ec1482ce3453fb210460ab795f7704b4a Mon Sep 17 00:00:00 2001 From: 0dysseas <31179964+0dysseas@users.noreply.github.com> Date: Tue, 17 Mar 2020 18:49:19 +0200 Subject: [PATCH 267/907] Fix serializer example in docs (#7233) --- docs/api-guide/serializers.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 5cf949f979..87d3d4056a 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -238,10 +238,12 @@ Serializer classes can also include reusable validators that are applied to the class Meta: # Each room only has one event per day. - validators = UniqueTogetherValidator( - queryset=Event.objects.all(), - fields=['room_number', 'date'] - ) + validators = [ + UniqueTogetherValidator( + queryset=Event.objects.all(), + fields=['room_number', 'date'] + ) + ] For more information see the [validators documentation](validators.md). From 8b5d3437f9146401813b72cece95e4f746c8e067 Mon Sep 17 00:00:00 2001 From: Mahmoud Adel <20120831@std.sci.cu.edu.eg> Date: Wed, 18 Mar 2020 00:45:45 +0200 Subject: [PATCH 268/907] Add django-rest-auth fork to docs (#7227) --- docs/api-guide/authentication.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index c4dbe8856f..ba8a650692 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -410,9 +410,15 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a [Djoser][djoser] library provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. The package works with a custom user model and it uses token based authentication. This is a ready to use REST implementation of Django authentication system. -## django-rest-auth +## django-rest-auth / dj-rest-auth -[Django-rest-auth][django-rest-auth] library provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc. By having these API endpoints, your client apps such as AngularJS, iOS, Android, and others can communicate to your Django backend site independently via REST APIs for user management. +This library provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc. By having these API endpoints, your client apps such as AngularJS, iOS, Android, and others can communicate to your Django backend site independently via REST APIs for user management. + + +There are currently two forks of this project. + +* [Django-rest-auth][django-rest-auth] is the original project, [but is not currently receiving updates](https://github.com/Tivix/django-rest-auth/issues/568). +* [Dj-rest-auth][dj-rest-auth] is a newer fork of the project. ## django-rest-framework-social-oauth2 @@ -456,6 +462,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a [mac]: https://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05 [djoser]: https://github.com/sunscrapers/djoser [django-rest-auth]: https://github.com/Tivix/django-rest-auth +[dj-rest-auth]: https://github.com/jazzband/dj-rest-auth [django-rest-framework-social-oauth2]: https://github.com/PhilipGarnero/django-rest-framework-social-oauth2 [django-rest-knox]: https://github.com/James1345/django-rest-knox [drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless From 5cc6ace9c45ac42cf59d52643ab9cbb6c565d23e Mon Sep 17 00:00:00 2001 From: Artur Barseghyan Date: Fri, 20 Mar 2020 19:28:51 +0100 Subject: [PATCH 269/907] Update third-party-packages.md (#7175) --- docs/community/third-party-packages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index baa30fd0c8..9fce55e947 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -272,6 +272,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [djangorestframework-mvt][djangorestframework-mvt] - An extension for creating views that serve Postgres data as Map Box Vector Tiles. * [drf-viewset-profiler][drf-viewset-profiler] - Lib to profile all methods from a viewset line by line. * [djangorestframework-features][djangorestframework-features] - Advanced schema generation and more based on named features. +* [django-elasticsearch-dsl-drf][django-elasticsearch-dsl-drf] - Integrate Elasticsearch DSL with Django REST framework. Package provides views, serializers, filter backends, pagination and other handy add-ons. [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework @@ -354,3 +355,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian [drf-viewset-profiler]: https://github.com/fvlima/drf-viewset-profiler [djangorestframework-features]: https://github.com/cloudcode-hungary/django-rest-framework-features/ +[django-elasticsearch-dsl-drf]: https://github.com/barseghyanartur/django-elasticsearch-dsl-drf From 57e7cc21e1b73ccf49c7b9c20a6d3f578c125f20 Mon Sep 17 00:00:00 2001 From: Rotzbua Date: Tue, 24 Mar 2020 19:52:17 +0100 Subject: [PATCH 270/907] Remove unavailable script (#7244) --- docs_theme/main.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs_theme/main.html b/docs_theme/main.html index c2a29e1aeb..b4e894781c 100644 --- a/docs_theme/main.html +++ b/docs_theme/main.html @@ -17,10 +17,6 @@ - - - + diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 5d9d80b057..9207f049b1 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -293,7 +293,7 @@

    {{ name }}

    csrfToken: "{% if request %}{{ csrf_token }}{% endif %}" }; - + diff --git a/rest_framework/templates/rest_framework/docs/error.html b/rest_framework/templates/rest_framework/docs/error.html index 6afd25e7be..6afc4a88b6 100644 --- a/rest_framework/templates/rest_framework/docs/error.html +++ b/rest_framework/templates/rest_framework/docs/error.html @@ -66,6 +66,6 @@

    Overriding this template

    - + diff --git a/rest_framework/templates/rest_framework/docs/index.html b/rest_framework/templates/rest_framework/docs/index.html index 6804afe10b..dfd363772a 100644 --- a/rest_framework/templates/rest_framework/docs/index.html +++ b/rest_framework/templates/rest_framework/docs/index.html @@ -38,7 +38,7 @@ {% include "rest_framework/docs/auth/basic.html" %} {% include "rest_framework/docs/auth/session.html" %} - + From 00e6079e94a4937c89ec68a5ba31658827cf4708 Mon Sep 17 00:00:00 2001 From: Derek Date: Mon, 11 May 2020 09:29:31 -0600 Subject: [PATCH 303/907] Nginx basic auth tutorial moved (#7324) --- docs/api-guide/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index ba8a650692..ebb0ab4d69 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -304,7 +304,7 @@ If successfully authenticated, `RemoteUserAuthentication` provides the following Consult your web server's documentation for information about configuring an authentication method, e.g.: * [Apache Authentication How-To](https://httpd.apache.org/docs/2.4/howto/auth.html) -* [NGINX (Restricting Access)](https://www.nginx.com/resources/admin-guide/#restricting_access) +* [NGINX (Restricting Access)](https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/) # Custom authentication From 089162e6e319a1c35f60398319cf70a13d404fa5 Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Wed, 13 May 2020 03:11:26 -0700 Subject: [PATCH 304/907] Fix ModelSerializer unique_together handling for field sources (#7143) * Fix ModelSerializer unique_together field sources Updates ModelSerializer to check for serializer fields that map to the model field sources in the unique_together lists. * Ensure field name ordering consistency --- rest_framework/serializers.py | 51 ++++++++++++++++++++++++++--------- tests/test_validators.py | 43 +++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 8c2486bea7..c1cea1e831 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -13,7 +13,7 @@ import copy import inspect import traceback -from collections import OrderedDict +from collections import OrderedDict, defaultdict from collections.abc import Mapping from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured @@ -1508,28 +1508,55 @@ def get_unique_together_validators(self): # which may map onto a model field. Any dotted field name lookups # cannot map to a field, and must be a traversal, so we're not # including those. - field_names = { - field.source for field in self._writable_fields + field_sources = OrderedDict( + (field.field_name, field.source) for field in self._writable_fields if (field.source != '*') and ('.' not in field.source) - } + ) # Special Case: Add read_only fields with defaults. - field_names |= { - field.source for field in self.fields.values() + field_sources.update(OrderedDict( + (field.field_name, field.source) for field in self.fields.values() if (field.read_only) and (field.default != empty) and (field.source != '*') and ('.' not in field.source) - } + )) + + # Invert so we can find the serializer field names that correspond to + # the model field names in the unique_together sets. This also allows + # us to check that multiple fields don't map to the same source. + source_map = defaultdict(list) + for name, source in field_sources.items(): + source_map[source].append(name) # Note that we make sure to check `unique_together` both on the # base model class, but also on any parent classes. validators = [] for parent_class in model_class_inheritance_tree: for unique_together in parent_class._meta.unique_together: - if field_names.issuperset(set(unique_together)): - validator = UniqueTogetherValidator( - queryset=parent_class._default_manager, - fields=unique_together + # Skip if serializer does not map to all unique together sources + if not set(source_map).issuperset(set(unique_together)): + continue + + for source in unique_together: + assert len(source_map[source]) == 1, ( + "Unable to create `UniqueTogetherValidator` for " + "`{model}.{field}` as `{serializer}` has multiple " + "fields ({fields}) that map to this model field. " + "Either remove the extra fields, or override " + "`Meta.validators` with a `UniqueTogetherValidator` " + "using the desired field names." + .format( + model=self.Meta.model.__name__, + serializer=self.__class__.__name__, + field=source, + fields=', '.join(source_map[source]), + ) ) - validators.append(validator) + + field_names = tuple(source_map[f][0] for f in unique_together) + validator = UniqueTogetherValidator( + queryset=parent_class._default_manager, + fields=field_names + ) + validators.append(validator) return validators def get_unique_for_date_validators(self): diff --git a/tests/test_validators.py b/tests/test_validators.py index 21c00073d6..4962cf5816 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -344,6 +344,49 @@ class Meta: ] } + def test_default_validator_with_fields_with_source(self): + class TestSerializer(serializers.ModelSerializer): + name = serializers.CharField(source='race_name') + + class Meta: + model = UniquenessTogetherModel + fields = ['name', 'position'] + + serializer = TestSerializer() + expected = dedent(""" + TestSerializer(): + name = CharField(source='race_name') + position = IntegerField() + class Meta: + validators = [] + """) + assert repr(serializer) == expected + + def test_default_validator_with_multiple_fields_with_same_source(self): + class TestSerializer(serializers.ModelSerializer): + name = serializers.CharField(source='race_name') + other_name = serializers.CharField(source='race_name') + + class Meta: + model = UniquenessTogetherModel + fields = ['name', 'other_name', 'position'] + + serializer = TestSerializer(data={ + 'name': 'foo', + 'other_name': 'foo', + 'position': 1, + }) + with pytest.raises(AssertionError) as excinfo: + serializer.is_valid() + + expected = ( + "Unable to create `UniqueTogetherValidator` for " + "`UniquenessTogetherModel.race_name` as `TestSerializer` has " + "multiple fields (name, other_name) that map to this model field. " + "Either remove the extra fields, or override `Meta.validators` " + "with a `UniqueTogetherValidator` using the desired field names.") + assert str(excinfo.value) == expected + def test_allow_explict_override(self): """ Ensure validators can be explicitly removed.. From e888fc11c76d1a671f6f7d3c080915ffe722f3f6 Mon Sep 17 00:00:00 2001 From: Kevin Brown Date: Wed, 13 May 2020 09:59:04 -0400 Subject: [PATCH 305/907] Merge NullBooleanField with BooleanField(allow_null=True) (#7122) * Make `NullBooleanField` subclass `BooleanField` This removes a lot of the redundancy that was in place becuase we were not doing this. This maintains the `None` initial value that was previously present, as well as disallowing `allow_null` to be passed in. * Remove special case for mapping `NullBooleanField` In newer versions of Django, the `NullBooleanField` is handled the same way as a `BooleanField(null=True)`. Given that we also support that combination, and that our own `NullBooleanField` behaves in the same manner, it makes sense to remove the special casing that exists for it. * Add test for BooleanField(null=True, choices) * Remove special case for NullBooleanField * Adjust mapping tests for NullBooleanField * Fixed linting error * Raise deprecation warning when NullBooleanField is used * Fix linting issue in imports --- rest_framework/__init__.py | 4 ++ rest_framework/fields.py | 55 ++++++--------------------- rest_framework/serializers.py | 2 +- rest_framework/utils/field_mapping.py | 2 +- tests/test_model_serializer.py | 23 ++++++++++- 5 files changed, 40 insertions(+), 46 deletions(-) diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 8f2bc4466f..84e9a8b866 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -31,3 +31,7 @@ class RemovedInDRF313Warning(DeprecationWarning): class RemovedInDRF314Warning(PendingDeprecationWarning): pass + + +class RemovedInDRF314Warning(PendingDeprecationWarning): + pass diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 958bebeef8..0447eb74db 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -30,7 +30,9 @@ from django.utils.translation import gettext_lazy as _ from pytz.exceptions import InvalidTimeError -from rest_framework import ISO_8601, RemovedInDRF313Warning +from rest_framework import ( + ISO_8601, RemovedInDRF313Warning, RemovedInDRF314Warning +) from rest_framework.compat import ProhibitNullCharactersValidator from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.settings import api_settings @@ -740,54 +742,21 @@ def to_representation(self, value): return bool(value) -class NullBooleanField(Field): - default_error_messages = { - 'invalid': _('Must be a valid boolean.') - } +class NullBooleanField(BooleanField): initial = None - TRUE_VALUES = { - 't', 'T', - 'y', 'Y', 'yes', 'YES', - 'true', 'True', 'TRUE', - 'on', 'On', 'ON', - '1', 1, - True - } - FALSE_VALUES = { - 'f', 'F', - 'n', 'N', 'no', 'NO', - 'false', 'False', 'FALSE', - 'off', 'Off', 'OFF', - '0', 0, 0.0, - False - } - NULL_VALUES = {'null', 'Null', 'NULL', '', None} def __init__(self, **kwargs): + warnings.warn( + "The `NullBooleanField` is deprecated and will be removed starting " + "with 3.14. Instead use the `BooleanField` field and set " + "`null=True` which does the same thing.", + RemovedInDRF314Warning, stacklevel=2 + ) + assert 'allow_null' not in kwargs, '`allow_null` is not a valid option.' kwargs['allow_null'] = True - super().__init__(**kwargs) - def to_internal_value(self, data): - try: - if data in self.TRUE_VALUES: - return True - elif data in self.FALSE_VALUES: - return False - elif data in self.NULL_VALUES: - return None - except TypeError: # Input is an unhashable type - pass - self.fail('invalid', input=data) - - def to_representation(self, value): - if value in self.NULL_VALUES: - return None - if value in self.TRUE_VALUES: - return True - elif value in self.FALSE_VALUES: - return False - return bool(value) + super().__init__(**kwargs) # String types... diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index c1cea1e831..cfb54de138 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -868,7 +868,7 @@ class ModelSerializer(Serializer): models.FloatField: FloatField, models.ImageField: ImageField, models.IntegerField: IntegerField, - models.NullBooleanField: NullBooleanField, + models.NullBooleanField: BooleanField, models.PositiveIntegerField: IntegerField, models.PositiveSmallIntegerField: IntegerField, models.SlugField: SlugField, diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index a25880d0f6..ed270be5e3 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -104,7 +104,7 @@ def get_field_kwargs(field_name, model_field): if model_field.has_default() or model_field.blank or model_field.null: kwargs['required'] = False - if model_field.null and not isinstance(model_field, models.NullBooleanField): + if model_field.null: kwargs['allow_null'] = True if model_field.blank and (isinstance(model_field, (models.CharField, models.TextField))): diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 0de628dc89..51b8f2e22f 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -182,7 +182,7 @@ class Meta: email_field = EmailField(max_length=100) float_field = FloatField() integer_field = IntegerField() - null_boolean_field = NullBooleanField(required=False) + null_boolean_field = BooleanField(allow_null=True, required=False) positive_integer_field = IntegerField() positive_small_integer_field = IntegerField() slug_field = SlugField(allow_unicode=False, max_length=100) @@ -236,6 +236,27 @@ class Meta: self.assertEqual(repr(NullableBooleanSerializer()), expected) + def test_nullable_boolean_field_choices(self): + class NullableBooleanChoicesModel(models.Model): + CHECKLIST_OPTIONS = ( + (None, 'Unknown'), + (True, 'Yes'), + (False, 'No'), + ) + + field = models.BooleanField(null=True, choices=CHECKLIST_OPTIONS) + + class NullableBooleanChoicesSerializer(serializers.ModelSerializer): + class Meta: + model = NullableBooleanChoicesModel + fields = ['field'] + + serializer = NullableBooleanChoicesSerializer(data=dict( + field=None, + )) + self.assertTrue(serializer.is_valid()) + self.assertEqual(serializer.errors, {}) + def test_method_field(self): """ Properties and methods on the model should be allowed as `Meta.fields` From 8bb9a37f4b680a9efb43996705df885dc5b3802e Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 13 May 2020 20:41:53 +0200 Subject: [PATCH 306/907] Removed duplicated class RemovedInDRF314Warning. Added accidently in e888fc11c76d1a671f6f7d3c080915ffe722f3f6 Co-authored-by: Jair Henrique --- rest_framework/__init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 84e9a8b866..8f2bc4466f 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -31,7 +31,3 @@ class RemovedInDRF313Warning(DeprecationWarning): class RemovedInDRF314Warning(PendingDeprecationWarning): pass - - -class RemovedInDRF314Warning(PendingDeprecationWarning): - pass From fccfdd21c078579bb0029db7289d5e19d58201e8 Mon Sep 17 00:00:00 2001 From: johnthagen Date: Wed, 13 May 2020 20:54:46 -0400 Subject: [PATCH 307/907] Remove object inheritance in docs (#7332) --- docs/api-guide/fields.md | 2 +- docs/api-guide/generic-views.md | 2 +- docs/api-guide/serializers.md | 2 +- docs/api-guide/validators.md | 2 +- rest_framework/schemas/generators.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 65c83b78e7..b2bdd50c8e 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -603,7 +603,7 @@ The `to_internal_value()` method is called to restore a primitive datatype into Let's look at an example of serializing a class that represents an RGB color value: - class Color(object): + class Color: """ A color represented in the RGB colorspace. """ diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index a256eb2d96..4ff549f078 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -319,7 +319,7 @@ Often you'll want to use the existing generic views, but use some slightly custo For example, if you need to lookup objects based on multiple fields in the URL conf, you could create a mixin class like the following: - class MultipleFieldLookupMixin(object): + class MultipleFieldLookupMixin: """ Apply this mixin to any view or viewset to get multiple field filtering based on a `lookup_fields` attribute, instead of the default single field filtering. diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 87d3d4056a..4f566ff598 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -21,7 +21,7 @@ Let's start by creating a simple object we can use for example purposes: from datetime import datetime - class Comment(object): + class Comment: def __init__(self, email, content, created=None): self.email = email self.content = content diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index 009cd2468d..4451489d4d 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -282,7 +282,7 @@ to your `Serializer` subclass. This is documented in the To write a class-based validator, use the `__call__` method. Class-based validators are useful as they allow you to parameterize and reuse behavior. - class MultipleOf(object): + class MultipleOf: def __init__(self, base): self.base = base diff --git a/rest_framework/schemas/generators.py b/rest_framework/schemas/generators.py index 4b6d82a149..77502d028b 100644 --- a/rest_framework/schemas/generators.py +++ b/rest_framework/schemas/generators.py @@ -143,7 +143,7 @@ def get_allowed_methods(self, callback): return [method for method in methods if method not in ('OPTIONS', 'HEAD')] -class BaseSchemaGenerator(object): +class BaseSchemaGenerator: endpoint_inspector_cls = EndpointEnumerator # 'pk' isn't great as an externally exposed name for an identifier, From aed74961ba03e3e6f53c468353f4e255eb788555 Mon Sep 17 00:00:00 2001 From: Jair Henrique Date: Thu, 14 May 2020 04:24:09 -0300 Subject: [PATCH 308/907] Remove compat for ProhibitNullCharactersValidator (#7333) --- rest_framework/compat.py | 5 ----- rest_framework/fields.py | 8 +++----- tests/test_fields.py | 6 ++---- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index df100966b3..1de23bfaaf 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -19,11 +19,6 @@ RegexURLResolver as URLResolver, ) -try: - from django.core.validators import ProhibitNullCharactersValidator # noqa -except ImportError: - ProhibitNullCharactersValidator = None - def get_original_route(urlpattern): """ diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 0447eb74db..da2dd54bed 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -14,7 +14,8 @@ from django.core.exceptions import ValidationError as DjangoValidationError from django.core.validators import ( EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator, - MinValueValidator, RegexValidator, URLValidator, ip_address_validators + MinValueValidator, ProhibitNullCharactersValidator, RegexValidator, + URLValidator, ip_address_validators ) from django.forms import FilePathField as DjangoFilePathField from django.forms import ImageField as DjangoImageField @@ -33,7 +34,6 @@ from rest_framework import ( ISO_8601, RemovedInDRF313Warning, RemovedInDRF314Warning ) -from rest_framework.compat import ProhibitNullCharactersValidator from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, json, representation @@ -785,9 +785,7 @@ def __init__(self, **kwargs): self.validators.append( MinLengthValidator(self.min_length, message=message)) - # ProhibitNullCharactersValidator is None on Django < 2.0 - if ProhibitNullCharactersValidator is not None: - self.validators.append(ProhibitNullCharactersValidator()) + self.validators.append(ProhibitNullCharactersValidator()) self.validators.append(ProhibitSurrogateCharactersValidator()) def run_validation(self, data=empty): diff --git a/tests/test_fields.py b/tests/test_fields.py index a4b78fd513..b1ad1dc664 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -13,7 +13,6 @@ import rest_framework from rest_framework import exceptions, serializers -from rest_framework.compat import ProhibitNullCharactersValidator from rest_framework.fields import ( BuiltinSignatureError, DjangoImageField, is_simple_callable ) @@ -747,7 +746,6 @@ def test_disallow_blank_with_trim_whitespace(self): field.run_validation(' ') assert exc_info.value.detail == ['This field may not be blank.'] - @pytest.mark.skipif(ProhibitNullCharactersValidator is None, reason="Skipped on Django < 2.0") def test_null_bytes(self): field = serializers.CharField() @@ -762,8 +760,8 @@ def test_surrogate_characters(self): field = serializers.CharField() for code_point, expected_message in ( - (0xD800, 'Surrogate characters are not allowed: U+D800.'), - (0xDFFF, 'Surrogate characters are not allowed: U+DFFF.'), + (0xD800, 'Surrogate characters are not allowed: U+D800.'), + (0xDFFF, 'Surrogate characters are not allowed: U+DFFF.'), ): with pytest.raises(serializers.ValidationError) as exc_info: field.run_validation(chr(code_point)) From b83e9121f39822a873b45f2c42c88c7a59c64f87 Mon Sep 17 00:00:00 2001 From: Jair Henrique Date: Thu, 14 May 2020 10:48:14 -0300 Subject: [PATCH 309/907] Remove compat urls for Django < 2.0 (#7335) --- rest_framework/compat.py | 10 ---------- rest_framework/urlpatterns.py | 3 ++- tests/schemas/test_coreapi.py | 3 ++- tests/test_urlpatterns.py | 4 ++-- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 1de23bfaaf..acace3467f 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -212,16 +212,6 @@ def md_filter_add_syntax_highlight(md): return False -# Django 1.x url routing syntax. Remove when dropping Django 1.11 support. -try: - from django.urls import include, path, re_path, register_converter # noqa -except ImportError: - from django.conf.urls import include, url # noqa - path = None - register_converter = None - re_path = url - - # `separators` argument to `json.dumps()` differs between 2.x and 3.x # See: https://bugs.python.org/issue22767 SHORT_SEPARATORS = (',', ':') diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index 831d344ddc..9c82e16336 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -1,7 +1,8 @@ from django.conf.urls import include, url +from django.urls import path, register_converter from rest_framework.compat import ( - URLResolver, get_regex_pattern, is_route_pattern, path, register_converter + URLResolver, get_regex_pattern, is_route_pattern ) from rest_framework.settings import api_settings diff --git a/tests/schemas/test_coreapi.py b/tests/schemas/test_coreapi.py index a634d69685..77e18a9a16 100644 --- a/tests/schemas/test_coreapi.py +++ b/tests/schemas/test_coreapi.py @@ -5,11 +5,12 @@ from django.core.exceptions import PermissionDenied from django.http import Http404 from django.test import TestCase, override_settings +from django.urls import path from rest_framework import ( filters, generics, pagination, permissions, serializers ) -from rest_framework.compat import coreapi, coreschema, get_regex_pattern, path +from rest_framework.compat import coreapi, coreschema, get_regex_pattern from rest_framework.decorators import action, api_view, schema from rest_framework.request import Request from rest_framework.routers import DefaultRouter, SimpleRouter diff --git a/tests/test_urlpatterns.py b/tests/test_urlpatterns.py index 25cc0032ee..51d2695355 100644 --- a/tests/test_urlpatterns.py +++ b/tests/test_urlpatterns.py @@ -3,9 +3,9 @@ from django.conf.urls import include, url from django.test import TestCase -from django.urls import Resolver404 +from django.urls import Resolver404, path, re_path -from rest_framework.compat import make_url_resolver, path, re_path +from rest_framework.compat import make_url_resolver from rest_framework.test import APIRequestFactory from rest_framework.urlpatterns import format_suffix_patterns From 65add6679d5eebe5c8baadb02b4c105da388e0e5 Mon Sep 17 00:00:00 2001 From: Jair Henrique Date: Thu, 14 May 2020 10:49:04 -0300 Subject: [PATCH 310/907] Remove unnecessary test skips (#7336) --- rest_framework/compat.py | 6 ----- tests/test_permissions.py | 46 +++++++++++++++++---------------------- 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index acace3467f..ed1f43b625 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -2,8 +2,6 @@ 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.views.generic import View @@ -217,7 +215,3 @@ def md_filter_add_syntax_highlight(md): SHORT_SEPARATORS = (',', ':') LONG_SEPARATORS = (', ', ': ') INDENT_SEPARATORS = (',', ': ') - - -# Version Constants. -PY36 = sys.version_info >= (3, 6) diff --git a/tests/test_permissions.py b/tests/test_permissions.py index d445f271d4..232c72dd26 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -3,7 +3,6 @@ from unittest import mock import django -import pytest from django.conf import settings from django.contrib.auth.models import AnonymousUser, Group, Permission, User from django.db import models @@ -14,7 +13,6 @@ HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers, status, views ) -from rest_framework.compat import PY36 from rest_framework.routers import DefaultRouter from rest_framework.test import APIRequestFactory from tests.models import BasicModel @@ -607,7 +605,6 @@ def test_several_levels_and_precedence(self): ) 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() @@ -616,19 +613,18 @@ def test_or_lazyness(self): 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() + assert hasperm is True + assert mock_allow.call_count == 1 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() + assert hasperm is True + assert mock_deny.call_count == 1 + assert mock_allow.call_count == 1 - @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() @@ -637,19 +633,18 @@ def test_object_or_lazyness(self): 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() + assert hasperm is True + assert mock_allow.call_count == 1 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() + assert hasperm is True + assert mock_deny.call_count == 1 + assert mock_allow.call_count == 1 - @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() @@ -658,19 +653,18 @@ def test_and_lazyness(self): 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() + assert hasperm is False + assert mock_allow.call_count == 1 + assert mock_deny.call_count == 1 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) + assert hasperm is False + assert mock_deny.call_count == 1 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() @@ -679,14 +673,14 @@ def test_object_and_lazyness(self): 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() + assert hasperm is False + assert mock_allow.call_count == 1 + assert mock_deny.call_count == 1 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) + assert hasperm is False + assert mock_deny.call_count == 1 mock_allow.assert_not_called() - mock_deny.assert_called_once() From bb795674f86828fc5f15d6d61501cc781811e053 Mon Sep 17 00:00:00 2001 From: Jair Henrique Date: Thu, 14 May 2020 15:31:38 -0300 Subject: [PATCH 311/907] Drop all compat support to Django < 2 urls (#7337) --- rest_framework/compat.py | 59 ---------------------------- rest_framework/schemas/generators.py | 4 +- rest_framework/urlpatterns.py | 14 +++---- tests/schemas/test_coreapi.py | 4 +- tests/test_filters.py | 2 - tests/test_routers.py | 13 +++--- tests/test_urlpatterns.py | 6 +-- 7 files changed, 18 insertions(+), 84 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index ed1f43b625..611068a621 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -5,65 +5,6 @@ from django.conf import settings from django.views.generic import View -try: - from django.urls import ( # noqa - URLPattern, - URLResolver, - ) -except ImportError: - # Will be removed in Django 2.0 - from django.urls import ( # noqa - RegexURLPattern as URLPattern, - RegexURLResolver as URLResolver, - ) - - -def get_original_route(urlpattern): - """ - Get the original route/regex that was typed in by the user into the path(), re_path() or url() directive. This - is in contrast with get_regex_pattern below, which for RoutePattern returns the raw regex generated from the path(). - """ - if hasattr(urlpattern, 'pattern'): - # Django 2.0 - return str(urlpattern.pattern) - else: - # Django < 2.0 - return urlpattern.regex.pattern - - -def get_regex_pattern(urlpattern): - """ - Get the raw regex out of the urlpattern's RegexPattern or RoutePattern. This is always a regular expression, - unlike get_original_route above. - """ - if hasattr(urlpattern, 'pattern'): - # Django 2.0 - return urlpattern.pattern.regex.pattern - else: - # Django < 2.0 - return urlpattern.regex.pattern - - -def is_route_pattern(urlpattern): - if hasattr(urlpattern, 'pattern'): - # Django 2.0 - from django.urls.resolvers import RoutePattern - return isinstance(urlpattern.pattern, RoutePattern) - else: - # Django < 2.0 - return False - - -def make_url_resolver(regex, urlpatterns): - try: - # Django 2.0 - from django.urls.resolvers import RegexPattern - return URLResolver(RegexPattern(regex), urlpatterns) - - except ImportError: - # Django < 2.0 - return URLResolver(regex, urlpatterns) - def unicode_http_header(value): # Coerce HTTP header value to unicode. diff --git a/rest_framework/schemas/generators.py b/rest_framework/schemas/generators.py index 77502d028b..d3c6446aa4 100644 --- a/rest_framework/schemas/generators.py +++ b/rest_framework/schemas/generators.py @@ -10,9 +10,9 @@ from django.contrib.admindocs.views import simplify_regex from django.core.exceptions import PermissionDenied from django.http import Http404 +from django.urls import URLPattern, URLResolver from rest_framework import exceptions -from rest_framework.compat import URLPattern, URLResolver, get_original_route from rest_framework.request import clone_request from rest_framework.settings import api_settings from rest_framework.utils.model_meta import _get_pk @@ -79,7 +79,7 @@ def get_api_endpoints(self, patterns=None, prefix=''): api_endpoints = [] for pattern in patterns: - path_regex = prefix + get_original_route(pattern) + path_regex = prefix + str(pattern.pattern) if isinstance(pattern, URLPattern): path = self.get_path_from_regex(path_regex) callback = pattern.callback diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index 9c82e16336..5b0bb44400 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -1,9 +1,7 @@ from django.conf.urls import include, url -from django.urls import path, register_converter +from django.urls import URLResolver, path, register_converter +from django.urls.resolvers import RoutePattern -from rest_framework.compat import ( - URLResolver, get_regex_pattern, is_route_pattern -) from rest_framework.settings import api_settings @@ -38,7 +36,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_r for urlpattern in urlpatterns: if isinstance(urlpattern, URLResolver): # Set of included URL patterns - regex = get_regex_pattern(urlpattern) + regex = urlpattern.pattern.regex.pattern namespace = urlpattern.namespace app_name = urlpattern.app_name kwargs = urlpattern.default_kwargs @@ -49,7 +47,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_r suffix_route) # if the original pattern was a RoutePattern we need to preserve it - if is_route_pattern(urlpattern): + if isinstance(urlpattern.pattern, RoutePattern): assert path is not None route = str(urlpattern.pattern) new_pattern = path(route, include((patterns, app_name), namespace), kwargs) @@ -59,7 +57,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_r ret.append(new_pattern) else: # Regular URL pattern - regex = get_regex_pattern(urlpattern).rstrip('$').rstrip('/') + suffix_pattern + regex = urlpattern.pattern.regex.pattern.rstrip('$').rstrip('/') + suffix_pattern view = urlpattern.callback kwargs = urlpattern.default_args name = urlpattern.name @@ -68,7 +66,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_r ret.append(urlpattern) # if the original pattern was a RoutePattern we need to preserve it - if is_route_pattern(urlpattern): + if isinstance(urlpattern.pattern, RoutePattern): assert path is not None assert suffix_route is not None route = str(urlpattern.pattern).rstrip('$').rstrip('/') + suffix_route diff --git a/tests/schemas/test_coreapi.py b/tests/schemas/test_coreapi.py index 77e18a9a16..403b3b6341 100644 --- a/tests/schemas/test_coreapi.py +++ b/tests/schemas/test_coreapi.py @@ -10,7 +10,7 @@ from rest_framework import ( filters, generics, pagination, permissions, serializers ) -from rest_framework.compat import coreapi, coreschema, get_regex_pattern +from rest_framework.compat import coreapi, coreschema from rest_framework.decorators import action, api_view, schema from rest_framework.request import Request from rest_framework.routers import DefaultRouter, SimpleRouter @@ -1079,7 +1079,7 @@ def test_should_include_endpoint_excludes_correctly(self): inspector = EndpointEnumerator(self.patterns) # Not pretty. Mimics internals of EndpointEnumerator to put should_include_endpoint under test - pairs = [(inspector.get_path_from_regex(get_regex_pattern(pattern)), pattern.callback) + pairs = [(inspector.get_path_from_regex(pattern.pattern.regex.pattern), pattern.callback) for pattern in self.patterns] should_include = [ diff --git a/tests/test_filters.py b/tests/test_filters.py index e69537666d..567e5f83fc 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -1,7 +1,6 @@ import datetime from importlib import reload as reload_module -import django import pytest from django.core.exceptions import ImproperlyConfigured from django.db import models @@ -191,7 +190,6 @@ def test_search_field_with_null_characters(self): assert terms == ['asdf'] - @pytest.mark.skipif(django.VERSION[:2] < (2, 2), reason="requires django 2.2 or higher") def test_search_field_with_additional_transforms(self): from django.test.utils import register_lookup diff --git a/tests/test_routers.py b/tests/test_routers.py index ff927ff339..007cb47683 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -8,7 +8,6 @@ from django.urls import resolve, reverse from rest_framework import permissions, serializers, viewsets -from rest_framework.compat import get_regex_pattern from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.routers import DefaultRouter, SimpleRouter @@ -192,8 +191,7 @@ def setUp(self): def test_custom_lookup_field_route(self): detail_route = notes_router.urls[-1] - detail_url_pattern = get_regex_pattern(detail_route) - assert '' in detail_url_pattern + assert '' in detail_route.pattern.regex.pattern def test_retrieve_lookup_field_list_view(self): response = self.client.get('/example/notes/') @@ -229,7 +227,7 @@ class NoteViewSet(viewsets.ModelViewSet): def test_urls_limited_by_lookup_value_regex(self): expected = ['^notes/$', '^notes/(?P[0-9a-f]{32})/$'] for idx in range(len(expected)): - assert expected[idx] == get_regex_pattern(self.urls[idx]) + assert expected[idx] == self.urls[idx].pattern.regex.pattern @override_settings(ROOT_URLCONF='tests.test_routers') @@ -249,8 +247,7 @@ def setUp(self): def test_custom_lookup_url_kwarg_route(self): detail_route = kwarged_notes_router.urls[-1] - detail_url_pattern = get_regex_pattern(detail_route) - assert '^notes/(?P' in detail_url_pattern + assert '^notes/(?P' in detail_route.pattern.regex.pattern def test_retrieve_lookup_url_kwarg_detail_view(self): response = self.client.get('/example2/notes/fo/') @@ -273,7 +270,7 @@ class NoteViewSet(viewsets.ModelViewSet): def test_urls_have_trailing_slash_by_default(self): expected = ['^notes/$', '^notes/(?P[^/.]+)/$'] for idx in range(len(expected)): - assert expected[idx] == get_regex_pattern(self.urls[idx]) + assert expected[idx] == self.urls[idx].pattern.regex.pattern class TestTrailingSlashRemoved(TestCase): @@ -288,7 +285,7 @@ class NoteViewSet(viewsets.ModelViewSet): def test_urls_can_have_trailing_slash_removed(self): expected = ['^notes$', '^notes/(?P[^/.]+)$'] for idx in range(len(expected)): - assert expected[idx] == get_regex_pattern(self.urls[idx]) + assert expected[idx] == self.urls[idx].pattern.regex.pattern class TestNameableRoot(TestCase): diff --git a/tests/test_urlpatterns.py b/tests/test_urlpatterns.py index 51d2695355..ec19494b08 100644 --- a/tests/test_urlpatterns.py +++ b/tests/test_urlpatterns.py @@ -3,9 +3,9 @@ from django.conf.urls import include, url from django.test import TestCase -from django.urls import Resolver404, path, re_path +from django.urls import Resolver404, URLResolver, path, re_path +from django.urls.resolvers import RegexPattern -from rest_framework.compat import make_url_resolver from rest_framework.test import APIRequestFactory from rest_framework.urlpatterns import format_suffix_patterns @@ -28,7 +28,7 @@ def _resolve_urlpatterns(self, urlpatterns, test_paths, allowed=None): urlpatterns = format_suffix_patterns(urlpatterns, allowed=allowed) except Exception: self.fail("Failed to apply `format_suffix_patterns` on the supplied urlpatterns") - resolver = make_url_resolver(r'^/', urlpatterns) + resolver = URLResolver(RegexPattern(r'^/'), urlpatterns) for test_path in test_paths: try: test_path, expected_resolved = test_path From acbd9d8222e763c7f9c7dc2de23c430c702e06d4 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Fri, 15 May 2020 13:40:47 +0600 Subject: [PATCH 312/907] django 3.1 alpha on matrix (#7334) * django 3.1 alpha on matrix * django 3.1 alpha on matrix --- .travis.yml | 3 +++ setup.py | 1 + tox.ini | 3 +++ 3 files changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index f1ec689f7e..39efaf4fcd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,13 +9,16 @@ matrix: - { python: "3.6", env: DJANGO=2.2 } - { python: "3.6", env: DJANGO=3.0 } + - { python: "3.6", env: DJANGO=3.1 } - { python: "3.6", env: DJANGO=master } - { python: "3.7", env: DJANGO=2.2 } - { python: "3.7", env: DJANGO=3.0 } + - { python: "3.7", env: DJANGO=3.1 } - { python: "3.7", env: DJANGO=master } - { python: "3.8", env: DJANGO=3.0 } + - { python: "3.8", env: DJANGO=3.1 } - { python: "3.8", env: DJANGO=master } - { python: "3.8", env: TOXENV=base } diff --git a/setup.py b/setup.py index 99826b4d0f..38e680e10f 100755 --- a/setup.py +++ b/setup.py @@ -91,6 +91,7 @@ def get_version(package): 'Framework :: Django', 'Framework :: Django :: 2.2', 'Framework :: Django :: 3.0', + 'Framework :: Django :: 3.1', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', diff --git a/tox.ini b/tox.ini index e5b8b64020..190865f23f 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,7 @@ envlist = {py35,py36,py37}-django22, {py36,py37,py38}-django30, + {py36,py37,py38}-django31, {py36,py37,py38}-djangomaster, base,dist,lint,docs, @@ -9,6 +10,7 @@ envlist = DJANGO = 2.2: django22 3.0: django30 + 3.1: django31 master: djangomaster [testenv] @@ -20,6 +22,7 @@ setenv = deps = django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 + django31: Django>=3.1a1,<3.2 djangomaster: https://github.com/django/django/archive/master.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 505a69cf432a1b9684462848059f731ea381ea6c Mon Sep 17 00:00:00 2001 From: Nikhil Kumar Date: Mon, 1 Jun 2020 13:49:43 -0400 Subject: [PATCH 313/907] Fixed heading markdown --- docs/api-guide/serializers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 4f566ff598..6323ceacdf 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -161,7 +161,7 @@ Each key in the dictionary will be the field name, and the values will be lists When deserializing a list of items, errors will be returned as a list of dictionaries representing each of the deserialized items. -#### Raising an exception on invalid data +#### Raising an exception on invalid data The `.is_valid()` method takes an optional `raise_exception` flag that will cause it to raise a `serializers.ValidationError` exception if there are validation errors. From 9dc7021770ecb5689302cb97b282dd85fb7ed108 Mon Sep 17 00:00:00 2001 From: Nikhil Kumar Date: Mon, 1 Jun 2020 14:03:10 -0400 Subject: [PATCH 314/907] Fixed h4 rendering --- docs/api-guide/fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index b2bdd50c8e..e309c9d916 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -371,7 +371,7 @@ Corresponds to `django.db.models.fields.TimeField` * `format` - A string representing the output format. If not specified, this defaults to the same value as the `TIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `time` objects should be returned by `to_representation`. In this case the time encoding will be determined by the renderer. * `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `TIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. -#### `TimeField` format strings +#### `TimeField` format strings Format strings may either be [Python strftime formats][strftime] which explicitly specify the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`) From b677b7b15dc85c3e2ba3f4407d7985f54a11eaad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rignon=20No=C3=ABl?= Date: Thu, 11 Jun 2020 13:33:04 -0400 Subject: [PATCH 315/907] Update link to dry-rest-permissions (#7374) dry-rest-permissions was no maintain since 2018, so FJNR-inc just got a new PyPi version on their fork --- docs/api-guide/permissions.md | 2 +- docs/community/third-party-packages.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index ac2924a83d..d035243d5d 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -322,7 +322,7 @@ The [Django Rest Framework Role Filters][django-rest-framework-role-filters] pac [filtering]: filtering.md [composed-permissions]: https://github.com/niwibe/djangorestframework-composed-permissions [rest-condition]: https://github.com/caxap/rest_condition -[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions +[dry-rest-permissions]: https://github.com/FJNR-inc/dry-rest-permissions [django-rest-framework-roles]: https://github.com/computer-lab/django-rest-framework-roles [djangorestframework-api-key]: https://florimondmanca.github.io/djangorestframework-api-key/ [django-rest-framework-role-filters]: https://github.com/allisson/django-rest-framework-role-filters diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 2033d97abc..b47dc098fd 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -327,7 +327,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-versatileimagefield-drf-docs]:https://django-versatileimagefield.readthedocs.io/en/latest/drf_integration.html [drf-tracking]: https://github.com/aschn/drf-tracking [django-rest-framework-braces]: https://github.com/dealertrack/django-rest-framework-braces -[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions +[dry-rest-permissions]: https://github.com/FJNR-inc/dry-rest-permissions [django-url-filter]: https://github.com/miki725/django-url-filter [drf-url-filter]: https://github.com/manjitkumar/drf-url-filters [cookiecutter-django-rest]: https://github.com/agconti/cookiecutter-django-rest From e2bd3b6a57c32b187671625544d002eae018e0df Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 15 Jun 2020 12:43:09 +0200 Subject: [PATCH 316/907] Adjusted token admin to map to user ID. (#7341) Closes #6131. * Adds a proxy model for Token that uses the user.pk, rather than it's own. * Adjusts Admin to map back from User ID to token instance. --- rest_framework/authtoken/admin.py | 45 ++++++++++++++++++++++++++++-- rest_framework/authtoken/models.py | 13 +++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/rest_framework/authtoken/admin.py b/rest_framework/authtoken/admin.py index f0cf646f46..b359e4cfe8 100644 --- a/rest_framework/authtoken/admin.py +++ b/rest_framework/authtoken/admin.py @@ -1,10 +1,51 @@ from django.contrib import admin +from django.contrib.admin.utils import quote +from django.contrib.admin.views.main import ChangeList +from django.contrib.auth import get_user_model +from django.core.exceptions import ValidationError +from django.urls import reverse -from rest_framework.authtoken.models import Token +from rest_framework.authtoken.models import Token, TokenProxy + +User = get_user_model() + + +class TokenChangeList(ChangeList): + """Map to matching User id""" + def url_for_result(self, result): + pk = result.user.pk + return reverse('admin:%s_%s_change' % (self.opts.app_label, + self.opts.model_name), + args=(quote(pk),), + current_app=self.model_admin.admin_site.name) -@admin.register(Token) class TokenAdmin(admin.ModelAdmin): list_display = ('key', 'user', 'created') fields = ('user',) ordering = ('-created',) + actions = None # Actions not compatible with mapped IDs. + + def get_changelist(self, request, **kwargs): + return TokenChangeList + + def get_object(self, request, object_id, from_field=None): + """ + Map from User ID to matching Token. + """ + queryset = self.get_queryset(request) + field = User._meta.pk + try: + object_id = field.to_python(object_id) + user = User.objects.get(**{field.name: object_id}) + return queryset.get(user=user) + except (queryset.model.DoesNotExist, User.DoesNotExist, ValidationError, ValueError): + return None + + def delete_model(self, request, obj): + # Map back to actual Token, since delete() uses pk. + token = Token.objects.get(key=obj.key) + return super().delete_model(request, token) + + +admin.site.register(TokenProxy, TokenAdmin) diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index bff42d3de8..f8a871bf1f 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -37,3 +37,16 @@ def generate_key(self): def __str__(self): return self.key + + +class TokenProxy(Token): + """ + Proxy mapping pk to user pk for use in admin. + """ + @property + def pk(self): + return self.user.pk + + class Meta: + proxy = True + verbose_name = "token" From e18e40d6ae42457f60ca9c68054ad40d15ba8433 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 16 Jun 2020 13:33:48 +0200 Subject: [PATCH 317/907] Updated Schema docs. (#7268) --- docs/api-guide/schemas.md | 373 ++++++++++++++++++++------------------ 1 file changed, 199 insertions(+), 174 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 3dc3f56289..402315ef9a 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -16,6 +16,25 @@ can interact with your API. Django REST Framework provides support for automatic generation of [OpenAPI][openapi] schemas. +## Overview + +Schema generation has several moving parts. It's worth having an overview: + +* `SchemaGenerator` is a top-level class that is responsible for walking your + configured URL patterns, finding `APIView` subclasses, enquiring for their + schema representation, and compiling the final schema object. +* `AutoSchema` encapsulates all the details necessary for per-view schema + introspection. Is attached to each view via the `schema` attribute. You + subclass `AutoSchema` in order to customize your schema. +* The `generateschema` management command allows you to generate a static schema + offline. +* Alternatively, you can route `SchemaView` to dynamically generate and serve + your schema. +* `settings.DEFAULT_SCHEMA_CLASS` allows you to specify an `AutoSchema` + subclass to serve as your project's default. + +The following sections explain more. + ## Generating an OpenAPI Schema ### Install dependencies @@ -115,23 +134,20 @@ The `get_schema_view()` helper takes the following keyword arguments: * `renderer_classes`: May be used to pass the set of renderer classes that can be used to render the API root endpoint. -## Customizing Schema Generation - -You may customize schema generation at the level of the schema as a whole, or -on a per-view basis. -### Schema Level Customization +## SchemaGenerator -In order to customize the top-level schema subclass -`rest_framework.schemas.openapi.SchemaGenerator` and provide it as an argument -to the `generateschema` command or `get_schema_view()` helper function. +**Schema-level customization** -#### SchemaGenerator +```python +from rest_framework.schemas.openapi import SchemaGenerator +``` -A class that walks a list of routed URL patterns, requests the schema for each -view and collates the resulting OpenAPI schema. +`SchemaGenerator` is a class that walks a list of routed URL patterns, requests +the schema for each view and collates the resulting OpenAPI schema. -Typically you'll instantiate `SchemaGenerator` with a `title` argument, like so: +Typically you won't need to instantiate `SchemaGenerator` yourself, but you can +do so like so: generator = SchemaGenerator(title='Stock Prices API') @@ -144,7 +160,12 @@ Arguments: * `patterns`: A list of URLs to inspect when generating the schema. Defaults to the project's URL conf. * `urlconf`: A URL conf module name to use when generating the schema. Defaults to `settings.ROOT_URLCONF`. -##### get_schema(self, request) +In order to customize the top-level schema, subclass +`rest_framework.schemas.openapi.SchemaGenerator` and provide your subclass +as an argument to the `generateschema` command or `get_schema_view()` helper +function. + +### get_schema(self, request) Returns a dictionary that represents the OpenAPI schema: @@ -155,233 +176,236 @@ The `request` argument is optional, and may be used if you want to apply per-user permissions to the resulting schema generation. This is a good point to override if you want to customize the generated -dictionary, for example to add custom -[specification extensions][openapi-specification-extensions]. +dictionary For example you might wish to add terms of service to the [top-level +`info` object][info-object]: -### Per-View Customization +``` +class TOSSchemaGenerator(SchemaGenerator): + def get_schema(self): + schema = super().get_schema() + schema["info"]["termsOfService"] = "https://example.com/tos.html" + return schema +``` -By default, view introspection is performed by an `AutoSchema` instance -accessible via the `schema` attribute on `APIView`. This provides the -appropriate [Open API operation object][openapi-operation] for the view, -request method and path: +## AutoSchema - auto_schema = view.schema - operation = auto_schema.get_operation(...) +**Per-View Customization** -In compiling the schema, `SchemaGenerator` calls `view.schema.get_operation()` -for each view, allowed method, and path. +```python +from rest_framework.schemas.openapi import AutoSchema +``` ---- +By default, view introspection is performed by an `AutoSchema` instance +accessible via the `schema` attribute on `APIView`. -**Note**: For basic `APIView` subclasses, default introspection is essentially -limited to the URL kwarg path parameters. For `GenericAPIView` -subclasses, which includes all the provided class based views, `AutoSchema` will -attempt to introspect serializer, pagination and filter fields, as well as -provide richer path field descriptions. (The key hooks here are the relevant -`GenericAPIView` attributes and methods: `get_serializer`, `pagination_class`, -`filter_backends` and so on.) + auto_schema = some_view.schema ---- +`AutoSchema` provides the OpenAPI elements needed for each view, request method +and path: -In order to customize the operation generation, you should provide an `AutoSchema` subclass, overriding `get_operation()` as you need: +* A list of [OpenAPI components][openapi-components]. In DRF terms these are + mappings of serializers that describe request and response bodies. +* The appropriate [OpenAPI operation object][openapi-operation] that describes + the endpoint, including path and query parameters for pagination, filtering, + and so on. - from rest_framework.views import APIView - from rest_framework.schemas.openapi import AutoSchema +```python +components = auto_schema.get_components(...) +operation = auto_schema.get_operation(...) +``` - class CustomSchema(AutoSchema): - def get_operation(...): - # Implement custom introspection here (or in other sub-methods) +In compiling the schema, `SchemaGenerator` calls `get_components()` and +`get_operation()` for each view, allowed method, and path. - class CustomView(APIView): - """APIView subclass with custom schema introspection.""" - schema = CustomSchema() +---- -This provides complete control over view introspection. +**Note**: The automatic introspection of components, and many operation +parameters relies on the relevant attributes and methods of +`GenericAPIView`: `get_serializer()`, `pagination_class`, `filter_backends`, +etc. For basic `APIView` subclasses, default introspection is essentially limited to +the URL kwarg path parameters for this reason. -You may disable schema generation for a view by setting `schema` to `None`: +---- - class CustomView(APIView): - ... - schema = None # Will not appear in schema +`AutoSchema` encapsulates the view introspection needed for schema generation. +Because of this all the schema generation logic is kept in a single place, +rather than being spread around the already extensive view, serializer and +field APIs. -This also applies to extra actions for `ViewSet`s: +Keeping with this pattern, try not to let schema logic leak into your own +views, serializers, or fields when customizing the schema generation. You might +be tempted to do something like this: - class CustomViewSet(viewsets.ModelViewSet): +```python +class CustomSchema(AutoSchema): + """ + AutoSchema subclass using schema_extra_info on the view. + """ + ... - @action(detail=True, schema=None) - def extra_action(self, request, pk=None): - ... +class CustomView(APIView): + schema = CustomSchema() + schema_extra_info = ... some extra info ... +``` -If you wish to provide a base `AutoSchema` subclass to be used throughout your -project you may adjust `settings.DEFAULT_SCHEMA_CLASS` appropriately. +Here, the `AutoSchema` subclass goes looking for `schema_extra_info` on the +view. This is _OK_ (it doesn't actually hurt) but it means you'll end up with +your schema logic spread out in a number of different places. +Instead try to subclass `AutoSchema` such that the `extra_info` doesn't leak +out into the view: -### Grouping Operations With Tags +```python +class BaseSchema(AutoSchema): + """ + AutoSchema subclass that knows how to use extra_info. + """ + ... -Tags can be used to group logical operations. Each tag name in the list MUST be unique. +class CustomSchema(BaseSchema): + extra_info = ... some extra info ... ---- -#### Django REST Framework generates tags automatically with the following logic: +class CustomView(APIView): + schema = CustomSchema() +``` -Tag name will be first element from the path. Also, any `_` in path name will be replaced by a `-`. -Consider below examples. +This style is slightly more verbose but maintains the encapsulation of the +schema related code. It's more _cohesive_ in the _parlance_. It'll keep the +rest of your API code more tidy. -Example 1: Consider a user management system. The following table will illustrate the tag generation logic. -Here first element from the paths is: `users`. Hence tag wil be `users` +If an option applies to many view classes, rather than creating a specific +subclass per-view, you may find it more convenient to allow specifying the +option as an `__init__()` kwarg to your base `AutoSchema` subclass: -Http Method | Path | Tags --------------------------------------|-------------------|------------- -PUT, PATCH, GET(Retrieve), DELETE | /users/{id}/ | ['users'] -POST, GET(List) | /users/ | ['users'] +```python +class CustomSchema(BaseSchema): + def __init__(self, **kwargs): + # store extra_info for later + self.extra_info = kwargs.pop("extra_info") + super().__init__(**kwargs) -Example 2: Consider a restaurant management system. The System has restaurants. Each restaurant has branches. -Consider REST APIs to deal with a branch of a particular restaurant. -Here first element from the paths is: `restaurants`. Hence tag wil be `restaurants`. +class CustomView(APIView): + schema = CustomSchema( + extra_info=... some extra info ... + ) +``` -Http Method | Path | Tags --------------------------------------|----------------------------------------------------|------------------- -PUT, PATCH, GET(Retrieve), DELETE: | /restaurants/{restaurant_id}/branches/{branch_id} | ['restaurants'] -POST, GET(List): | /restaurants/{restaurant_id}/branches/ | ['restaurants'] +This saves you having to create a custom subclass per-view for a commonly used option. -Example 3: Consider Order items for an e commerce company. +Not all `AutoSchema` methods expose related `__init__()` kwargs, but those for +the more commonly needed options do. -Http Method | Path | Tags --------------------------------------|-------------------------|------------- -PUT, PATCH, GET(Retrieve), DELETE | /order_items/{id}/ | ['order-items'] -POST, GET(List) | /order_items/ | ['order-items'] - +### `AutoSchema` methods ---- -#### Overriding auto generated tags: -You can override auto-generated tags by passing `tags` argument to the constructor of `AutoSchema`. `tags` argument must be a list or tuple of string. -```python -from rest_framework.schemas.openapi import AutoSchema -from rest_framework.views import APIView +#### `get_components()` -class MyView(APIView): - schema = AutoSchema(tags=['tag1', 'tag2']) - ... -``` +Generates the OpenAPI components that describe request and response bodies, +deriving their properties from the serializer. -If you need more customization, you can override the `get_tags` method of `AutoSchema` class. Consider the following example: +Returns a dictionary mapping the component name to the generated +representation. By default this has just a single pair but you may override +`get_components()` to return multiple pairs if your view uses multiple +serializers. -```python -from rest_framework.schemas.openapi import AutoSchema -from rest_framework.views import APIView +#### `get_component_name()` -class MySchema(AutoSchema): - ... - def get_tags(self, path, method): - if method == 'POST': - tags = ['tag1', 'tag2'] - elif method == 'GET': - tags = ['tag2', 'tag3'] - elif path == '/example/path/': - tags = ['tag3', 'tag4'] - else: - tags = ['tag5', 'tag6', 'tag7'] - - return tags - -class MyView(APIView): - schema = MySchema() - ... -``` +Computes the component's name from the serializer. -### OperationId +You may see warnings if your API has duplicate component names. If so you can override `get_component_name()` or pass the `component_name` `__init__()` kwarg (see below) to provide different names. -The schema generator generates an [operationid][openapi-operationid] for each operation. This `operationId` is deduced from the model name, serializer name or view name. The operationId may looks like "listItems", "retrieveItem", "updateItem", etc.. -The `operationId` is camelCase by convention. +#### `map_serializer()` -If you have several views with the same model, the generator may generate duplicate operationId. -In order to work around this, you can override the second part of the operationId: operation name. +Maps serializers to their OpenAPI representations. -```python -from rest_framework.schemas.openapi import AutoSchema +Most serializers should conform to the standard OpenAPI `object` type, but you may +wish to override `map_serializer()` in order to customize this or other +serializer-level fields. -class ExampleView(APIView): - """APIView subclass with custom schema introspection.""" - schema = AutoSchema(operation_id_base="Custom") -``` +#### `map_field()` -The previous example will generate the following operationId: "listCustoms", "retrieveCustom", "updateCustom", "partialUpdateCustom", "destroyCustom". -You need to provide the singular form of he operation name. For the list operation, a "s" will be appended at the end of the operation. +Maps individual serializer fields to their schema representation. The base implementation +will handle the default fields that Django REST Framework provides. -If you need more configuration over the `operationId` field, you can override the `get_operation_id_base` and `get_operation_id` methods from the `AutoSchema` class: +For `SerializerMethodField` instances, for which the schema is unknown, or custom field subclasses you should override `map_field()` to generate the correct schema: ```python class CustomSchema(AutoSchema): - def get_operation_id_base(self, path, method, action): - pass + """Extension of ``AutoSchema`` to add support for custom field schemas.""" - def get_operation_id(self, path, method): - pass - -class MyView(APIView): - schema = AutoSchema(component_name="Ulysses") + def map_field(self, field): + # Handle SerializerMethodFields or custom fields here... + # ... + return super().map_field(field) ``` -### Components - -Since DRF 3.12, Schema uses the [OpenAPI Components][openapi-components]. This method defines components in the schema and [references them][openapi-reference] inside request and response objects. By default, the component's name is deduced from the Serializer's name. +Authors of third-party packages should aim to provide an `AutoSchema` subclass, +and a mixin, overriding `map_field()` so that users can easily generate schemas +for their custom fields. -Using OpenAPI's components provides the following advantages: +#### `get_tags()` -* The schema is more readable and lightweight. -* If you use the schema to generate an SDK (using [openapi-generator][openapi-generator] or [swagger-codegen][swagger-codegen]). The generator can name your SDK's models. +OpenAPI groups operations by tags. By default tags taken from the first path +segment of the routed URL. For example, a URL like `/users/{id}/` will generate +the tag `users`. -### Handling component's schema errors +You can pass an `__init__()` kwarg to manually specify tags (see below), or +override `get_tags()` to provide custom logic. -You may get the following error while generating the schema: -``` -"Serializer" is an invalid class name for schema generation. -Serializer's class name should be unique and explicit. e.g. "ItemSerializer". -``` +#### `get_operation()` -This error occurs when the Serializer name is "Serializer". You should choose a component's name unique across your schema and different than "Serializer". +Returns the [OpenAPI operation object][openapi-operation] that describes the +endpoint, including path and query parameters for pagination, filtering, and so +on. -You may also get the following warning: -``` -Schema component "ComponentName" has been overriden with a different value. -``` +Together with `get_components()`, this is the main entry point to the view +introspection. -This warning occurs when different components have the same name in one schema. Your component name should be unique across your project. This is likely an error that may lead to an invalid schema. +#### `get_operation_id()` -You have two ways to solve the previous issues: +There must be a unique [operationid](openapi-operationid) for each operation. +By default the `operationId` is deduced from the model name, serializer name or +view name. The operationId looks like "listItems", "retrieveItem", +"updateItem", etc. The `operationId` is camelCase by convention. -* You can rename your serializer with a unique name and another name than "Serializer". -* You can set the `component_name` kwarg parameter of the AutoSchema constructor (see below). -* You can override the `get_component_name` method of the AutoSchema class (see below). +#### `get_operation_id_base()` -#### Set a custom component's name for your view +If you have several views with the same model name, you may see duplicate +operationIds. -To override the component's name in your view, you can use the `component_name` parameter of the AutoSchema constructor: +In order to work around this, you can override `get_operation_id_base()` to +provide a different base for name part of the ID. -```python -from rest_framework.schemas.openapi import AutoSchema +### `AutoSchema.__init__()` kwargs -class MyView(APIView): - schema = AutoSchema(component_name="Ulysses") -``` +`AutoSchema` provides a number of `__init__()` kwargs that can be used for +common customizations, if the default generated values are not appropriate. -#### Override the default implementation +The available kwargs are: -If you want to have more control and customization about how the schema's components are generated, you can override the `get_component_name` and `get_components` method from the AutoSchema class. +* `tags`: Specify a list of tags. +* `component_name`: Specify the component name. +* `operation_id_base`: Specify the resource-name part of operation IDs. -```python -from rest_framework.schemas.openapi import AutoSchema +You pass the kwargs when declaring the `AutoSchema` instance on your view: -class CustomSchema(AutoSchema): - def get_components(self, path, method): - # Implement your custom implementation +``` +class PetDetailView(generics.RetrieveUpdateDestroyAPIView): + schema = AutoSchema( + tags=['Pets'], + component_name='Pet', + operation_id_base='Pet', + ) + ... +``` - def get_component_name(self, serializer): - # Implement your custom implementation +Assuming a `Pet` model and `PetSerializer` serializer, the kwargs in this +example are probably not needed. Often, though, you'll need to pass the kwargs +if you have multiple view targeting the same model, or have multiple views with +identically named serializers. -class CustomView(APIView): - """APIView subclass with custom schema introspection.""" - schema = CustomSchema() -``` +If your views have related customizations that are needed frequently, you can +create a base `AutoSchema` subclass for your project that takes additional +`__init__()` kwargs to save subclassing `AutoSchema` for each view. [openapi]: https://github.com/OAI/OpenAPI-Specification [openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification-extensions @@ -392,3 +416,4 @@ class CustomView(APIView): [openapi-reference]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#referenceObject [openapi-generator]: https://github.com/OpenAPITools/openapi-generator [swagger-codegen]: https://github.com/swagger-api/swagger-codegen +[info-object]: https://swagger.io/specification/#infoObject From 7a04269209d398a79fe5969044fab27470808f47 Mon Sep 17 00:00:00 2001 From: w Date: Sat, 20 Jun 2020 01:29:41 +0800 Subject: [PATCH 318/907] Fixed docs typo (#7382) --- docs/community/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 0e634aa1e9..d6f56317d2 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -187,7 +187,7 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10. * Fixed Javascript `e.indexOf` is not a function error [#5982][gh5982] * Fix schemas for extra actions [#5992][gh5992] * Improved get_error_detail to use error_dict/error_list [#5785][gh5785] -* Imprvied URLs in Admin renderer [#5988][gh5988] +* Improved URLs in Admin renderer [#5988][gh5988] * Add "Community" section to docs, minor cleanup [#5993][gh5993] * Moved guardian imports out of compat [#6054][gh6054] * Deprecate the `DjangoObjectPermissionsFilter` class, moved to the `djangorestframework-guardian` package. [#6075][gh6075] From 19915d19170c48961cc4cb97d773c99cba11aff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Thanh=20L=E1=BB=A3i=20=28Leonn=29?= Date: Tue, 23 Jun 2020 04:24:50 +0700 Subject: [PATCH 319/907] Fix docs typo (#7387) --- docs/api-guide/fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index b2bdd50c8e..7722a1f274 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -595,7 +595,7 @@ If you want to create a custom field, you'll need to subclass `Field` and then o The `.to_representation()` method is called to convert the initial datatype into a primitive, serializable datatype. -The `to_internal_value()` method is called to restore a primitive datatype into its internal python representation. This method should raise a `serializers.ValidationError` if the data is invalid. +The `.to_internal_value()` method is called to restore a primitive datatype into its internal python representation. This method should raise a `serializers.ValidationError` if the data is invalid. ## Examples From 5ce237e00471d885f05e6d979ec777552809b3b1 Mon Sep 17 00:00:00 2001 From: Dhaval Mehta <20968146+dhaval-mehta@users.noreply.github.com> Date: Sun, 28 Jun 2020 17:58:59 +0530 Subject: [PATCH 320/907] Corrected regex serialization for OpenAPI. (#7389) * replace \Z by \z in regex * fix test cases for Django >= 3.0 * fix isort * Added comment for why `\z`. Co-authored-by: Carlton Gibson --- rest_framework/schemas/openapi.py | 4 +++- tests/schemas/test_openapi.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 9b3082822a..9774a94c76 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -554,7 +554,9 @@ def map_field_validators(self, field, schema): if isinstance(v, URLValidator): schema['format'] = 'uri' if isinstance(v, RegexValidator): - schema['pattern'] = v.regex.pattern + # In Python, the token \Z does what \z does in other engines. + # https://stackoverflow.com/questions/53283160 + schema['pattern'] = v.regex.pattern.replace('\\Z', '\\z') elif isinstance(v, MaxLengthValidator): attr_name = 'maxLength' if isinstance(field, serializers.ListField): diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 0e86a7f507..d483f3d456 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -855,6 +855,7 @@ def test_serializer_validators(self): assert properties['url']['type'] == 'string' assert properties['url']['nullable'] is True assert properties['url']['default'] == 'http://www.example.com' + assert '\\Z' not in properties['url']['pattern'] assert properties['uuid']['type'] == 'string' assert properties['uuid']['format'] == 'uuid' From 36bd1b30d827ef3daaa929ac35c5c5b6ca38b435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuzhan=20=C3=87elikarslan?= Date: Mon, 6 Jul 2020 13:56:46 +0300 Subject: [PATCH 321/907] drf-encrypt-content added into third party list in documentation's serializer page (#7398) --- docs/api-guide/serializers.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 4f566ff598..ceec319ded 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -1178,6 +1178,11 @@ The [html-json-forms][html-json-forms] package provides an algorithm and seriali The [drf-writable-nested][drf-writable-nested] package provides writable nested model serializer which allows to create/update models with nested related data. +## DRF Encrypt Content + +The [drf-encrypt-content][drf-encrypt-content] package helps you encrypt your data, serialized through ModelSerializer. It also contains some helper functions. Which helps you to encrypt your data. + + [cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion [relations]: relations.md [model-managers]: https://docs.djangoproject.com/en/stable/topics/db/managers/ @@ -1199,3 +1204,4 @@ The [drf-writable-nested][drf-writable-nested] package provides writable nested [drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions [djangorestframework-queryfields]: https://djangorestframework-queryfields.readthedocs.io/ [drf-writable-nested]: https://github.com/beda-software/drf-writable-nested +[drf-encrypt-content]: https://github.com/oguzhancelikarslan/drf-encrypt-content From d46d5cbaaaa5626f5b72b3bdefbf8438b8ed401d Mon Sep 17 00:00:00 2001 From: Minjae Kim Date: Tue, 7 Jul 2020 17:58:32 +0900 Subject: [PATCH 322/907] Not include charset when charset is None (#7400) --- rest_framework/test.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index ab16c2787c..f2581cacca 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -179,9 +179,11 @@ def _encode_data(self, data, format=None, content_type=None): ret = renderer.render(data) # Determine the content-type header from the renderer - content_type = "{}; charset={}".format( - renderer.media_type, renderer.charset - ) + content_type = renderer.media_type + if renderer.charset: + content_type = "{}; charset={}".format( + content_type, renderer.charset + ) # Coerce text to bytes if required. if isinstance(ret, str): From 76232437d458c818dcf0e52502d9d561b8aa7180 Mon Sep 17 00:00:00 2001 From: Anton Agestam Date: Tue, 7 Jul 2020 11:05:36 +0200 Subject: [PATCH 323/907] Allow type checkers to make serializers generic (#7385) --- rest_framework/serializers.py | 4 ++++ tests/test_serializer.py | 8 ++++++++ tests/test_serializer_lists.py | 9 +++++++++ 3 files changed, 21 insertions(+) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index cfb54de138..916f8bec44 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -121,6 +121,10 @@ def __new__(cls, *args, **kwargs): return cls.many_init(*args, **kwargs) return super().__new__(cls, *args, **kwargs) + # Allow type checkers to make serializers generic. + def __class_getitem__(cls, *args, **kwargs): + return cls + @classmethod def many_init(cls, *args, **kwargs): """ diff --git a/tests/test_serializer.py b/tests/test_serializer.py index a58c46b2d9..afefd70e1c 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -1,6 +1,7 @@ import inspect import pickle import re +import sys from collections import ChainMap from collections.abc import Mapping @@ -204,6 +205,13 @@ class ExampleSerializer(serializers.Serializer): exceptions.ErrorDetail(string='Raised error', code='invalid') ]} + @pytest.mark.skipif( + sys.version_info < (3, 7), + reason="subscriptable classes requires Python 3.7 or higher", + ) + def test_serializer_is_subscriptable(self): + assert serializers.Serializer is serializers.Serializer["foo"] + class TestValidateMethod: def test_non_field_error_validate_method(self): diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 98e72385a2..f35c4fcc9e 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -1,3 +1,5 @@ +import sys + import pytest from django.http import QueryDict from django.utils.datastructures import MultiValueDict @@ -55,6 +57,13 @@ def test_validate_html_input(self): assert serializer.is_valid() assert serializer.validated_data == expected_output + @pytest.mark.skipif( + sys.version_info < (3, 7), + reason="subscriptable classes requires Python 3.7 or higher", + ) + def test_list_serializer_is_subscriptable(self): + assert serializers.ListSerializer is serializers.ListSerializer["foo"] + class TestListSerializerContainingNestedSerializer: """ From 4b06e0a5a114b7631bd237e29e2d6d22412a50bd Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sun, 12 Jul 2020 11:08:40 +0200 Subject: [PATCH 324/907] Fix tests crash on SQLite without JSON1 extension. (#7409) --- tests/test_serializer_nested.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index a614e05d13..09ac37f246 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -311,6 +311,9 @@ class NonRelationalPersonModel(models.Model): """Model declaring a postgres JSONField""" data = postgres_fields.JSONField() + class Meta: + required_db_features = {'supports_json_field'} + @pytest.mark.skipif(not postgres_fields, reason='psycopg2 is not installed') class TestNestedNonRelationalFieldWrite: From 1e164c5eebfe0284444dd8538ac2a93f76306324 Mon Sep 17 00:00:00 2001 From: Anirudh Bagri Date: Thu, 23 Jul 2020 21:04:34 +0530 Subject: [PATCH 325/907] Just smalling cleaning up, causing confusion (#7426) --- docs/tutorial/quickstart.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 546144670c..ee839790f1 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -178,7 +178,7 @@ We can now access our API, both from the command-line, using tools like `curl`.. }, { "email": "tom@example.com", - "groups": [ ], + "groups": [], "url": "http://127.0.0.1:8000/users/2/", "username": "tom" } @@ -204,7 +204,7 @@ Or using the [httpie][httpie], command line tool... }, { "email": "tom@example.com", - "groups": [ ], + "groups": [], "url": "http://127.0.0.1:8000/users/2/", "username": "tom" } From 599e2b183db846a632b1efd148e6bc97d926ee5c Mon Sep 17 00:00:00 2001 From: John Vandenberg Date: Fri, 24 Jul 2020 00:26:35 +0700 Subject: [PATCH 326/907] urlpatterns: Remove unnecessary branching (#7405) Functions path and register_converter are constants. --- rest_framework/urlpatterns.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index 5b0bb44400..efcfd8401f 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -105,12 +105,9 @@ def format_suffix_patterns(urlpatterns, suffix_required=False, allowed=None): else: suffix_pattern = r'\.(?P<%s>[a-z0-9]+)/?$' % suffix_kwarg - if path and register_converter: - converter_name, suffix_converter = _get_format_path_converter(suffix_kwarg, allowed) - register_converter(suffix_converter, converter_name) + converter_name, suffix_converter = _get_format_path_converter(suffix_kwarg, allowed) + register_converter(suffix_converter, converter_name) - suffix_route = '<%s:%s>' % (converter_name, suffix_kwarg) - else: - suffix_route = None + suffix_route = '<%s:%s>' % (converter_name, suffix_kwarg) return apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_route) From c252c3dfa550e57a7a9ac9bde518d10bb639c315 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 29 Jul 2020 14:55:10 +0100 Subject: [PATCH 327/907] Update sponsors (#7434) * Adjusted token admin to map to user ID. Closes #6131. * Adds a proxy model for Token that uses the user.pk, rather than it's own. * Adjusts Admin to map back from User ID to token instance. * Update sponsors Co-authored-by: Carlton Gibson --- README.md | 16 ++++------------ docs/img/premium/bitio-readme.png | Bin 0 -> 27386 bytes docs/index.md | 6 ++---- 3 files changed, 6 insertions(+), 16 deletions(-) create mode 100644 docs/img/premium/bitio-readme.png diff --git a/README.md b/README.md index 95020bb1d6..4dfff31195 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,11 @@ The initial aim is to provide a single full-time position on REST framework. [![][sentry-img]][sentry-url] [![][stream-img]][stream-url] [![][rollbar-img]][rollbar-url] -[![][cadre-img]][cadre-url] -[![][kloudless-img]][kloudless-url] [![][esg-img]][esg-url] -[![][lightson-img]][lightson-url] [![][retool-img]][retool-url] +[![][bitio-img]][bitio-url] -Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Kloudless][kloudless-url], [ESG][esg-url], [Lights On Software][lightson-url], and [Retool][retool-url]. +Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [ESG][esg-url], [Retool][retool-url], and [bit.io][bitio-url]. --- @@ -191,25 +189,19 @@ Please see the [security policy][security-policy]. [funding]: https://fund.django-rest-framework.org/topics/funding/ [sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors -[rover-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rover-readme.png [sentry-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/sentry-readme.png [stream-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/stream-readme.png [rollbar-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rollbar-readme.png -[cadre-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cadre-readme.png -[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 [esg-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/esg-readme.png -[lightson-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/lightson-readme.png [retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png +[bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png [sentry-url]: https://getsentry.com/welcome/ [stream-url]: https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf [rollbar-url]: https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial -[cadre-url]: https://cadre.com/ -[kloudless-url]: https://hubs.ly/H0f30Lf0 [esg-url]: https://software.esg-usa.com/ -[lightson-url]: https://lightsonsoftware.com [retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship +[bitio-url]: https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship [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 diff --git a/docs/img/premium/bitio-readme.png b/docs/img/premium/bitio-readme.png new file mode 100644 index 0000000000000000000000000000000000000000..42959bb60c23dcca3f21f43ed55438ef75e48180 GIT binary patch literal 27386 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
  • ESG
  • Rollbar
  • -
  • Cadre
  • -
  • Kloudless
  • -
  • Lights On Software
  • Retool
  • +
  • bit.io
  • -*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), and [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship).* +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), and [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship).* --- From 30ca04df835e2d41f32a0ecad3a23bcd06533658 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Wed, 29 Jul 2020 09:58:53 -0400 Subject: [PATCH 328/907] Fix broken links in documenting API section The changed links were not working on the documentation site. I'm not 100% sure that these changes are correct but they generate working links in the GitHub previews. --- docs/topics/documenting-your-api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md index 5c5872650d..e86a6cc771 100644 --- a/docs/topics/documenting-your-api.md +++ b/docs/topics/documenting-your-api.md @@ -209,8 +209,8 @@ 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 [hypermedia-docs]: rest-hypermedia-hateoas.md -[metadata-docs]: ../api-guide/metadata/ -[schemas-examples]: ../api-guide/schemas/#examples +[metadata-docs]: ../api-guide/metadata.md +[schemas-examples]: ../api-guide/schemas.md#examples [image-drf-yasg]: ../img/drf-yasg.png [image-self-describing-api]: ../img/self-describing.png From 559088463b8625880e6a03dde804cb5a61da3088 Mon Sep 17 00:00:00 2001 From: Paul Watts Date: Wed, 29 Jul 2020 12:21:13 -0700 Subject: [PATCH 329/907] Update an example to not use oppressive language (#7439) * Update an example to use less oppressive language For reference: https://tools.ietf.org/id/draft-knodel-terminology-00.html * Code review update Blocklisted -> blocked. --- docs/api-guide/permissions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index d035243d5d..f317c12e66 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -243,19 +243,19 @@ Custom permissions will raise a `PermissionDenied` exception if the test fails. ## Examples -The following is an example of a permission class that checks the incoming request's IP address against a blacklist, and denies the request if the IP has been blacklisted. +The following is an example of a permission class that checks the incoming request's IP address against a blocklist, and denies the request if the IP has been blocked. from rest_framework import permissions - class BlacklistPermission(permissions.BasePermission): + class BlocklistPermission(permissions.BasePermission): """ - Global permission check for blacklisted IPs. + Global permission check for blocked IPs. """ def has_permission(self, request, view): ip_addr = request.META['REMOTE_ADDR'] - blacklisted = Blacklist.objects.filter(ip_addr=ip_addr).exists() - return not blacklisted + blocked = Blocklist.objects.filter(ip_addr=ip_addr).exists() + return not blocked As well as global permissions, that are run against all incoming requests, you can also create object-level permissions, that are only run against operations that affect a particular object instance. For example: From 7d5155e0ebd770e95c20a103fb0874568b9d646a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 5 Aug 2020 14:13:01 +0100 Subject: [PATCH 330/907] Resize sponsor images for new GitHub layout (#7465) --- docs/img/premium/bitio-readme.png | Bin 27386 -> 18526 bytes docs/img/premium/cadre-readme.png | Bin 6989 -> 6933 bytes docs/img/premium/esg-readme.png | Bin 13590 -> 13473 bytes docs/img/premium/kloudless-readme.png | Bin 14582 -> 14501 bytes docs/img/premium/lightson-readme.png | Bin 16420 -> 16358 bytes docs/img/premium/release-history.png | Bin 17960 -> 17889 bytes docs/img/premium/retool-readme.png | Bin 9053 -> 8962 bytes docs/img/premium/rollbar-readme.png | Bin 17335 -> 17227 bytes docs/img/premium/sentry-readme.png | Bin 24549 -> 24504 bytes docs/img/premium/stream-readme.png | Bin 19281 -> 19213 bytes docs/index.md | 2 +- 11 files changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/img/premium/bitio-readme.png b/docs/img/premium/bitio-readme.png index 42959bb60c23dcca3f21f43ed55438ef75e48180..c47118cc6956b8a27a1be8edf3b84f8fa119e54c 100644 GIT binary patch delta 17602 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 delta 26532 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 diff --git a/docs/img/premium/esg-readme.png b/docs/img/premium/esg-readme.png index 50aec5f1f77da7e7f4bd046adbc812f6d589ab76..8f84e5669772aa94441876f3a9a46a45eba414a7 100644 GIT binary patch delta 12513 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%0AN0aY;f^nS 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 UyH455TMUq*teQ;KJLBO00X8LX=l}o! delta 13198 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 diff --git a/docs/img/premium/lightson-readme.png b/docs/img/premium/lightson-readme.png index 0de66562b293f337395c008cdd516b779a100ddb..9afc1eee952fe21701bbd07b59a3443d0e20255c 100644 GIT binary patch delta 12681 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 diff --git a/docs/img/premium/retool-readme.png b/docs/img/premium/retool-readme.png index b5dc3aee746f12e183a52eb067a8edd031ac9435..56adba04d3acc812f5499e19ecd9c72d3632eb32 100644 GIT binary patch literal 8962 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 literal 9053 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

    |5jz}4ReE`$VjeJN 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 Z#d<;6VmyH&8uLPCv%6c>JiQ|3<>*PxC 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 S-sJ11;bbKhCCbGO{Qn0dF5(pc literal 24549 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 Kbe*JW$bSHrTxjS3 diff --git a/docs/img/premium/stream-readme.png b/docs/img/premium/stream-readme.png index 0ca650eaa85c0b9675870ff6467d8e346c9e830b..15da6ba71d07b65483d340b7dd519c6171126eba 100644 GIT binary patch literal 19213 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 X{nT9-*dJL4I-tta)z4*}Q$iB}z?3lk literal 19281 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 diff --git a/docs/index.md b/docs/index.md index 7741f8af5f..c7b78e9c33 100644 --- a/docs/index.md +++ b/docs/index.md @@ -71,7 +71,7 @@ continued development by **[signing up for a paid plan][funding]**.

  • ESG
  • Rollbar
  • Retool
  • -
  • bit.io
  • +
  • bit.io
  • From 17f2b39bdb530c9c7e0722e41682788ea6019bde Mon Sep 17 00:00:00 2001 From: Masked Man Date: Thu, 6 Aug 2020 03:03:30 +0430 Subject: [PATCH 331/907] Fix docs issue (#7418) --- docs/api-guide/serializers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index ceec319ded..efa3256200 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -595,7 +595,7 @@ Normally if a `ModelSerializer` does not generate the fields you need by default ### `.serializer_field_mapping` -A mapping of Django model classes to REST framework serializer classes. You can override this mapping to alter the default serializer classes that should be used for each model class. +A mapping of Django model fields to REST framework serializer fields. You can override this mapping to alter the default serializer fields that should be used for each model field. ### `.serializer_related_field` From 0d2bbd317743f2459ccdf2bd750edb0c10261eaf Mon Sep 17 00:00:00 2001 From: Michael K Date: Wed, 5 Aug 2020 23:51:06 +0000 Subject: [PATCH 332/907] Fix Request docstring kwarg names (#7443) --- rest_framework/request.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/request.py b/rest_framework/request.py index 43cdf5e9b2..2a007cd2bb 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -143,9 +143,9 @@ class Request: Kwargs: - request(HttpRequest). The original request instance. - - parsers_classes(list/tuple). The parsers to use for parsing the + - parsers(list/tuple). The parsers to use for parsing the request content. - - authentication_classes(list/tuple). The authentications used to try + - authenticators(list/tuple). The authenticators used to try authenticating the request's user. """ From 1e383f103a1a84461e949e59b001ee61a2e0f7c4 Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Wed, 5 Aug 2020 21:29:47 -0700 Subject: [PATCH 333/907] Check extra action func.__name__ (#7098) --- rest_framework/viewsets.py | 13 ++++++++++- tests/test_viewsets.py | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index 9cb48729ec..5a1f8acf5b 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -33,6 +33,15 @@ def _is_extra_action(attr): return hasattr(attr, 'mapping') and isinstance(attr.mapping, MethodMapper) +def _check_attr_name(func, name): + assert func.__name__ == name, ( + 'Expected function (`{func.__name__}`) to match its attribute name ' + '(`{name}`). If using a decorator, ensure the inner function is ' + 'decorated with `functools.wraps`, or that `{func.__name__}.__name__` ' + 'is otherwise set to `{name}`.').format(func=func, name=name) + return func + + class ViewSetMixin: """ This is the magic. @@ -164,7 +173,9 @@ def get_extra_actions(cls): """ Get the methods that are marked as an extra ViewSet `@action`. """ - return [method for _, method in getmembers(cls, _is_extra_action)] + return [_check_attr_name(method, name) + for name, method + in getmembers(cls, _is_extra_action)] def get_extra_action_url_map(self): """ diff --git a/tests/test_viewsets.py b/tests/test_viewsets.py index 465806a091..2a2997a0bd 100644 --- a/tests/test_viewsets.py +++ b/tests/test_viewsets.py @@ -1,4 +1,5 @@ from collections import OrderedDict +from functools import wraps import pytest from django.conf.urls import include, url @@ -33,6 +34,13 @@ class Action(models.Model): pass +def decorate(fn): + @wraps(fn) + def wrapper(self, request, *args, **kwargs): + return fn(self, request, *args, **kwargs) + return wrapper + + class ActionViewSet(GenericViewSet): queryset = Action.objects.all() @@ -68,6 +76,16 @@ def custom_detail_action(self, request, *args, **kwargs): def unresolvable_detail_action(self, request, *args, **kwargs): raise NotImplementedError + @action(detail=False) + @decorate + def wrapped_list_action(self, request, *args, **kwargs): + raise NotImplementedError + + @action(detail=True) + @decorate + def wrapped_detail_action(self, request, *args, **kwargs): + raise NotImplementedError + class ActionNamesViewSet(GenericViewSet): @@ -191,6 +209,8 @@ def test_extra_actions(self): 'detail_action', 'list_action', 'unresolvable_detail_action', + 'wrapped_detail_action', + 'wrapped_list_action', ] self.assertEqual(actual, expected) @@ -204,9 +224,35 @@ def test_should_only_return_decorated_methods(self): 'detail_action', 'list_action', 'unresolvable_detail_action', + 'wrapped_detail_action', + 'wrapped_list_action', ] self.assertEqual(actual, expected) + def test_attr_name_check(self): + def decorate(fn): + def wrapper(self, request, *args, **kwargs): + return fn(self, request, *args, **kwargs) + return wrapper + + class ActionViewSet(GenericViewSet): + queryset = Action.objects.all() + + @action(detail=False) + @decorate + def wrapped_list_action(self, request, *args, **kwargs): + raise NotImplementedError + + view = ActionViewSet() + with pytest.raises(AssertionError) as excinfo: + view.get_extra_actions() + + assert str(excinfo.value) == ( + 'Expected function (`wrapper`) to match its attribute name ' + '(`wrapped_list_action`). If using a decorator, ensure the inner ' + 'function is decorated with `functools.wraps`, or that ' + '`wrapper.__name__` is otherwise set to `wrapped_list_action`.') + @override_settings(ROOT_URLCONF='tests.test_viewsets') class GetExtraActionUrlMapTests(TestCase): @@ -218,6 +264,7 @@ def test_list_view(self): expected = OrderedDict([ ('Custom list action', 'http://testserver/api/actions/custom_list_action/'), ('List action', 'http://testserver/api/actions/list_action/'), + ('Wrapped list action', 'http://testserver/api/actions/wrapped_list_action/'), ]) self.assertEqual(view.get_extra_action_url_map(), expected) @@ -229,6 +276,7 @@ def test_detail_view(self): expected = OrderedDict([ ('Custom detail action', 'http://testserver/api/actions/1/custom_detail_action/'), ('Detail action', 'http://testserver/api/actions/1/detail_action/'), + ('Wrapped detail action', 'http://testserver/api/actions/1/wrapped_detail_action/'), # "Unresolvable detail action" excluded, since it's not resolvable ]) From 3d708ac7005e29ca75aa68b447edb8d9dc111637 Mon Sep 17 00:00:00 2001 From: Vibhu Agarwal Date: Wed, 12 Aug 2020 05:06:23 +0530 Subject: [PATCH 334/907] Fix broken docs link (#7473) --- docs/topics/rest-hypermedia-hateoas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/rest-hypermedia-hateoas.md b/docs/topics/rest-hypermedia-hateoas.md index d48319a269..a11bd6cc72 100644 --- a/docs/topics/rest-hypermedia-hateoas.md +++ b/docs/topics/rest-hypermedia-hateoas.md @@ -34,7 +34,7 @@ REST framework also includes [serialization] and [parser]/[renderer] components What REST framework doesn't do is give you machine readable hypermedia formats such as [HAL][hal], [Collection+JSON][collection], [JSON API][json-api] or HTML [microformats] by default, or the ability to auto-magically create fully HATEOAS style APIs that include hypermedia-based form descriptions and semantically labelled hyperlinks. Doing so would involve making opinionated choices about API design that should really remain outside of the framework's scope. -[cite]: https://vimeo.com/channels/restfest/page:2 +[cite]: https://vimeo.com/channels/restfest/49503453 [dissertation]: https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm [hypertext-driven]: https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven [restful-web-apis]: http://restfulwebapis.org/ From 355afcf64b27a06c8b48c0291fa3eae61e0c3695 Mon Sep 17 00:00:00 2001 From: David Smith <39445562+smithdc1@users.noreply.github.com> Date: Mon, 17 Aug 2020 21:26:56 +0100 Subject: [PATCH 335/907] isort v5 (#7484) --- requirements/requirements-codestyle.txt | 2 +- rest_framework/compat.py | 5 +++-- runtests.py | 2 +- setup.cfg | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/requirements/requirements-codestyle.txt b/requirements/requirements-codestyle.txt index d9efde1d07..434fc41af1 100644 --- a/requirements/requirements-codestyle.txt +++ b/requirements/requirements-codestyle.txt @@ -4,4 +4,4 @@ flake8-tidy-imports==4.1.0 pycodestyle==2.5.0 # Sort and lint imports -isort==4.3.21 +isort==5.4.2 diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 611068a621..4bae7729f7 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -96,8 +96,8 @@ def apply_markdown(text): try: import pygments - from pygments.lexers import get_lexer_by_name, TextLexer from pygments.formatters import HtmlFormatter + from pygments.lexers import TextLexer, get_lexer_by_name def pygments_highlight(text, lang, style): lexer = get_lexer_by_name(lang, stripall=False) @@ -121,9 +121,10 @@ def pygments_css(style): # starting from this blogpost and modified to support current markdown extensions API # https://zerokspot.com/weblog/2008/06/18/syntax-highlighting-in-markdown-with-pygments/ - from markdown.preprocessors import Preprocessor import re + from markdown.preprocessors import Preprocessor + class CodeBlockPreprocessor(Preprocessor): pattern = re.compile( r'^\s*``` *([^\n]+)\n(.+?)^\s*```', re.M | re.S) diff --git a/runtests.py b/runtests.py index 19513cdbf4..82028ea32c 100755 --- a/runtests.py +++ b/runtests.py @@ -11,7 +11,7 @@ FLAKE8_ARGS = ['rest_framework', 'tests'] -ISORT_ARGS = ['--recursive', '--check-only', '--diff', 'rest_framework', 'tests'] +ISORT_ARGS = ['--check-only', '--diff', 'rest_framework', 'tests'] def exit_on_failure(ret, message=None): diff --git a/setup.cfg b/setup.cfg index 81da18b1c1..abb7cca908 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,7 +13,7 @@ banned-modules = json = use from rest_framework.utils import json! skip=.tox atomic=true multi_line_output=5 -known_standard_library=types +extra_standard_library=types known_third_party=pytest,_pytest,django,pytz,uritemplate known_first_party=rest_framework,tests From 374c0d414237708a28eccc412c078c06d0d82be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=80=D0=B8=D1=81=20=D0=92=D0=B5=D1=80=D1=85?= =?UTF-8?q?=D0=BE=D0=B2=D1=81=D0=BA=D0=B8=D0=B9?= Date: Thu, 20 Aug 2020 06:23:59 -0600 Subject: [PATCH 336/907] Punctuation fix (#7488) * Punctuation fix * more punctuation --- docs/api-guide/relations.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index ef6efec5ea..88b462e1a5 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -56,7 +56,7 @@ In order to explain the various types of relational fields, we'll use a couple o `StringRelatedField` may be used to represent the target of the relationship using its `__str__` method. -For example, the following serializer. +For example, the following serializer: class AlbumSerializer(serializers.ModelSerializer): tracks = serializers.StringRelatedField(many=True) @@ -65,7 +65,7 @@ For example, the following serializer. model = Album fields = ['album_name', 'artist', 'tracks'] -Would serialize to the following representation. +Would serialize to the following representation: { 'album_name': 'Things We Lost In The Fire', @@ -291,7 +291,7 @@ Would serialize to a nested representation like this: ## Writable nested serializers -By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create `create()` and/or `update()` methods in order to explicitly specify how the child relationships should be saved. +By default nested serializers are read-only. If you want to support write-operations to a nested serializer field you'll need to create `create()` and/or `update()` methods in order to explicitly specify how the child relationships should be saved: class TrackSerializer(serializers.ModelSerializer): class Meta: @@ -343,7 +343,7 @@ To provide a dynamic queryset based on the `context`, you can also override `.ge ## Example -For example, we could define a relational field to serialize a track to a custom string representation, using its ordering, title, and duration. +For example, we could define a relational field to serialize a track to a custom string representation, using its ordering, title, and duration: import time @@ -359,7 +359,7 @@ For example, we could define a relational field to serialize a track to a custom model = Album fields = ['album_name', 'artist', 'tracks'] -This custom field would then serialize to the following representation. +This custom field would then serialize to the following representation: { 'album_name': 'Sometimes I Wish We Were an Eagle', @@ -535,7 +535,7 @@ And the following two models, which may have associated tags: text = models.CharField(max_length=1000) tags = GenericRelation(TaggedItem) -We could define a custom field that could be used to serialize tagged instances, using the type of each instance to determine how it should be serialized. +We could define a custom field that could be used to serialize tagged instances, using the type of each instance to determine how it should be serialized: class TaggedObjectRelatedField(serializers.RelatedField): """ From 48c327c681da481b7c36c674307bd58adfa4286c Mon Sep 17 00:00:00 2001 From: Justin Duke Date: Mon, 24 Aug 2020 03:44:34 -0700 Subject: [PATCH 337/907] Fix schema typo (#7491) --- rest_framework/schemas/openapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 9774a94c76..8a8e267e0f 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -50,7 +50,7 @@ def check_duplicate_operation_id(self, paths): 'You have a duplicated operationId in your OpenAPI schema: {operation_id}\n' '\tRoute: {route1}, Method: {method1}\n' '\tRoute: {route2}, Method: {method2}\n' - '\tAn operationId has to be unique accros your schema. Your schema may not work in other tools.' + '\tAn operationId has to be unique across your schema. Your schema may not work in other tools.' .format( route1=ids[operation_id]['route'], method1=ids[operation_id]['method'], From e215db206a6a80432c4f2a5cb711fc858e9069b3 Mon Sep 17 00:00:00 2001 From: Vlad <38493865+VladSaichenko@users.noreply.github.com> Date: Tue, 25 Aug 2020 18:50:02 +0700 Subject: [PATCH 338/907] Updated url()'s with path() and re_path() (#7492) --- rest_framework/documentation.py | 6 +++--- rest_framework/routers.py | 7 +++---- rest_framework/urlpatterns.py | 7 +++---- rest_framework/urls.py | 6 +++--- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/rest_framework/documentation.py b/rest_framework/documentation.py index ce61fa6bff..53e5ab551a 100644 --- a/rest_framework/documentation.py +++ b/rest_framework/documentation.py @@ -1,4 +1,4 @@ -from django.conf.urls import include, url +from django.urls import include, path from rest_framework.renderers import ( CoreJSONRenderer, DocumentationRenderer, SchemaJSRenderer @@ -82,7 +82,7 @@ def include_docs_urls( permission_classes=permission_classes, ) urls = [ - url(r'^$', docs_view, name='docs-index'), - url(r'^schema.js$', schema_js_view, name='schema-js') + path('', docs_view, name='docs-index'), + path('schema.js', schema_js_view, name='schema-js') ] return include((urls, 'api-docs'), namespace='api-docs') diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 657ad67bce..e2afa573fe 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -16,9 +16,8 @@ import itertools from collections import OrderedDict, namedtuple -from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured -from django.urls import NoReverseMatch +from django.urls import NoReverseMatch, re_path from rest_framework import views from rest_framework.response import Response @@ -265,7 +264,7 @@ def get_urls(self): view = viewset.as_view(mapping, **initkwargs) name = route.name.format(basename=basename) - ret.append(url(regex, view, name=name)) + ret.append(re_path(regex, view, name=name)) return ret @@ -340,7 +339,7 @@ def get_urls(self): if self.include_root_view: view = self.get_api_root_view(api_urls=urls) - root_url = url(r'^$', view, name=self.root_view_name) + root_url = re_path(r'^$', view, name=self.root_view_name) urls.append(root_url) if self.include_format_suffixes: diff --git a/rest_framework/urlpatterns.py b/rest_framework/urlpatterns.py index efcfd8401f..bed5708eb8 100644 --- a/rest_framework/urlpatterns.py +++ b/rest_framework/urlpatterns.py @@ -1,5 +1,4 @@ -from django.conf.urls import include, url -from django.urls import URLResolver, path, register_converter +from django.urls import URLResolver, include, path, re_path, register_converter from django.urls.resolvers import RoutePattern from rest_framework.settings import api_settings @@ -52,7 +51,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_r route = str(urlpattern.pattern) new_pattern = path(route, include((patterns, app_name), namespace), kwargs) else: - new_pattern = url(regex, include((patterns, app_name), namespace), kwargs) + new_pattern = re_path(regex, include((patterns, app_name), namespace), kwargs) ret.append(new_pattern) else: @@ -72,7 +71,7 @@ def apply_suffix_patterns(urlpatterns, suffix_pattern, suffix_required, suffix_r route = str(urlpattern.pattern).rstrip('$').rstrip('/') + suffix_route new_pattern = path(route, view, kwargs, name) else: - new_pattern = url(regex, view, kwargs, name) + new_pattern = re_path(regex, view, kwargs, name) ret.append(new_pattern) diff --git a/rest_framework/urls.py b/rest_framework/urls.py index 482a0a3642..d9b858ebca 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -11,11 +11,11 @@ You should make sure your authentication settings include `SessionAuthentication`. """ -from django.conf.urls import url from django.contrib.auth import views +from django.urls import path app_name = 'rest_framework' urlpatterns = [ - url(r'^login/$', views.LoginView.as_view(template_name='rest_framework/login.html'), name='login'), - url(r'^logout/$', views.LogoutView.as_view(), name='logout'), + path('login/', views.LoginView.as_view(template_name='rest_framework/login.html'), name='login'), + path('logout/', views.LogoutView.as_view(), name='logout'), ] From 7f3a3557a050147dd2420aa41d1bf7ddd7f9818e Mon Sep 17 00:00:00 2001 From: johnthagen Date: Tue, 25 Aug 2020 18:15:17 -0400 Subject: [PATCH 339/907] Add drf_ujson2 reference (#7494) --- docs/api-guide/renderers.md | 5 +++-- docs/community/third-party-packages.md | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index a508a9ff99..ca3a29b82c 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -503,7 +503,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily ## UltraJSON -[UltraJSON][ultrajson] is an optimized C JSON encoder which can give significantly faster JSON rendering. [Jacob Haslehurst][hzy] maintains the [drf-ujson-renderer][drf-ujson-renderer] package which implements JSON rendering using the UJSON package. +[UltraJSON][ultrajson] is an optimized C JSON encoder which can give significantly faster JSON rendering. [Adam Mertz][Amertz08] maintains [drf_ujson2][drf_ujson2], a fork of the now unmaintained [drf-ujson-renderer][drf-ujson-renderer], which implements JSON rendering using the UJSON package. ## CamelCase JSON @@ -547,8 +547,9 @@ Comma-separated values are a plain-text tabular data format, that can be easily [djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack [djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv [ultrajson]: https://github.com/esnme/ultrajson -[hzy]: https://github.com/hzy +[Amertz08]: https://github.com/Amertz08 [drf-ujson-renderer]: https://github.com/gizmag/drf-ujson-renderer +[drf_ujson2]: https://github.com/Amertz08/drf_ujson2 [djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case [Django REST Pandas]: https://github.com/wq/django-rest-pandas [Pandas]: https://pandas.pydata.org/ diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index b47dc098fd..79cb56e01b 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -240,7 +240,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [djangorestframework-csv][djangorestframework-csv] - Provides CSV renderer support. * [djangorestframework-jsonapi][djangorestframework-jsonapi] - Provides a parser, renderer, serializers, and other tools to help build an API that is compliant with the jsonapi.org spec. -* [drf_ujson][drf_ujson] - Implements JSON rendering using the UJSON package. +* [drf_ujson2][drf_ujson2] - Implements JSON rendering using the UJSON package. * [rest-pandas][rest-pandas] - Pandas DataFrame-powered renderers including Excel, CSV, and SVG formats. * [djangorestframework-rapidjson][djangorestframework-rapidjson] - Provides rapidjson support with parser and renderer. @@ -313,7 +313,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack [djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case [djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv -[drf_ujson]: https://github.com/gizmag/drf-ujson-renderer +[drf_ujson2]: https://github.com/Amertz08/drf_ujson2 [rest-pandas]: https://github.com/wq/django-rest-pandas [djangorestframework-rapidjson]: https://github.com/allisson/django-rest-framework-rapidjson [djangorestframework-chain]: https://github.com/philipn/django-rest-framework-chain From b3e02592d019fc4c66b5f4b11a462691e5cc64f1 Mon Sep 17 00:00:00 2001 From: Bob Thomas Date: Thu, 3 Sep 2020 06:47:11 -0400 Subject: [PATCH 340/907] Add support for Django 3.1 JSONField (#7467) Django 3.1 adds a new generic JSONField to replace the PostgreSQL-specific one. This adds support for the new field type, which should behave the same as the existing PostgreSQL field. Django's new JSONField also includes support for a custom "decoder", so add support for that in the serializer field. --- rest_framework/fields.py | 3 ++- rest_framework/serializers.py | 9 +++++++-- rest_framework/utils/field_mapping.py | 3 ++- tests/test_model_serializer.py | 26 ++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index da2dd54bed..f218713f1a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1758,6 +1758,7 @@ class JSONField(Field): def __init__(self, *args, **kwargs): self.binary = kwargs.pop('binary', False) self.encoder = kwargs.pop('encoder', None) + self.decoder = kwargs.pop('decoder', None) super().__init__(*args, **kwargs) def get_value(self, dictionary): @@ -1777,7 +1778,7 @@ def to_internal_value(self, data): if self.binary or getattr(data, 'is_json_string', False): if isinstance(data, bytes): data = data.decode() - return json.loads(data) + return json.loads(data, cls=self.decoder) else: json.dumps(data, cls=self.encoder) except (TypeError, ValueError): diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 916f8bec44..439220b34c 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -884,6 +884,8 @@ class ModelSerializer(Serializer): models.GenericIPAddressField: IPAddressField, models.FilePathField: FilePathField, } + if hasattr(models, 'JSONField'): + serializer_field_mapping[models.JSONField] = JSONField if postgres_fields: serializer_field_mapping[postgres_fields.HStoreField] = HStoreField serializer_field_mapping[postgres_fields.ArrayField] = ListField @@ -1242,10 +1244,13 @@ def build_standard_field(self, field_name, model_field): # `allow_blank` is only valid for textual fields. field_kwargs.pop('allow_blank', None) - if postgres_fields and isinstance(model_field, postgres_fields.JSONField): + is_django_jsonfield = hasattr(models, 'JSONField') and isinstance(model_field, models.JSONField) + if (postgres_fields and isinstance(model_field, postgres_fields.JSONField)) or is_django_jsonfield: # Populate the `encoder` argument of `JSONField` instances generated - # for the PostgreSQL specific `JSONField`. + # for the model `JSONField`. field_kwargs['encoder'] = getattr(model_field, 'encoder', None) + if is_django_jsonfield: + field_kwargs['decoder'] = getattr(model_field, 'decoder', None) if postgres_fields and isinstance(model_field, postgres_fields.ArrayField): # Populate the `child` argument on `ListField` instances generated diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index ed270be5e3..c008495ccf 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -92,7 +92,8 @@ def get_field_kwargs(field_name, model_field): kwargs['allow_unicode'] = model_field.allow_unicode if isinstance(model_field, models.TextField) and not model_field.choices or \ - (postgres_fields and isinstance(model_field, postgres_fields.JSONField)): + (postgres_fields and isinstance(model_field, postgres_fields.JSONField)) or \ + (hasattr(models, 'JSONField') and isinstance(model_field, models.JSONField)): kwargs['style'] = {'base_template': 'textarea.html'} if isinstance(model_field, models.AutoField) or not model_field.editable: diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 51b8f2e22f..1733930a69 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -7,6 +7,7 @@ """ import datetime import decimal +import json # noqa import sys import tempfile from collections import OrderedDict @@ -478,6 +479,7 @@ class Meta: """) self.assertEqual(repr(TestSerializer()), expected) + @pytest.mark.skipif(hasattr(models, 'JSONField'), reason='has models.JSONField') def test_json_field(self): class JSONFieldModel(models.Model): json_field = postgres_fields.JSONField() @@ -496,6 +498,30 @@ class Meta: self.assertEqual(repr(TestSerializer()), expected) +class CustomJSONDecoder(json.JSONDecoder): + pass + + +@pytest.mark.skipif(not hasattr(models, 'JSONField'), reason='no models.JSONField') +class TestDjangoJSONFieldMapping(TestCase): + def test_json_field(self): + class JSONFieldModel(models.Model): + json_field = models.JSONField() + json_field_with_encoder = models.JSONField(encoder=DjangoJSONEncoder, decoder=CustomJSONDecoder) + + class TestSerializer(serializers.ModelSerializer): + class Meta: + model = JSONFieldModel + fields = ['json_field', 'json_field_with_encoder'] + + expected = dedent(""" + TestSerializer(): + json_field = JSONField(decoder=None, encoder=None, style={'base_template': 'textarea.html'}) + json_field_with_encoder = JSONField(decoder=, encoder=, style={'base_template': 'textarea.html'}) + """) + self.assertEqual(repr(TestSerializer()), expected) + + # Tests for relational field mappings. # ------------------------------------ From f323049ecc24b48b8453867d0d8952e8e0c1c003 Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Thu, 3 Sep 2020 03:49:15 -0700 Subject: [PATCH 341/907] Fix pk-only optimization for properties (#7142) * Add callable/prop tests for pk-only optimization * Fix related field pk-only optimization for props --- rest_framework/relations.py | 9 +++++-- tests/models.py | 9 +++++++ tests/test_relations_pk.py | 47 +++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 3a2a8fb4b5..3cd46379da 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -175,8 +175,13 @@ def get_attribute(self, instance): value = attribute_instance.serializable_value(self.source_attrs[-1]) if is_simple_callable(value): # Handle edge case where the relationship `source` argument - # points to a `get_relationship()` method on the model - value = value().pk + # points to a `get_relationship()` method on the model. + value = value() + + # Handle edge case where relationship `source` argument points + # to an instance instead of a pk (e.g., a `@property`). + value = getattr(value, 'pk', value) + return PKOnlyObject(pk=value) except AttributeError: pass diff --git a/tests/models.py b/tests/models.py index 2c340a3e61..afe649760a 100644 --- a/tests/models.py +++ b/tests/models.py @@ -37,6 +37,15 @@ class ManyToManySource(RESTFrameworkModel): class ForeignKeyTarget(RESTFrameworkModel): name = models.CharField(max_length=100) + def get_first_source(self): + """Used for testing related field against a callable.""" + return self.sources.all().order_by('pk')[0] + + @property + def first_source(self): + """Used for testing related field against a property.""" + return self.sources.all().order_by('pk')[0] + class UUIDForeignKeyTarget(RESTFrameworkModel): uuid = models.UUIDField(primary_key=True, default=uuid.uuid4) diff --git a/tests/test_relations_pk.py b/tests/test_relations_pk.py index 0da9da890a..7a4878a2bf 100644 --- a/tests/test_relations_pk.py +++ b/tests/test_relations_pk.py @@ -30,6 +30,25 @@ class Meta: fields = ('id', 'name', 'sources') +class ForeignKeyTargetCallableSourceSerializer(serializers.ModelSerializer): + first_source = serializers.PrimaryKeyRelatedField( + source='get_first_source', + read_only=True, + ) + + class Meta: + model = ForeignKeyTarget + fields = ('id', 'name', 'first_source') + + +class ForeignKeyTargetPropertySourceSerializer(serializers.ModelSerializer): + first_source = serializers.PrimaryKeyRelatedField(read_only=True) + + class Meta: + model = ForeignKeyTarget + fields = ('id', 'name', 'first_source') + + class ForeignKeySourceSerializer(serializers.ModelSerializer): class Meta: model = ForeignKeySource @@ -389,6 +408,34 @@ class Meta: assert len(queryset) == 1 +class PKRelationTests(TestCase): + + def setUp(self): + self.target = ForeignKeyTarget.objects.create(name='target-1') + ForeignKeySource.objects.create(name='source-1', target=self.target) + ForeignKeySource.objects.create(name='source-2', target=self.target) + + def test_relation_field_callable_source(self): + serializer = ForeignKeyTargetCallableSourceSerializer(self.target) + expected = { + 'id': 1, + 'name': 'target-1', + 'first_source': 1, + } + with self.assertNumQueries(1): + self.assertEqual(serializer.data, expected) + + def test_relation_field_property_source(self): + serializer = ForeignKeyTargetPropertySourceSerializer(self.target) + expected = { + 'id': 1, + 'name': 'target-1', + 'first_source': 1, + } + with self.assertNumQueries(1): + self.assertEqual(serializer.data, expected) + + class PKNullableForeignKeyTests(TestCase): def setUp(self): target = ForeignKeyTarget(name='target-1') From 35c0abf24ea4a6c38e6fc2ee22b4aabe73776783 Mon Sep 17 00:00:00 2001 From: Charles Date: Thu, 3 Sep 2020 19:51:03 +0900 Subject: [PATCH 342/907] Change Token.generate_key to a Classmethod (#7502) * adds classmethod decorator to generate_key and test to confirm change is acceptable * self -> cls --- rest_framework/authtoken/models.py | 3 ++- tests/authentication/test_authentication.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index f8a871bf1f..fd8a50e0e7 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -32,7 +32,8 @@ def save(self, *args, **kwargs): self.key = self.generate_key() return super().save(*args, **kwargs) - def generate_key(self): + @classmethod + def generate_key(cls): return binascii.hexlify(os.urandom(20)).decode() def __str__(self): diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py index 4760ea319c..e872fa744b 100644 --- a/tests/authentication/test_authentication.py +++ b/tests/authentication/test_authentication.py @@ -397,6 +397,10 @@ def test_generate_key_returns_string(self): key = token.generate_key() assert isinstance(key, str) + def test_generate_key_accessible_as_classmethod(self): + key = self.model.generate_key() + assert isinstance(key, str) + def test_token_login_json(self): """Ensure token login view using JSON POST works.""" client = APIClient(enforce_csrf_checks=True) From b8ab30683a03f8c6af2dda27ba893920ea83bc92 Mon Sep 17 00:00:00 2001 From: David Smith Date: Thu, 13 Aug 2020 21:33:25 +0100 Subject: [PATCH 343/907] default_app_config application variable is deprecated in Django 3.2 --- rest_framework/__init__.py | 6 +++++- rest_framework/authtoken/__init__.py | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 8f2bc4466f..483276e14a 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -7,6 +7,8 @@ \_| \_\____/\____/ \_/ |_| |_| \__,_|_| |_| |_|\___| \_/\_/ \___/|_| |_|\_| """ +import django + __title__ = 'Django REST framework' __version__ = '3.11.0' __author__ = 'Tom Christie' @@ -22,7 +24,9 @@ # Default datetime input and output formats ISO_8601 = 'iso-8601' -default_app_config = 'rest_framework.apps.RestFrameworkConfig' + +if django.VERSION < (3, 2): + default_app_config = 'rest_framework.apps.RestFrameworkConfig' class RemovedInDRF313Warning(DeprecationWarning): diff --git a/rest_framework/authtoken/__init__.py b/rest_framework/authtoken/__init__.py index 82f5b91711..285fe15c6b 100644 --- a/rest_framework/authtoken/__init__.py +++ b/rest_framework/authtoken/__init__.py @@ -1 +1,4 @@ -default_app_config = 'rest_framework.authtoken.apps.AuthTokenConfig' +import django + +if django.VERSION < (3, 2): + default_app_config = 'rest_framework.authtoken.apps.AuthTokenConfig' From d5461e93fea2367fb0a5c7ef28d49c6be150273c Mon Sep 17 00:00:00 2001 From: David Smith <39445562+smithdc1@users.noreply.github.com> Date: Sat, 5 Sep 2020 09:02:27 +0100 Subject: [PATCH 344/907] Bump flake8 to 3.8.3 (#7521) --- requirements/requirements-codestyle.txt | 4 ++-- rest_framework/renderers.py | 4 ++-- rest_framework/utils/field_mapping.py | 1 - tests/test_fields.py | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/requirements/requirements-codestyle.txt b/requirements/requirements-codestyle.txt index 434fc41af1..4f54d6e778 100644 --- a/requirements/requirements-codestyle.txt +++ b/requirements/requirements-codestyle.txt @@ -1,7 +1,7 @@ # PEP8 code linting, which we run on all commits. -flake8==3.7.9 +flake8==3.8.3 flake8-tidy-imports==4.1.0 -pycodestyle==2.5.0 +pycodestyle==2.6.0 # Sort and lint imports isort==5.4.2 diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index a96fa6e65d..c790879b9f 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -856,8 +856,8 @@ def get_context(self, data, request): return { 'document': data, 'langs': self.languages, - 'lang_htmls': ["rest_framework/docs/langs/%s.html" % l for l in self.languages], - 'lang_intro_htmls': ["rest_framework/docs/langs/%s-intro.html" % l for l in self.languages], + 'lang_htmls': ["rest_framework/docs/langs/%s.html" % language for language in self.languages], + 'lang_intro_htmls': ["rest_framework/docs/langs/%s-intro.html" % language for language in self.languages], 'code_style': pygments_css(self.code_style), 'request': request } diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index c008495ccf..4f8a4f1926 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -58,7 +58,6 @@ def get_detail_view_name(model): that refer to instances of the model. """ return '%(model_name)s-detail' % { - 'app_label': model._meta.app_label, 'model_name': model._meta.object_name.lower() } diff --git a/tests/test_fields.py b/tests/test_fields.py index b1ad1dc664..506e75905b 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -669,7 +669,7 @@ def test_disallow_unhashable_collection_types(self): for input_value in inputs: with pytest.raises(serializers.ValidationError) as exc_info: field.run_validation(input_value) - expected = ['Must be a valid boolean.'.format(input_value)] + expected = ['Must be a valid boolean.'] assert exc_info.value.detail == expected From 9990b5928139072c8e010c8b5c86616f0d40ef9d Mon Sep 17 00:00:00 2001 From: David Smith <39445562+smithdc1@users.noreply.github.com> Date: Mon, 7 Sep 2020 19:00:17 +0100 Subject: [PATCH 345/907] Dropped test compatibility shims for Django <2.2. (#7523) --- tests/conftest.py | 29 +++++++++++++---------------- tests/schemas/test_coreapi.py | 1 - tests/test_model_serializer.py | 22 +--------------------- tests/test_permissions.py | 7 ------- tests/test_urlpatterns.py | 9 --------- 5 files changed, 14 insertions(+), 54 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d28edeb8a3..ac29e4a429 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -67,22 +67,19 @@ def pytest_configure(config): ) # guardian is optional - # Note that for the test cases we're installing a version of django-guardian - # that's only compatible with Django 2.0+. - if django.VERSION >= (2, 0, 0): - try: - import guardian # NOQA - except ImportError: - pass - else: - settings.ANONYMOUS_USER_ID = -1 - settings.AUTHENTICATION_BACKENDS = ( - 'django.contrib.auth.backends.ModelBackend', - 'guardian.backends.ObjectPermissionBackend', - ) - settings.INSTALLED_APPS += ( - 'guardian', - ) + try: + import guardian # NOQA + except ImportError: + pass + else: + settings.ANONYMOUS_USER_ID = -1 + settings.AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', + 'guardian.backends.ObjectPermissionBackend', + ) + settings.INSTALLED_APPS += ( + 'guardian', + ) if config.getoption('--no-pkgroot'): sys.path.pop(0) diff --git a/tests/schemas/test_coreapi.py b/tests/schemas/test_coreapi.py index 403b3b6341..5f646258b5 100644 --- a/tests/schemas/test_coreapi.py +++ b/tests/schemas/test_coreapi.py @@ -456,7 +456,6 @@ def test_schema_for_regular_views(self): @unittest.skipUnless(coreapi, 'coreapi is not installed') -@unittest.skipUnless(path, 'needs Django 2') @override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) class TestSchemaGeneratorDjango2(TestCase): def setUp(self): diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 1733930a69..7da1b41ae5 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -12,7 +12,6 @@ import tempfile from collections import OrderedDict -import django import pytest from django.core.exceptions import ImproperlyConfigured from django.core.serializers.json import DjangoJSONEncoder @@ -63,7 +62,7 @@ class RegularFieldsModel(models.Model): email_field = models.EmailField(max_length=100) float_field = models.FloatField() integer_field = models.IntegerField() - null_boolean_field = models.NullBooleanField() + null_boolean_field = models.BooleanField(null=True, default=False) positive_integer_field = models.PositiveIntegerField() positive_small_integer_field = models.PositiveSmallIntegerField() slug_field = models.SlugField(max_length=100) @@ -218,25 +217,6 @@ class Meta: """) self.assertEqual(repr(TestSerializer()), expected) - # merge this into test_regular_fields / RegularFieldsModel when - # Django 2.1 is the minimum supported version - @pytest.mark.skipif(django.VERSION < (2, 1), reason='Django version < 2.1') - def test_nullable_boolean_field(self): - class NullableBooleanModel(models.Model): - field = models.BooleanField(null=True, default=False) - - class NullableBooleanSerializer(serializers.ModelSerializer): - class Meta: - model = NullableBooleanModel - fields = ['field'] - - expected = dedent(""" - NullableBooleanSerializer(): - field = BooleanField(allow_null=True, required=False) - """) - - self.assertEqual(repr(NullableBooleanSerializer()), expected) - def test_nullable_boolean_field_choices(self): class NullableBooleanChoicesModel(models.Model): CHECKLIST_OPTIONS = ( diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 232c72dd26..4e6cae4b81 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -2,7 +2,6 @@ import unittest from unittest import mock -import django from django.conf import settings from django.contrib.auth.models import AnonymousUser, Group, Permission, User from django.db import models @@ -248,12 +247,6 @@ class BasicPermModel(models.Model): class Meta: app_label = 'tests' - if django.VERSION < (2, 1): - permissions = ( - ('view_basicpermmodel', 'Can view basic perm model'), - # add, change, delete built in to django - ) - class BasicPermSerializer(serializers.ModelSerializer): class Meta: diff --git a/tests/test_urlpatterns.py b/tests/test_urlpatterns.py index ec19494b08..90820b5326 100644 --- a/tests/test_urlpatterns.py +++ b/tests/test_urlpatterns.py @@ -1,4 +1,3 @@ -import unittest from collections import namedtuple from django.conf.urls import include, url @@ -66,7 +65,6 @@ def test_trailing_slash(self): ] self._test_trailing_slash(urlpatterns) - @unittest.skipUnless(path, 'needs Django 2') def test_trailing_slash_django2(self): urlpatterns = [ path('test/', dummy_view), @@ -87,14 +85,12 @@ def test_format_suffix(self): ] self._test_format_suffix(urlpatterns) - @unittest.skipUnless(path, 'needs Django 2') def test_format_suffix_django2(self): urlpatterns = [ path('test', dummy_view), ] self._test_format_suffix(urlpatterns) - @unittest.skipUnless(path, 'needs Django 2') def test_format_suffix_django2_args(self): urlpatterns = [ path('convtest/', dummy_view), @@ -124,7 +120,6 @@ def test_default_args(self): ] self._test_default_args(urlpatterns) - @unittest.skipUnless(path, 'needs Django 2') def test_default_args_django2(self): urlpatterns = [ path('test', dummy_view, {'foo': 'bar'}), @@ -148,7 +143,6 @@ def test_included_urls(self): ] self._test_included_urls(urlpatterns) - @unittest.skipUnless(path, 'needs Django 2') def test_included_urls_django2(self): nested_patterns = [ path('path', dummy_view) @@ -158,7 +152,6 @@ def test_included_urls_django2(self): ] self._test_included_urls(urlpatterns) - @unittest.skipUnless(path, 'needs Django 2') def test_included_urls_django2_mixed(self): nested_patterns = [ path('path', dummy_view) @@ -168,7 +161,6 @@ def test_included_urls_django2_mixed(self): ] self._test_included_urls(urlpatterns) - @unittest.skipUnless(path, 'needs Django 2') def test_included_urls_django2_mixed_args(self): nested_patterns = [ path('path/', dummy_view), @@ -216,7 +208,6 @@ def test_allowed_formats(self): ] self._test_allowed_formats(urlpatterns) - @unittest.skipUnless(path, 'needs Django 2') def test_allowed_formats_django2(self): urlpatterns = [ path('test', dummy_view), From 410575dace9d64a53ec771ea5e21b40525ed0e95 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 8 Sep 2020 15:32:27 +0100 Subject: [PATCH 346/907] Replace all url() calls with path() or re_path() (#7512) * url() is deprecated in Django 3.1 * update given feedbacks on url() is deprecated in Django 3.1 * Fix test_urlpatterns.py to continue testing mixed re_path() and path() * Fix one missed reference Co-authored-by: sanjusci --- docs/api-guide/authentication.md | 4 +- docs/api-guide/filtering.md | 2 +- docs/api-guide/format-suffixes.md | 6 +- docs/api-guide/generic-views.md | 2 +- docs/api-guide/parsers.md | 2 +- docs/api-guide/routers.md | 14 ++--- docs/api-guide/schemas.md | 2 +- docs/community/3.5-announcement.md | 6 +- docs/community/3.6-announcement.md | 2 +- docs/community/3.9-announcement.md | 3 +- docs/coreapi/from-documenting-your-api.md | 4 +- docs/coreapi/schemas.md | 9 +-- docs/index.md | 2 +- docs/topics/api-clients.md | 2 +- .../4-authentication-and-permissions.md | 2 +- .../templates/rest_framework/docs/error.html | 2 +- rest_framework/urls.py | 2 +- rest_framework/versioning.py | 12 ++-- tests/authentication/test_authentication.py | 30 ++++----- tests/browsable_api/auth_urls.py | 6 +- tests/browsable_api/no_auth_urls.py | 4 +- .../test_browsable_nested_api.py | 4 +- tests/schemas/test_coreapi.py | 61 +++++++++--------- tests/schemas/test_managementcommand.py | 4 +- tests/schemas/test_openapi.py | 40 ++++++------ tests/test_api_client.py | 16 ++--- tests/test_atomic_requests.py | 4 +- tests/test_fields.py | 2 +- tests/test_htmlrenderer.py | 8 +-- tests/test_lazy_hyperlinks.py | 4 +- tests/test_metadata.py | 3 +- tests/test_middleware.py | 6 +- tests/test_relations.py | 4 +- tests/test_relations_hyperlink.py | 18 +++--- tests/test_renderers.py | 20 +++--- tests/test_request.py | 8 +-- tests/test_requests_client.py | 10 +-- tests/test_response.py | 20 +++--- tests/test_reverse.py | 5 +- tests/test_routers.py | 23 ++++--- tests/test_testing.py | 12 ++-- tests/test_urlpatterns.py | 63 +++++++------------ tests/test_utils.py | 14 ++--- tests/test_versioning.py | 30 ++++----- tests/test_viewsets.py | 4 +- tests/urls.py | 4 +- 46 files changed, 243 insertions(+), 262 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index ebb0ab4d69..5878040a48 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -199,7 +199,7 @@ When using `TokenAuthentication`, you may want to provide a mechanism for client from rest_framework.authtoken import views urlpatterns += [ - url(r'^api-token-auth/', views.obtain_auth_token) + path('api-token-auth/', views.obtain_auth_token) ] Note that the URL part of the pattern can be whatever you want to use. @@ -238,7 +238,7 @@ For example, you may return additional user information beyond the `token` value And in your `urls.py`: urlpatterns += [ - url(r'^api-token-auth/', CustomAuthToken.as_view()) + path('api-token-auth/', CustomAuthToken.as_view()) ] diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 8e3bd9ef5d..41c1341ddf 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -45,7 +45,7 @@ Another style of filtering might involve restricting the queryset based on some For example if your URL config contained an entry like this: - url('^purchases/(?P.+)/$', PurchaseList.as_view()), + re_path('^purchases/(?P.+)/$', PurchaseList.as_view()), You could then write a view that returned a purchase queryset filtered by the username portion of the URL: diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md index 04467b3d31..dfdf24953d 100644 --- a/docs/api-guide/format-suffixes.md +++ b/docs/api-guide/format-suffixes.md @@ -32,9 +32,9 @@ Example: from blog import views urlpatterns = [ - url(r'^/$', views.apt_root), - url(r'^comments/$', views.comment_list), - url(r'^comments/(?P[0-9]+)/$', views.comment_detail) + path('', views.apt_root), + path('comments/', views.comment_list), + path('comments//', views.comment_detail) ] urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'html']) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 4ff549f078..afc2cab563 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -45,7 +45,7 @@ For more complex cases you might also want to override various methods on the vi For very simple cases you might want to pass through any class attributes using the `.as_view()` method. For example, your URLconf might include something like the following entry: - url(r'^/users/', ListCreateAPIView.as_view(queryset=User.objects.all(), serializer_class=UserSerializer), name='user-list') + path('users/', ListCreateAPIView.as_view(queryset=User.objects.all(), serializer_class=UserSerializer), name='user-list') --- diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index a3bc74a2ba..e8f03de8bd 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -125,7 +125,7 @@ If it is called without a `filename` URL keyword argument, then the client must # urls.py urlpatterns = [ # ... - url(r'^upload/(?P[^/]+)$', FileUploadView.as_view()) + re_path(r'^upload/(?P[^/]+)$', FileUploadView.as_view()) ] --- diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 5f6802222f..8d8594eeea 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -63,7 +63,7 @@ For example, you can append `router.urls` to a list of existing views... router.register(r'accounts', AccountViewSet) urlpatterns = [ - url(r'^forgot-password/$', ForgotPasswordFormView.as_view()), + path('forgot-password/', ForgotPasswordFormView.as_view()), ] urlpatterns += router.urls @@ -71,22 +71,22 @@ For example, you can append `router.urls` to a list of existing views... Alternatively you can use Django's `include` function, like so... urlpatterns = [ - url(r'^forgot-password/$', ForgotPasswordFormView.as_view()), - url(r'^', include(router.urls)), + path('forgot-password', ForgotPasswordFormView.as_view()), + path('', include(router.urls)), ] You may use `include` with an application namespace: urlpatterns = [ - url(r'^forgot-password/$', ForgotPasswordFormView.as_view()), - url(r'^api/', include((router.urls, 'app_name'))), + path('forgot-password/', ForgotPasswordFormView.as_view()), + path('api/', include((router.urls, 'app_name'))), ] Or both an application and instance namespace: urlpatterns = [ - url(r'^forgot-password/$', ForgotPasswordFormView.as_view()), - url(r'^api/', include((router.urls, 'app_name'), namespace='instance_name')), + path('forgot-password/', ForgotPasswordFormView.as_view()), + path('api/', include((router.urls, 'app_name'), namespace='instance_name')), ] See Django's [URL namespaces docs][url-namespace-docs] and the [`include` API reference][include-api-reference] for more details. diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 402315ef9a..e39cd21a8e 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -114,7 +114,7 @@ The `get_schema_view()` helper takes the following keyword arguments: only want the `myproject.api` urls to be exposed in the schema: schema_url_patterns = [ - url(r'^api/', include('myproject.api.urls')), + path('api/', include('myproject.api.urls')), ] schema_view = get_schema_view( diff --git a/docs/community/3.5-announcement.md b/docs/community/3.5-announcement.md index cce2dd0501..91bfce4286 100644 --- a/docs/community/3.5-announcement.md +++ b/docs/community/3.5-announcement.md @@ -69,7 +69,7 @@ schema_view = get_schema_view( ) urlpatterns = [ - url(r'^swagger/$', schema_view), + path('swagger/', schema_view), ... ] ``` @@ -198,8 +198,8 @@ Make sure to include the view before your router urls. For example: schema_view = get_schema_view(title='Example API') urlpatterns = [ - url('^$', schema_view), - url(r'^', include(router.urls)), + path('', schema_view), + path('', include(router.urls)), ] ### Schema path representations diff --git a/docs/community/3.6-announcement.md b/docs/community/3.6-announcement.md index c41ad8ecbd..35704eb583 100644 --- a/docs/community/3.6-announcement.md +++ b/docs/community/3.6-announcement.md @@ -73,7 +73,7 @@ To install the API documentation, you'll need to include it in your projects URL urlpatterns = [ ... - url(r'^docs/', include_docs_urls(title=API_TITLE, description=API_DESCRIPTION)) + path('docs/', include_docs_urls(title=API_TITLE, description=API_DESCRIPTION)) ] Once installed you should see something a little like this: diff --git a/docs/community/3.9-announcement.md b/docs/community/3.9-announcement.md index 1cf4464d6f..fee6e69096 100644 --- a/docs/community/3.9-announcement.md +++ b/docs/community/3.9-announcement.md @@ -62,6 +62,7 @@ Here's an example of adding an OpenAPI schema to the URL conf: ```python from rest_framework.schemas import get_schema_view from rest_framework.renderers import JSONOpenAPIRenderer +from django.urls import path schema_view = get_schema_view( title='Server Monitoring API', @@ -70,7 +71,7 @@ schema_view = get_schema_view( ) urlpatterns = [ - url('^schema.json$', schema_view), + path('schema.json', schema_view), ... ] ``` diff --git a/docs/coreapi/from-documenting-your-api.md b/docs/coreapi/from-documenting-your-api.md index 9ac3be6864..604dfa6686 100644 --- a/docs/coreapi/from-documenting-your-api.md +++ b/docs/coreapi/from-documenting-your-api.md @@ -19,7 +19,7 @@ To install the API documentation, you'll need to include it in your project's UR urlpatterns = [ ... - url(r'^docs/', include_docs_urls(title='My API title')) + path('docs/', include_docs_urls(title='My API title')) ] This will include two different views: @@ -41,7 +41,7 @@ You may ensure views are given a `request` instance by calling `include_docs_url urlpatterns = [ ... # Generate schema with valid `request` instance: - url(r'^docs/', include_docs_urls(title='My API title', public=False)) + path('docs/', include_docs_urls(title='My API title', public=False)) ] diff --git a/docs/coreapi/schemas.md b/docs/coreapi/schemas.md index 69606f8532..e7a418b803 100644 --- a/docs/coreapi/schemas.md +++ b/docs/coreapi/schemas.md @@ -43,11 +43,12 @@ To add a dynamically generated schema view to your API, use `get_schema_view`. ```python from rest_framework.schemas import get_schema_view +from django.urls import path schema_view = get_schema_view(title="Example API") urlpatterns = [ - url('^schema$', schema_view), + path('schema', schema_view), ... ] ``` @@ -292,7 +293,7 @@ The simplest way to include a schema in your project is to use the schema_view = get_schema_view(title="Server Monitoring API") urlpatterns = [ - url('^$', schema_view), + path('', schema_view), ... ] @@ -358,7 +359,7 @@ List of url patterns to limit the schema introspection to. If you only want the to be exposed in the schema: schema_url_patterns = [ - url(r'^api/', include('myproject.api.urls')), + path('api/', include('myproject.api.urls')), ] schema_view = get_schema_view( @@ -411,7 +412,7 @@ return the schema. **urls.py:** urlpatterns = [ - url('/', schema_view), + path('', schema_view), ... ] diff --git a/docs/index.md b/docs/index.md index c7b78e9c33..54654c7c50 100644 --- a/docs/index.md +++ b/docs/index.md @@ -120,7 +120,7 @@ If you're intending to use the browsable API you'll probably also want to add RE urlpatterns = [ ... - url(r'^api-auth/', include('rest_framework.urls')) + path('api-auth/', include('rest_framework.urls')) ] Note that the URL path can be whatever you want. diff --git a/docs/topics/api-clients.md b/docs/topics/api-clients.md index b020c380a8..9b61eaf427 100644 --- a/docs/topics/api-clients.md +++ b/docs/topics/api-clients.md @@ -384,7 +384,7 @@ First, install the API documentation views. These will include the schema resour urlpatterns = [ ... - url(r'^docs/', include_docs_urls(title='My API service'), name='api-docs'), + path('docs/', include_docs_urls(title='My API service'), name='api-docs'), ] Once the API documentation URLs are installed, you'll be able to include both the required JavaScript resources. Note that the ordering of these two lines is important, as the schema loading requires CoreAPI to already be installed. diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 6808780fa7..79ce355c93 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -137,7 +137,7 @@ We can add a login view for use with the browsable API, by editing the URLconf i Add the following import at the top of the file: - from django.conf.urls import include + from django.urls import path, include And, at the end of the file, add a pattern to include the login and logout views for the browsable API. diff --git a/rest_framework/templates/rest_framework/docs/error.html b/rest_framework/templates/rest_framework/docs/error.html index 6afc4a88b6..694f88a150 100644 --- a/rest_framework/templates/rest_framework/docs/error.html +++ b/rest_framework/templates/rest_framework/docs/error.html @@ -48,7 +48,7 @@

    403 Forbidden.

    when including the docs urls:

    -   url(r'^docs/', include_docs_urls(title='Your API',
    +   path('docs/', include_docs_urls(title='Your API',
                                         authentication_classes=[],
                                         permission_classes=[])),
     
    diff --git a/rest_framework/urls.py b/rest_framework/urls.py index d9b858ebca..0aa301332b 100644 --- a/rest_framework/urls.py +++ b/rest_framework/urls.py @@ -6,7 +6,7 @@ urlpatterns = [ ... - url(r'^auth/', include('rest_framework.urls')) + path('auth/', include('rest_framework.urls')) ] You should make sure your authentication settings include `SessionAuthentication`. diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py index 8c35a1a58f..78cfc9dc81 100644 --- a/rest_framework/versioning.py +++ b/rest_framework/versioning.py @@ -60,8 +60,8 @@ class URLPathVersioning(BaseVersioning): An example URL conf for two views that accept two different versions. urlpatterns = [ - url(r'^(?P[v1|v2]+)/users/$', users_list, name='users-list'), - url(r'^(?P[v1|v2]+)/users/(?P[0-9]+)/$', users_detail, name='users-detail') + re_path(r'^(?P[v1|v2]+)/users/$', users_list, name='users-list'), + re_path(r'^(?P[v1|v2]+)/users/(?P[0-9]+)/$', users_detail, name='users-detail') ] GET /1.0/something/ HTTP/1.1 @@ -99,14 +99,14 @@ class NamespaceVersioning(BaseVersioning): # users/urls.py urlpatterns = [ - url(r'^/users/$', users_list, name='users-list'), - url(r'^/users/(?P[0-9]+)/$', users_detail, name='users-detail') + path('/users/', users_list, name='users-list'), + path('/users//', users_detail, name='users-detail') ] # urls.py urlpatterns = [ - url(r'^v1/', include('users.urls', namespace='v1')), - url(r'^v2/', include('users.urls', namespace='v2')) + path('v1/', include('users.urls', namespace='v1')), + path('v2/', include('users.urls', namespace='v2')) ] GET /1.0/something/ HTTP/1.1 diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py index e872fa744b..a73e0d79c7 100644 --- a/tests/authentication/test_authentication.py +++ b/tests/authentication/test_authentication.py @@ -2,10 +2,10 @@ import pytest from django.conf import settings -from django.conf.urls import include, url from django.contrib.auth.models import User from django.http import HttpResponse from django.test import TestCase, override_settings +from django.urls import include, path from rest_framework import ( HTTP_HEADER_ENCODING, exceptions, permissions, renderers, status @@ -47,34 +47,34 @@ def put(self, request): urlpatterns = [ - url( - r'^session/$', + path( + 'session/', MockView.as_view(authentication_classes=[SessionAuthentication]) ), - url( - r'^basic/$', + path( + 'basic/', MockView.as_view(authentication_classes=[BasicAuthentication]) ), - url( - r'^remote-user/$', + path( + 'remote-user/', MockView.as_view(authentication_classes=[RemoteUserAuthentication]) ), - url( - r'^token/$', + path( + 'token/', MockView.as_view(authentication_classes=[TokenAuthentication]) ), - url( - r'^customtoken/$', + path( + 'customtoken/', MockView.as_view(authentication_classes=[CustomTokenAuthentication]) ), - url( - r'^customkeywordtoken/$', + path( + 'customkeywordtoken/', MockView.as_view( authentication_classes=[CustomKeywordTokenAuthentication] ) ), - url(r'^auth-token/$', obtain_auth_token), - url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), + path('auth-token/', obtain_auth_token), + path('auth/', include('rest_framework.urls', namespace='rest_framework')), ] diff --git a/tests/browsable_api/auth_urls.py b/tests/browsable_api/auth_urls.py index 7530c5e408..151278cbdc 100644 --- a/tests/browsable_api/auth_urls.py +++ b/tests/browsable_api/auth_urls.py @@ -1,8 +1,8 @@ -from django.conf.urls import include, url +from django.urls import include, path from .views import MockView urlpatterns = [ - url(r'^$', MockView.as_view()), - url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), + path('', MockView.as_view()), + path('auth/', include('rest_framework.urls', namespace='rest_framework')), ] diff --git a/tests/browsable_api/no_auth_urls.py b/tests/browsable_api/no_auth_urls.py index 348bfe1c0c..65701c0653 100644 --- a/tests/browsable_api/no_auth_urls.py +++ b/tests/browsable_api/no_auth_urls.py @@ -1,7 +1,7 @@ -from django.conf.urls import url +from django.urls import path from .views import MockView urlpatterns = [ - url(r'^$', MockView.as_view()), + path('', MockView.as_view()), ] diff --git a/tests/browsable_api/test_browsable_nested_api.py b/tests/browsable_api/test_browsable_nested_api.py index f945d2a43c..a6c2ee6bd9 100644 --- a/tests/browsable_api/test_browsable_nested_api.py +++ b/tests/browsable_api/test_browsable_nested_api.py @@ -1,6 +1,6 @@ -from django.conf.urls import url from django.test import TestCase from django.test.utils import override_settings +from django.urls import path from rest_framework import serializers from rest_framework.generics import ListCreateAPIView @@ -23,7 +23,7 @@ class NestedSerializersView(ListCreateAPIView): urlpatterns = [ - url(r'^api/$', NestedSerializersView.as_view(), name='api'), + path('api/', NestedSerializersView.as_view(), name='api'), ] diff --git a/tests/schemas/test_coreapi.py b/tests/schemas/test_coreapi.py index 5f646258b5..7b1f15fefd 100644 --- a/tests/schemas/test_coreapi.py +++ b/tests/schemas/test_coreapi.py @@ -1,11 +1,10 @@ import unittest import pytest -from django.conf.urls import include, url from django.core.exceptions import PermissionDenied from django.http import Http404 from django.test import TestCase, override_settings -from django.urls import path +from django.urls import include, path from rest_framework import ( filters, generics, pagination, permissions, serializers @@ -145,8 +144,8 @@ def schema_view(request): router = DefaultRouter() router.register('example', ExampleViewSet, basename='example') urlpatterns = [ - url(r'^$', schema_view), - url(r'^', include(router.urls)) + path('', schema_view), + path('', include(router.urls)) ] @@ -407,9 +406,9 @@ def get(self, *args, **kwargs): class TestSchemaGenerator(TestCase): def setUp(self): self.patterns = [ - url(r'^example/?$', views.ExampleListView.as_view()), - url(r'^example/(?P\d+)/?$', views.ExampleDetailView.as_view()), - url(r'^example/(?P\d+)/sub/?$', views.ExampleDetailView.as_view()), + path('example/', views.ExampleListView.as_view()), + path('example//', views.ExampleDetailView.as_view()), + path('example//sub/', views.ExampleDetailView.as_view()), ] def test_schema_for_regular_views(self): @@ -513,9 +512,9 @@ def test_schema_for_regular_views(self): class TestSchemaGeneratorNotAtRoot(TestCase): def setUp(self): self.patterns = [ - url(r'^api/v1/example/?$', views.ExampleListView.as_view()), - url(r'^api/v1/example/(?P\d+)/?$', views.ExampleDetailView.as_view()), - url(r'^api/v1/example/(?P\d+)/sub/?$', views.ExampleDetailView.as_view()), + path('api/v1/example/', views.ExampleListView.as_view()), + path('api/v1/example//', views.ExampleDetailView.as_view()), + path('api/v1/example//sub/', views.ExampleDetailView.as_view()), ] def test_schema_for_regular_views(self): @@ -569,7 +568,7 @@ def setUp(self): router = DefaultRouter() router.register('example1', MethodLimitedViewSet, basename='example1') self.patterns = [ - url(r'^', include(router.urls)) + path('', include(router.urls)) ] def test_schema_for_regular_views(self): @@ -635,8 +634,8 @@ def setUp(self): router.register('example1', Http404ExampleViewSet, basename='example1') router.register('example2', PermissionDeniedExampleViewSet, basename='example2') self.patterns = [ - url('^example/?$', views.ExampleListView.as_view()), - url(r'^', include(router.urls)) + path('example/', views.ExampleListView.as_view()), + path('', include(router.urls)) ] def test_schema_for_regular_views(self): @@ -679,7 +678,7 @@ class ForeignKeySourceView(generics.CreateAPIView): class TestSchemaGeneratorWithForeignKey(TestCase): def setUp(self): self.patterns = [ - url(r'^example/?$', ForeignKeySourceView.as_view()), + path('example/', ForeignKeySourceView.as_view()), ] def test_schema_for_regular_views(self): @@ -725,7 +724,7 @@ class ManyToManySourceView(generics.CreateAPIView): class TestSchemaGeneratorWithManyToMany(TestCase): def setUp(self): self.patterns = [ - url(r'^example/?$', ManyToManySourceView.as_view()), + path('example/', ManyToManySourceView.as_view()), ] def test_schema_for_regular_views(self): @@ -1041,9 +1040,9 @@ def included_fbv(request): class SchemaGenerationExclusionTests(TestCase): def setUp(self): self.patterns = [ - url('^excluded-cbv/$', ExcludedAPIView.as_view()), - url('^excluded-fbv/$', excluded_fbv), - url('^included-fbv/$', included_fbv), + path('excluded-cbv/', ExcludedAPIView.as_view()), + path('excluded-fbv/', excluded_fbv), + path('included-fbv/', included_fbv), ] def test_schema_generator_excludes_correctly(self): @@ -1136,8 +1135,8 @@ def simple_fbv(request): pass patterns = [ - url(r'^test', simple_fbv), - url(r'^test/list/', simple_fbv), + path('test', simple_fbv), + path('test/list/', simple_fbv), ] generator = SchemaGenerator(title='Naming Colisions', patterns=patterns) @@ -1173,14 +1172,14 @@ def _verify_cbv_links(self, loc, url, methods=None, suffixes=None): def test_manually_routing_generic_view(self): patterns = [ - url(r'^test', NamingCollisionView.as_view()), - url(r'^test/retrieve/', NamingCollisionView.as_view()), - url(r'^test/update/', NamingCollisionView.as_view()), + path('test', NamingCollisionView.as_view()), + path('test/retrieve/', NamingCollisionView.as_view()), + path('test/update/', NamingCollisionView.as_view()), # Fails with method names: - url(r'^test/get/', NamingCollisionView.as_view()), - url(r'^test/put/', NamingCollisionView.as_view()), - url(r'^test/delete/', NamingCollisionView.as_view()), + path('test/get/', NamingCollisionView.as_view()), + path('test/put/', NamingCollisionView.as_view()), + path('test/delete/', NamingCollisionView.as_view()), ] generator = SchemaGenerator(title='Naming Colisions', patterns=patterns) @@ -1196,7 +1195,7 @@ def test_manually_routing_generic_view(self): def test_from_router(self): patterns = [ - url(r'from-router', include(naming_collisions_router.urls)), + path('from-router', include(naming_collisions_router.urls)), ] generator = SchemaGenerator(title='Naming Colisions', patterns=patterns) @@ -1228,8 +1227,8 @@ def test_from_router(self): def test_url_under_same_key_not_replaced(self): patterns = [ - url(r'example/(?P\d+)/$', BasicNamingCollisionView.as_view()), - url(r'example/(?P\w+)/$', BasicNamingCollisionView.as_view()), + path('example//', BasicNamingCollisionView.as_view()), + path('example//', BasicNamingCollisionView.as_view()), ] generator = SchemaGenerator(title='Naming Colisions', patterns=patterns) @@ -1245,8 +1244,8 @@ def simple_fbv(request): pass patterns = [ - url(r'^test/list/', simple_fbv), - url(r'^test/(?P\d+)/list/', simple_fbv), + path('test/list/', simple_fbv), + path('test//list/', simple_fbv), ] generator = SchemaGenerator(title='Naming Colisions', patterns=patterns) diff --git a/tests/schemas/test_managementcommand.py b/tests/schemas/test_managementcommand.py index 115f871e55..645eaf91d7 100644 --- a/tests/schemas/test_managementcommand.py +++ b/tests/schemas/test_managementcommand.py @@ -3,10 +3,10 @@ import tempfile 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.urls import path from rest_framework.compat import uritemplate, yaml from rest_framework.management.commands import generateschema @@ -20,7 +20,7 @@ def get(self, request): urlpatterns = [ - url(r'^$', FooView.as_view()) + path('', FooView.as_view()) ] diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index d483f3d456..8ea910dc1e 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -2,8 +2,8 @@ import warnings import pytest -from django.conf.urls import url from django.test import RequestFactory, TestCase, override_settings +from django.urls import path from django.utils.translation import gettext_lazy as _ from rest_framework import filters, generics, pagination, routers, serializers @@ -719,8 +719,8 @@ def test_repeat_operation_ids(self): def test_duplicate_operation_id(self): patterns = [ - url(r'^duplicate1/?$', views.ExampleOperationIdDuplicate1.as_view()), - url(r'^duplicate2/?$', views.ExampleOperationIdDuplicate2.as_view()), + path('duplicate1/', views.ExampleOperationIdDuplicate1.as_view()), + path('duplicate2/', views.ExampleOperationIdDuplicate2.as_view()), ] generator = SchemaGenerator(patterns=patterns) @@ -874,7 +874,7 @@ class ExampleStringTagsViewSet(views.ExampleGenericAPIView): schema = AutoSchema(tags=['example1', 'example2']) url_patterns = [ - url(r'^test/?$', ExampleStringTagsViewSet.as_view()), + path('test/', ExampleStringTagsViewSet.as_view()), ] generator = SchemaGenerator(patterns=url_patterns) schema = generator.get_schema(request=create_request('/')) @@ -911,8 +911,8 @@ class BranchAPIView(views.ExampleGenericAPIView): pass url_patterns = [ - url(r'^any-dash_underscore/?$', RestaurantAPIView.as_view()), - url(r'^restaurants/branches/?$', BranchAPIView.as_view()) + path('any-dash_underscore/', RestaurantAPIView.as_view()), + path('restaurants/branches/', BranchAPIView.as_view()) ] generator = SchemaGenerator(patterns=url_patterns) schema = generator.get_schema(request=create_request('/')) @@ -930,7 +930,7 @@ def test_override_settings(self): def test_paths_construction(self): """Construction of the `paths` key.""" patterns = [ - url(r'^example/?$', views.ExampleListView.as_view()), + path('example/', views.ExampleListView.as_view()), ] generator = SchemaGenerator(patterns=patterns) generator._initialise_endpoints() @@ -946,8 +946,8 @@ def test_paths_construction(self): def test_prefixed_paths_construction(self): """Construction of the `paths` key maintains a common prefix.""" patterns = [ - url(r'^v1/example/?$', views.ExampleListView.as_view()), - url(r'^v1/example/{pk}/?$', views.ExampleDetailView.as_view()), + path('v1/example/', views.ExampleListView.as_view()), + path('v1/example/{pk}/', views.ExampleDetailView.as_view()), ] generator = SchemaGenerator(patterns=patterns) generator._initialise_endpoints() @@ -959,8 +959,8 @@ def test_prefixed_paths_construction(self): def test_mount_url_prefixed_to_paths(self): patterns = [ - url(r'^example/?$', views.ExampleListView.as_view()), - url(r'^example/{pk}/?$', views.ExampleDetailView.as_view()), + path('example/', views.ExampleListView.as_view()), + path('example/{pk}/', views.ExampleDetailView.as_view()), ] generator = SchemaGenerator(patterns=patterns, url='/api') generator._initialise_endpoints() @@ -973,7 +973,7 @@ def test_mount_url_prefixed_to_paths(self): def test_schema_construction(self): """Construction of the top level dictionary.""" patterns = [ - url(r'^example/?$', views.ExampleListView.as_view()), + path('example/', views.ExampleListView.as_view()), ] generator = SchemaGenerator(patterns=patterns) @@ -995,7 +995,7 @@ def test_schema_with_no_paths(self): def test_schema_information(self): """Construction of the top level dictionary.""" patterns = [ - url(r'^example/?$', views.ExampleListView.as_view()), + path('example/', views.ExampleListView.as_view()), ] generator = SchemaGenerator(patterns=patterns, title='My title', version='1.2.3', description='My description') @@ -1009,7 +1009,7 @@ def test_schema_information(self): def test_schema_information_empty(self): """Construction of the top level dictionary.""" patterns = [ - url(r'^example/?$', views.ExampleListView.as_view()), + path('example/', views.ExampleListView.as_view()), ] generator = SchemaGenerator(patterns=patterns) @@ -1022,7 +1022,7 @@ def test_schema_information_empty(self): def test_serializer_model(self): """Construction of the top level dictionary.""" patterns = [ - url(r'^example/?$', views.ExampleGenericAPIViewModel.as_view()), + path('example/', views.ExampleGenericAPIViewModel.as_view()), ] generator = SchemaGenerator(patterns=patterns) @@ -1038,7 +1038,7 @@ def test_serializer_model(self): def test_authtoken_serializer(self): patterns = [ - url(r'^api-token-auth/', obtain_auth_token) + path('api-token-auth/', obtain_auth_token) ] generator = SchemaGenerator(patterns=patterns) @@ -1065,7 +1065,7 @@ def test_authtoken_serializer(self): def test_component_name(self): patterns = [ - url(r'^example/?$', views.ExampleAutoSchemaComponentName.as_view()), + path('example/', views.ExampleAutoSchemaComponentName.as_view()), ] generator = SchemaGenerator(patterns=patterns) @@ -1080,8 +1080,8 @@ def test_component_name(self): def test_duplicate_component_name(self): patterns = [ - url(r'^duplicate1/?$', views.ExampleAutoSchemaDuplicate1.as_view()), - url(r'^duplicate2/?$', views.ExampleAutoSchemaDuplicate2.as_view()), + path('duplicate1/', views.ExampleAutoSchemaDuplicate1.as_view()), + path('duplicate2/', views.ExampleAutoSchemaDuplicate2.as_view()), ] generator = SchemaGenerator(patterns=patterns) @@ -1104,7 +1104,7 @@ class ExampleView(generics.DestroyAPIView): schema = AutoSchema(operation_id_base='example') url_patterns = [ - url(r'^example/?$', ExampleView.as_view()), + path('example/', ExampleView.as_view()), ] generator = SchemaGenerator(patterns=url_patterns) schema = generator.get_schema(request=create_request('/')) diff --git a/tests/test_api_client.py b/tests/test_api_client.py index 74a3579e2f..976f10ed1d 100644 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -2,9 +2,9 @@ import tempfile import unittest -from django.conf.urls import url from django.http import HttpResponse from django.test import override_settings +from django.urls import path, re_path from rest_framework.compat import coreapi, coreschema from rest_framework.parsers import FileUploadParser @@ -178,13 +178,13 @@ def get(self, request): urlpatterns = [ - url(r'^$', SchemaView.as_view()), - url(r'^example/$', ListView.as_view()), - url(r'^example/(?P[0-9]+)/$', DetailView.as_view()), - url(r'^upload/$', UploadView.as_view()), - url(r'^download/$', DownloadView.as_view()), - url(r'^text/$', TextView.as_view()), - url(r'^headers/$', HeadersView.as_view()), + path('', SchemaView.as_view()), + path('example/', ListView.as_view()), + re_path(r'^example/(?P[0-9]+)/$', DetailView.as_view()), + path('upload/', UploadView.as_view()), + path('download/', DownloadView.as_view()), + path('text/', TextView.as_view()), + path('headers/', HeadersView.as_view()), ] diff --git a/tests/test_atomic_requests.py b/tests/test_atomic_requests.py index de04d2c069..15b41e02f4 100644 --- a/tests/test_atomic_requests.py +++ b/tests/test_atomic_requests.py @@ -1,9 +1,9 @@ import unittest -from django.conf.urls import url from django.db import connection, connections, transaction from django.http import Http404 from django.test import TestCase, TransactionTestCase, override_settings +from django.urls import path from rest_framework import status from rest_framework.exceptions import APIException @@ -44,7 +44,7 @@ def get(self, request, *args, **kwargs): urlpatterns = ( - url(r'^$', NonAtomicAPIExceptionView.as_view()), + path('', NonAtomicAPIExceptionView.as_view()), ) diff --git a/tests/test_fields.py b/tests/test_fields.py index 506e75905b..fdd570d8a6 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -697,7 +697,7 @@ class TestNullBooleanField(TestBooleanField): None: None, 'other': True } - field = serializers.NullBooleanField() + field = serializers.BooleanField(allow_null=True) class TestNullableBooleanField(TestNullBooleanField): diff --git a/tests/test_htmlrenderer.py b/tests/test_htmlrenderer.py index e31a9ced52..fa0f4efc61 100644 --- a/tests/test_htmlrenderer.py +++ b/tests/test_htmlrenderer.py @@ -1,10 +1,10 @@ import django.template.loader import pytest -from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.http import Http404 from django.template import TemplateDoesNotExist, engines from django.test import TestCase, override_settings +from django.urls import path from rest_framework import status from rest_framework.decorators import api_view, renderer_classes @@ -35,9 +35,9 @@ def not_found(request): urlpatterns = [ - url(r'^$', example), - url(r'^permission_denied$', permission_denied), - url(r'^not_found$', not_found), + path('', example), + path('permission_denied', permission_denied), + path('not_found', not_found), ] diff --git a/tests/test_lazy_hyperlinks.py b/tests/test_lazy_hyperlinks.py index cf3ee735fc..716d02d2a8 100644 --- a/tests/test_lazy_hyperlinks.py +++ b/tests/test_lazy_hyperlinks.py @@ -1,6 +1,6 @@ -from django.conf.urls import url from django.db import models from django.test import TestCase, override_settings +from django.urls import path from rest_framework import serializers from rest_framework.renderers import JSONRenderer @@ -29,7 +29,7 @@ def dummy_view(request): urlpatterns = [ - url(r'^example/(?P[0-9]+)/$', dummy_view, name='example-detail'), + path('example//', dummy_view, name='example-detail'), ] diff --git a/tests/test_metadata.py b/tests/test_metadata.py index e1a1fd3528..4abc0fc07e 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -311,7 +311,8 @@ class ExampleListSerializer(serializers.ListSerializer): class TestSimpleMetadataFieldInfo(TestCase): def test_null_boolean_field_info_type(self): options = metadata.SimpleMetadata() - field_info = options.get_field_info(serializers.NullBooleanField()) + field_info = options.get_field_info(serializers.BooleanField( + allow_null=True)) assert field_info['type'] == 'boolean' def test_related_field_choices(self): diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 28a5e558a1..6b2c91db72 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -1,7 +1,7 @@ -from django.conf.urls import url from django.contrib.auth.models import User from django.http import HttpRequest from django.test import override_settings +from django.urls import path from rest_framework.authentication import TokenAuthentication from rest_framework.authtoken.models import Token @@ -17,8 +17,8 @@ def post(self, request): urlpatterns = [ - url(r'^auth$', APIView.as_view(authentication_classes=(TokenAuthentication,))), - url(r'^post$', PostView.as_view()), + path('auth', APIView.as_view(authentication_classes=(TokenAuthentication,))), + path('post', PostView.as_view()), ] diff --git a/tests/test_relations.py b/tests/test_relations.py index 9f05e3b314..92aeecf6c4 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -2,9 +2,9 @@ import pytest from _pytest.monkeypatch import MonkeyPatch -from django.conf.urls import url from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist from django.test import override_settings +from django.urls import re_path from django.utils.datastructures import MultiValueDict from rest_framework import relations, serializers @@ -146,7 +146,7 @@ def test_pk_representation(self): urlpatterns = [ - url(r'^example/(?P.+)/$', lambda: None, name='example'), + re_path(r'^example/(?P.+)/$', lambda: None, name='example'), ] diff --git a/tests/test_relations_hyperlink.py b/tests/test_relations_hyperlink.py index 5ad0e31ff8..77e4cd95f6 100644 --- a/tests/test_relations_hyperlink.py +++ b/tests/test_relations_hyperlink.py @@ -1,5 +1,5 @@ -from django.conf.urls import url from django.test import TestCase, override_settings +from django.urls import path from rest_framework import serializers from rest_framework.test import APIRequestFactory @@ -17,14 +17,14 @@ def dummy_view(request, pk): urlpatterns = [ - url(r'^dummyurl/(?P[0-9]+)/$', dummy_view, name='dummy-url'), - url(r'^manytomanysource/(?P[0-9]+)/$', dummy_view, name='manytomanysource-detail'), - url(r'^manytomanytarget/(?P[0-9]+)/$', dummy_view, name='manytomanytarget-detail'), - url(r'^foreignkeysource/(?P[0-9]+)/$', dummy_view, name='foreignkeysource-detail'), - url(r'^foreignkeytarget/(?P[0-9]+)/$', dummy_view, name='foreignkeytarget-detail'), - url(r'^nullableforeignkeysource/(?P[0-9]+)/$', dummy_view, name='nullableforeignkeysource-detail'), - url(r'^onetoonetarget/(?P[0-9]+)/$', dummy_view, name='onetoonetarget-detail'), - url(r'^nullableonetoonesource/(?P[0-9]+)/$', dummy_view, name='nullableonetoonesource-detail'), + path('dummyurl//', dummy_view, name='dummy-url'), + path('manytomanysource//', dummy_view, name='manytomanysource-detail'), + path('manytomanytarget//', dummy_view, name='manytomanytarget-detail'), + path('foreignkeysource//', dummy_view, name='foreignkeysource-detail'), + path('foreignkeytarget//', dummy_view, name='foreignkeytarget-detail'), + path('nullableforeignkeysource//', dummy_view, name='nullableforeignkeysource-detail'), + path('onetoonetarget//', dummy_view, name='onetoonetarget-detail'), + path('nullableonetoonesource//', dummy_view, name='nullableonetoonesource-detail'), ] diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 71c5fb3f64..8271608e15 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -3,12 +3,12 @@ from collections.abc import MutableMapping 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.urls import include, path, re_path from django.utils.safestring import SafeText from django.utils.translation import gettext_lazy as _ @@ -111,14 +111,14 @@ def get(self, request, **kwargs): urlpatterns = [ - url(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])), - url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB])), - url(r'^cache$', MockGETView.as_view()), - url(r'^parseerror$', MockPOSTView.as_view(renderer_classes=[JSONRenderer, BrowsableAPIRenderer])), - url(r'^html$', HTMLView.as_view()), - url(r'^html1$', HTMLView1.as_view()), - url(r'^empty$', EmptyGETView.as_view()), - url(r'^api', include('rest_framework.urls', namespace='rest_framework')) + re_path(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB])), + path('', MockView.as_view(renderer_classes=[RendererA, RendererB])), + path('cache', MockGETView.as_view()), + path('parseerror', MockPOSTView.as_view(renderer_classes=[JSONRenderer, BrowsableAPIRenderer])), + path('html', HTMLView.as_view()), + path('html1', HTMLView1.as_view()), + path('empty', EmptyGETView.as_view()), + path('api', include('rest_framework.urls', namespace='rest_framework')) ] @@ -637,7 +637,7 @@ class AuthExampleViewSet(ExampleViewSet): router = SimpleRouter() router.register('examples', ExampleViewSet, basename='example') router.register('auth-examples', AuthExampleViewSet, basename='auth-example') - urlpatterns = [url(r'^api/', include(router.urls))] + urlpatterns = [path('api/', include(router.urls))] def setUp(self): self.renderer = BrowsableAPIRenderer() diff --git a/tests/test_request.py b/tests/test_request.py index be84fe5f97..4425c020fb 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -5,7 +5,6 @@ import tempfile import pytest -from django.conf.urls import url from django.contrib.auth import authenticate, login, logout from django.contrib.auth.middleware import AuthenticationMiddleware from django.contrib.auth.models import User @@ -13,6 +12,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.http.request import RawPostDataException from django.test import TestCase, override_settings +from django.urls import path from rest_framework import status from rest_framework.authentication import SessionAuthentication @@ -153,9 +153,9 @@ def post(self, request): urlpatterns = [ - url(r'^$', MockView.as_view()), - url(r'^echo/$', EchoView.as_view()), - url(r'^upload/$', FileUploadView.as_view()) + path('', MockView.as_view()), + path('echo/', EchoView.as_view()), + path('upload/', FileUploadView.as_view()) ] diff --git a/tests/test_requests_client.py b/tests/test_requests_client.py index 59b388c5a6..c8e7be6ee3 100644 --- a/tests/test_requests_client.py +++ b/tests/test_requests_client.py @@ -1,10 +1,10 @@ import unittest -from django.conf.urls import url from django.contrib.auth import authenticate, login from django.contrib.auth.models import User from django.shortcuts import redirect from django.test import override_settings +from django.urls import path from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie @@ -90,10 +90,10 @@ def post(self, request): urlpatterns = [ - url(r'^$', Root.as_view(), name='root'), - url(r'^headers/$', HeadersView.as_view(), name='headers'), - url(r'^session/$', SessionView.as_view(), name='session'), - url(r'^auth/$', AuthView.as_view(), name='auth'), + path('', Root.as_view(), name='root'), + path('headers/', HeadersView.as_view(), name='headers'), + path('session/', SessionView.as_view(), name='session'), + path('auth/', AuthView.as_view(), name='auth'), ] diff --git a/tests/test_response.py b/tests/test_response.py index d3a56d01b8..0d5528dc9a 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -1,5 +1,5 @@ -from django.conf.urls import include, url from django.test import TestCase, override_settings +from django.urls import include, path, re_path from rest_framework import generics, routers, serializers, status, viewsets from rest_framework.parsers import JSONParser @@ -117,15 +117,15 @@ class HTMLNewModelView(generics.ListCreateAPIView): urlpatterns = [ - url(r'^setbyview$', MockViewSettingContentType.as_view(renderer_classes=[RendererA, RendererB, RendererC])), - url(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])), - url(r'^$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])), - url(r'^html$', HTMLView.as_view()), - url(r'^json$', JSONView.as_view()), - url(r'^html1$', HTMLView1.as_view()), - url(r'^html_new_model$', HTMLNewModelView.as_view()), - url(r'^html_new_model_viewset', include(new_model_viewset_router.urls)), - url(r'^restframework', include('rest_framework.urls', namespace='rest_framework')) + path('setbyview', MockViewSettingContentType.as_view(renderer_classes=[RendererA, RendererB, RendererC])), + re_path(r'^.*\.(?P.+)$', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])), + path('', MockView.as_view(renderer_classes=[RendererA, RendererB, RendererC])), + path('html', HTMLView.as_view()), + path('json', JSONView.as_view()), + path('html1', HTMLView1.as_view()), + path('html_new_model', HTMLNewModelView.as_view()), + path('html_new_model_viewset', include(new_model_viewset_router.urls)), + path('restframework', include('rest_framework.urls', namespace='rest_framework')) ] diff --git a/tests/test_reverse.py b/tests/test_reverse.py index 9ab1667c52..b26b448c9d 100644 --- a/tests/test_reverse.py +++ b/tests/test_reverse.py @@ -1,6 +1,5 @@ -from django.conf.urls import url from django.test import TestCase, override_settings -from django.urls import NoReverseMatch +from django.urls import NoReverseMatch, path from rest_framework.reverse import reverse from rest_framework.test import APIRequestFactory @@ -13,7 +12,7 @@ def null_view(request): urlpatterns = [ - url(r'^view$', null_view, name='view'), + path('view', null_view, name='view'), ] diff --git a/tests/test_routers.py b/tests/test_routers.py index 007cb47683..f767a843d9 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -1,11 +1,10 @@ from collections import namedtuple import pytest -from django.conf.urls import include, url from django.core.exceptions import ImproperlyConfigured from django.db import models from django.test import TestCase, override_settings -from django.urls import resolve, reverse +from django.urls import include, path, resolve, reverse from rest_framework import permissions, serializers, viewsets from rest_framework.decorators import action @@ -118,7 +117,7 @@ class TestSimpleRouter(URLPatternsTestCase, TestCase): router.register('basics', BasicViewSet, basename='basic') urlpatterns = [ - url(r'^api/', include(router.urls)), + path('api/', include(router.urls)), ] def setUp(self): @@ -163,8 +162,8 @@ def test_register_after_accessing_urls(self): class TestRootView(URLPatternsTestCase, TestCase): urlpatterns = [ - url(r'^non-namespaced/', include(namespaced_router.urls)), - url(r'^namespaced/', include((namespaced_router.urls, 'namespaced'), namespace='namespaced')), + path('non-namespaced/', include(namespaced_router.urls)), + path('namespaced/', include((namespaced_router.urls, 'namespaced'), namespace='namespaced')), ] def test_retrieve_namespaced_root(self): @@ -181,8 +180,8 @@ class TestCustomLookupFields(URLPatternsTestCase, TestCase): Ensure that custom lookup fields are correctly routed. """ urlpatterns = [ - url(r'^example/', include(notes_router.urls)), - url(r'^example2/', include(kwarged_notes_router.urls)), + path('example/', include(notes_router.urls)), + path('example2/', include(kwarged_notes_router.urls)), ] def setUp(self): @@ -238,8 +237,8 @@ class TestLookupUrlKwargs(URLPatternsTestCase, TestCase): Setup a deep lookup_field, but map it to a simple URL kwarg. """ urlpatterns = [ - url(r'^example/', include(notes_router.urls)), - url(r'^example2/', include(kwarged_notes_router.urls)), + path('example/', include(notes_router.urls)), + path('example2/', include(kwarged_notes_router.urls)), ] def setUp(self): @@ -426,7 +425,7 @@ def test_inherited_list_and_detail_route_decorators(self): class TestEmptyPrefix(URLPatternsTestCase, TestCase): urlpatterns = [ - url(r'^empty-prefix/', include(empty_prefix_router.urls)), + path('empty-prefix/', include(empty_prefix_router.urls)), ] def test_empty_prefix_list(self): @@ -443,7 +442,7 @@ def test_empty_prefix_detail(self): class TestRegexUrlPath(URLPatternsTestCase, TestCase): urlpatterns = [ - url(r'^regex/', include(regex_url_path_router.urls)), + path('regex/', include(regex_url_path_router.urls)), ] def test_regex_url_path_list(self): @@ -462,7 +461,7 @@ def test_regex_url_path_detail(self): class TestViewInitkwargs(URLPatternsTestCase, TestCase): urlpatterns = [ - url(r'^example/', include(notes_router.urls)), + path('example/', include(notes_router.urls)), ] def test_suffix(self): diff --git a/tests/test_testing.py b/tests/test_testing.py index 8094bfd8d2..cc60e4f003 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,9 +1,9 @@ from io import BytesIO -from django.conf.urls import url from django.contrib.auth.models import User from django.shortcuts import redirect from django.test import TestCase, override_settings +from django.urls import path from rest_framework import fields, serializers from rest_framework.decorators import api_view @@ -47,10 +47,10 @@ def post_view(request): urlpatterns = [ - url(r'^view/$', view), - url(r'^session-view/$', session_view), - url(r'^redirect-view/$', redirect_view), - url(r'^post-view/$', post_view) + path('view/', view), + path('session-view/', session_view), + path('redirect-view/', redirect_view), + path('post-view/', post_view) ] @@ -284,7 +284,7 @@ def test_empty_request_content_type(self): class TestUrlPatternTestCase(URLPatternsTestCase): urlpatterns = [ - url(r'^$', view), + path('', view), ] @classmethod diff --git a/tests/test_urlpatterns.py b/tests/test_urlpatterns.py index 90820b5326..adcd0a7427 100644 --- a/tests/test_urlpatterns.py +++ b/tests/test_urlpatterns.py @@ -1,8 +1,7 @@ from collections import namedtuple -from django.conf.urls import include, url from django.test import TestCase -from django.urls import Resolver404, URLResolver, path, re_path +from django.urls import Resolver404, URLResolver, include, path, re_path from django.urls.resolvers import RegexPattern from rest_framework.test import APIRequestFactory @@ -61,7 +60,7 @@ def _test_trailing_slash(self, urlpatterns): def test_trailing_slash(self): urlpatterns = [ - url(r'^test/$', dummy_view), + path('test/', dummy_view), ] self._test_trailing_slash(urlpatterns) @@ -81,7 +80,7 @@ def _test_format_suffix(self, urlpatterns): def test_format_suffix(self): urlpatterns = [ - url(r'^test$', dummy_view), + path('test', dummy_view), ] self._test_format_suffix(urlpatterns) @@ -116,7 +115,7 @@ def _test_default_args(self, urlpatterns): def test_default_args(self): urlpatterns = [ - url(r'^test$', dummy_view, {'foo': 'bar'}), + path('test', dummy_view, {'foo': 'bar'}), ] self._test_default_args(urlpatterns) @@ -135,15 +134,6 @@ def _test_included_urls(self, urlpatterns): self._resolve_urlpatterns(urlpatterns, test_paths) def test_included_urls(self): - nested_patterns = [ - url(r'^path$', dummy_view) - ] - urlpatterns = [ - url(r'^test/', include(nested_patterns), {'foo': 'bar'}), - ] - self._test_included_urls(urlpatterns) - - def test_included_urls_django2(self): nested_patterns = [ path('path', dummy_view) ] @@ -152,44 +142,35 @@ def test_included_urls_django2(self): ] self._test_included_urls(urlpatterns) - def test_included_urls_django2_mixed(self): - nested_patterns = [ - path('path', dummy_view) - ] - urlpatterns = [ - url('^test/', include(nested_patterns), {'foo': 'bar'}), - ] - self._test_included_urls(urlpatterns) - - def test_included_urls_django2_mixed_args(self): + def test_included_urls_mixed(self): nested_patterns = [ path('path/', dummy_view), - url('^url/(?P[0-9]+)$', dummy_view) + re_path(r'^re_path/(?P[0-9]+)$', dummy_view) ] urlpatterns = [ - url('^purl/(?P[0-9]+)/', include(nested_patterns), {'foo': 'bar'}), + re_path(r'^pre_path/(?P[0-9]+)/', include(nested_patterns), {'foo': 'bar'}), path('ppath//', include(nested_patterns), {'foo': 'bar'}), ] test_paths = [ - # parent url() nesting child path() - URLTestPath('/purl/87/path/42', (), {'parent': '87', 'child': 42, 'foo': 'bar', }), - URLTestPath('/purl/87/path/42.api', (), {'parent': '87', 'child': 42, 'foo': 'bar', 'format': 'api'}), - URLTestPath('/purl/87/path/42.asdf', (), {'parent': '87', 'child': 42, 'foo': 'bar', 'format': 'asdf'}), + # parent re_path() nesting child path() + URLTestPath('/pre_path/87/path/42', (), {'parent': '87', 'child': 42, 'foo': 'bar', }), + URLTestPath('/pre_path/87/path/42.api', (), {'parent': '87', 'child': 42, 'foo': 'bar', 'format': 'api'}), + URLTestPath('/pre_path/87/path/42.asdf', (), {'parent': '87', 'child': 42, 'foo': 'bar', 'format': 'asdf'}), - # parent path() nesting child url() - URLTestPath('/ppath/87/url/42', (), {'parent': 87, 'child': '42', 'foo': 'bar', }), - URLTestPath('/ppath/87/url/42.api', (), {'parent': 87, 'child': '42', 'foo': 'bar', 'format': 'api'}), - URLTestPath('/ppath/87/url/42.asdf', (), {'parent': 87, 'child': '42', 'foo': 'bar', 'format': 'asdf'}), + # parent path() nesting child re_path() + URLTestPath('/ppath/87/re_path/42', (), {'parent': 87, 'child': '42', 'foo': 'bar', }), + URLTestPath('/ppath/87/re_path/42.api', (), {'parent': 87, 'child': '42', 'foo': 'bar', 'format': 'api'}), + URLTestPath('/ppath/87/re_path/42.asdf', (), {'parent': 87, 'child': '42', 'foo': 'bar', 'format': 'asdf'}), # parent path() nesting child path() URLTestPath('/ppath/87/path/42', (), {'parent': 87, 'child': 42, 'foo': 'bar', }), URLTestPath('/ppath/87/path/42.api', (), {'parent': 87, 'child': 42, 'foo': 'bar', 'format': 'api'}), URLTestPath('/ppath/87/path/42.asdf', (), {'parent': 87, 'child': 42, 'foo': 'bar', 'format': 'asdf'}), - # parent url() nesting child url() - URLTestPath('/purl/87/url/42', (), {'parent': '87', 'child': '42', 'foo': 'bar', }), - URLTestPath('/purl/87/url/42.api', (), {'parent': '87', 'child': '42', 'foo': 'bar', 'format': 'api'}), - URLTestPath('/purl/87/url/42.asdf', (), {'parent': '87', 'child': '42', 'foo': 'bar', 'format': 'asdf'}), + # parent re_path() nesting child re_path() + URLTestPath('/pre_path/87/re_path/42', (), {'parent': '87', 'child': '42', 'foo': 'bar', }), + URLTestPath('/pre_path/87/re_path/42.api', (), {'parent': '87', 'child': '42', 'foo': 'bar', 'format': 'api'}), + URLTestPath('/pre_path/87/re_path/42.asdf', (), {'parent': '87', 'child': '42', 'foo': 'bar', 'format': 'asdf'}), ] self._resolve_urlpatterns(urlpatterns, test_paths) @@ -202,13 +183,13 @@ def _test_allowed_formats(self, urlpatterns): ] self._resolve_urlpatterns(urlpatterns, test_paths, allowed=allowed_formats) - def test_allowed_formats(self): + def test_allowed_formats_re_path(self): urlpatterns = [ - url('^test$', dummy_view), + re_path(r'^test$', dummy_view), ] self._test_allowed_formats(urlpatterns) - def test_allowed_formats_django2(self): + def test_allowed_formats_path(self): urlpatterns = [ path('test', dummy_view), ] diff --git a/tests/test_utils.py b/tests/test_utils.py index 500c6a3fae..c72f680fe4 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,7 +1,7 @@ from unittest import mock -from django.conf.urls import url from django.test import TestCase, override_settings +from django.urls import path from rest_framework.decorators import action from rest_framework.routers import SimpleRouter @@ -64,12 +64,12 @@ def suffixed_action(self, request, *args, **kwargs): router = SimpleRouter() router.register(r'resources', ResourceViewSet) urlpatterns = [ - url(r'^$', Root.as_view()), - url(r'^resource/$', ResourceRoot.as_view()), - url(r'^resource/customname$', CustomNameResourceInstance.as_view()), - url(r'^resource/(?P[0-9]+)$', ResourceInstance.as_view()), - url(r'^resource/(?P[0-9]+)/$', NestedResourceRoot.as_view()), - url(r'^resource/(?P[0-9]+)/(?P[A-Za-z]+)$', NestedResourceInstance.as_view()), + path('', Root.as_view()), + path('resource/', ResourceRoot.as_view()), + path('resource/customname', CustomNameResourceInstance.as_view()), + path('resource/', ResourceInstance.as_view()), + path('resource//', NestedResourceRoot.as_view()), + path('resource//', NestedResourceInstance.as_view()), ] urlpatterns += router.urls diff --git a/tests/test_versioning.py b/tests/test_versioning.py index d4e269df30..d40d542290 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -1,6 +1,6 @@ import pytest -from django.conf.urls import include, url from django.test import override_settings +from django.urls import include, path, re_path from rest_framework import serializers, status, versioning from rest_framework.decorators import APIView @@ -144,14 +144,14 @@ class FakeResolverMatch: class TestURLReversing(URLPatternsTestCase, APITestCase): included = [ - url(r'^namespaced/$', dummy_view, name='another'), - url(r'^example/(?P\d+)/$', dummy_pk_view, name='example-detail') + path('namespaced/', dummy_view, name='another'), + path('example//', dummy_pk_view, name='example-detail') ] urlpatterns = [ - url(r'^v1/', include((included, 'v1'), namespace='v1')), - url(r'^another/$', dummy_view, name='another'), - url(r'^(?P[v1|v2]+)/another/$', dummy_view, name='another'), + path('v1/', include((included, 'v1'), namespace='v1')), + path('another/', dummy_view, name='another'), + re_path(r'^(?P[v1|v2]+)/another/$', dummy_view, name='another'), ] def test_reverse_unversioned(self): @@ -310,12 +310,12 @@ def test_missing_with_default_and_none_allowed(self): class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase): included = [ - url(r'^namespaced/(?P\d+)/$', dummy_pk_view, name='namespaced'), + path('namespaced//', dummy_pk_view, name='namespaced'), ] urlpatterns = [ - url(r'^v1/', include((included, 'v1'), namespace='v1')), - url(r'^v2/', include((included, 'v2'), namespace='v2')) + path('v1/', include((included, 'v1'), namespace='v1')), + path('v2/', include((included, 'v2'), namespace='v2')) ] def setUp(self): @@ -342,17 +342,17 @@ def test_bug_2489(self): class TestNamespaceVersioningHyperlinkedRelatedFieldScheme(URLPatternsTestCase, APITestCase): nested = [ - url(r'^namespaced/(?P\d+)/$', dummy_pk_view, name='nested'), + path('namespaced//', dummy_pk_view, name='nested'), ] included = [ - url(r'^namespaced/(?P\d+)/$', dummy_pk_view, name='namespaced'), - url(r'^nested/', include((nested, 'nested-namespace'), namespace='nested-namespace')) + path('namespaced//', dummy_pk_view, name='namespaced'), + path('nested/', include((nested, 'nested-namespace'), namespace='nested-namespace')) ] urlpatterns = [ - url(r'^v1/', include((included, 'restframeworkv1'), namespace='v1')), - url(r'^v2/', include((included, 'restframeworkv2'), namespace='v2')), - url(r'^non-api/(?P\d+)/$', dummy_pk_view, name='non-api-view') + path('v1/', include((included, 'restframeworkv1'), namespace='v1')), + path('v2/', include((included, 'restframeworkv2'), namespace='v2')), + path('non-api//', dummy_pk_view, name='non-api-view') ] def _create_field(self, view_name, version): diff --git a/tests/test_viewsets.py b/tests/test_viewsets.py index 2a2997a0bd..8842b0b1cd 100644 --- a/tests/test_viewsets.py +++ b/tests/test_viewsets.py @@ -2,9 +2,9 @@ from functools import wraps import pytest -from django.conf.urls import include, url from django.db import models from django.test import TestCase, override_settings +from django.urls import include, path from rest_framework import status from rest_framework.decorators import action @@ -124,7 +124,7 @@ class ActionViewSetWithMapping(ActionViewSet): urlpatterns = [ - url(r'^api/', include(router.urls)), + path('api/', include(router.urls)), ] diff --git a/tests/urls.py b/tests/urls.py index 76ada5e3d7..d9147683f2 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -3,14 +3,14 @@ We need only the docs urls for DocumentationRenderer tests. """ -from django.conf.urls import url +from django.urls import path from rest_framework.compat import coreapi from rest_framework.documentation import include_docs_urls if coreapi: urlpatterns = [ - url(r'^docs/', include_docs_urls(title='Test Suite API')), + path('docs/', include_docs_urls(title='Test Suite API')), ] else: urlpatterns = [] From ddcd8b539be4a5ffb7e6c89830bec9a24914ba55 Mon Sep 17 00:00:00 2001 From: Michael K Date: Tue, 8 Sep 2020 14:40:21 +0000 Subject: [PATCH 347/907] Run tests against Python 3.9 (#7517) 3.9.0 final is expected on Monday, 2020-10-05 See https://www.python.org/dev/peps/pep-0596/#schedule Also mention Django 3.1 compat where missing. --- .travis.yml | 3 +++ README.md | 4 ++-- docs/index.md | 4 ++-- setup.py | 1 + tox.ini | 6 +++--- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 39efaf4fcd..c204c54604 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,9 @@ matrix: - { python: "3.8", env: DJANGO=3.1 } - { python: "3.8", env: DJANGO=master } + - { python: "3.9-dev", env: DJANGO=3.1 } + - { python: "3.9-dev", env: DJANGO=master } + - { python: "3.8", env: TOXENV=base } - { python: "3.8", env: TOXENV=lint } - { python: "3.8", env: TOXENV=docs } diff --git a/README.md b/README.md index 4dfff31195..8af1466f8a 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,8 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements -* Python (3.5, 3.6, 3.7, 3.8) -* Django (2.2, 3.0) +* Python (3.5, 3.6, 3.7, 3.8, 3.9) +* Django (2.2, 3.0, 3.1) We **highly recommend** and only officially support the latest patch release of each Python and Django series. diff --git a/docs/index.md b/docs/index.md index 54654c7c50..0273da9f14 100644 --- a/docs/index.md +++ b/docs/index.md @@ -83,8 +83,8 @@ continued development by **[signing up for a paid plan][funding]**. REST framework requires the following: -* Python (3.5, 3.6, 3.7, 3.8) -* Django (2.2, 3.0) +* Python (3.5, 3.6, 3.7, 3.8, 3.9) +* Django (2.2, 3.0, 3.1) We **highly recommend** and only officially support the latest patch release of each Python and Django series. diff --git a/setup.py b/setup.py index 38e680e10f..e2a1c0222c 100755 --- a/setup.py +++ b/setup.py @@ -101,6 +101,7 @@ def get_version(package): 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3 :: Only', 'Topic :: Internet :: WWW/HTTP', ], diff --git a/tox.ini b/tox.ini index 190865f23f..d5e769764b 100644 --- a/tox.ini +++ b/tox.ini @@ -2,8 +2,8 @@ envlist = {py35,py36,py37}-django22, {py36,py37,py38}-django30, - {py36,py37,py38}-django31, - {py36,py37,py38}-djangomaster, + {py36,py37,py38,py39}-django31, + {py36,py37,py38,py39}-djangomaster, base,dist,lint,docs, [travis:env] @@ -22,7 +22,7 @@ setenv = deps = django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 - django31: Django>=3.1a1,<3.2 + django31: Django>=3.1,<3.2 djangomaster: https://github.com/django/django/archive/master.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 6b632c15b1bccf53287f81df143428b59d710241 Mon Sep 17 00:00:00 2001 From: Francisco Couzo Date: Tue, 8 Sep 2020 11:42:01 -0300 Subject: [PATCH 348/907] Remove unnecessary type conversions (#7526) --- rest_framework/pagination.py | 2 +- rest_framework/renderers.py | 2 +- rest_framework/serializers.py | 12 ++++-------- rest_framework/utils/urls.py | 4 ++-- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index a73f2e5267..60a57c8e4a 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -80,7 +80,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)) + idx for idx in sorted(included) if 0 < idx <= final ] diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index c790879b9f..0fb74ca004 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -329,7 +329,7 @@ def render_field(self, field, parent_style): if isinstance(field._field, serializers.HiddenField): return '' - style = dict(self.default_style[field]) + style = self.default_style[field].copy() style.update(field.style) if 'template_pack' not in style: style['template_pack'] = parent_style.get('template_pack', self.template_pack) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 439220b34c..3def1946cf 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -194,10 +194,7 @@ def save(self, **kwargs): "inspect 'serializer.validated_data' instead. " ) - validated_data = dict( - list(self.validated_data.items()) + - list(kwargs.items()) - ) + validated_data = {**self.validated_data, **kwargs} if self.instance is not None: self.instance = self.update(self.instance, validated_data) @@ -699,8 +696,7 @@ def save(self, **kwargs): ) validated_data = [ - dict(list(attrs.items()) + list(kwargs.items())) - for attrs in self.validated_data + {**attrs, **kwargs} for attrs in self.validated_data ] if self.instance is not None: @@ -1410,7 +1406,7 @@ def get_uniqueness_extra_kwargs(self, field_names, declared_fields, extra_kwargs # so long as all the field names are included on the serializer. for parent_class in [model] + list(model._meta.parents): for unique_together_list in parent_class._meta.unique_together: - if set(field_names).issuperset(set(unique_together_list)): + if set(field_names).issuperset(unique_together_list): unique_constraint_names |= set(unique_together_list) # Now we have all the field names that have uniqueness constraints @@ -1541,7 +1537,7 @@ def get_unique_together_validators(self): for parent_class in model_class_inheritance_tree: for unique_together in parent_class._meta.unique_together: # Skip if serializer does not map to all unique together sources - if not set(source_map).issuperset(set(unique_together)): + if not set(source_map).issuperset(unique_together): continue for source in unique_together: diff --git a/rest_framework/utils/urls.py b/rest_framework/utils/urls.py index 3534e5f49a..afb06994ce 100644 --- a/rest_framework/utils/urls.py +++ b/rest_framework/utils/urls.py @@ -11,7 +11,7 @@ def replace_query_param(url, key, val): (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url)) query_dict = parse.parse_qs(query, keep_blank_values=True) query_dict[force_str(key)] = [force_str(val)] - query = parse.urlencode(sorted(list(query_dict.items())), doseq=True) + query = parse.urlencode(sorted(query_dict.items()), doseq=True) return parse.urlunsplit((scheme, netloc, path, query, fragment)) @@ -23,5 +23,5 @@ def remove_query_param(url, key): (scheme, netloc, path, query, fragment) = parse.urlsplit(force_str(url)) query_dict = parse.parse_qs(query, keep_blank_values=True) query_dict.pop(key, None) - query = parse.urlencode(sorted(list(query_dict.items())), doseq=True) + query = parse.urlencode(sorted(query_dict.items()), doseq=True) return parse.urlunsplit((scheme, netloc, path, query, fragment)) From 327cbef29977bb999f292d3c8b7b3efc2491691d Mon Sep 17 00:00:00 2001 From: Rafael Henter Date: Tue, 8 Sep 2020 11:44:03 -0300 Subject: [PATCH 349/907] Add Django API Client to the third party packages (#7440) --- docs/community/third-party-packages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 79cb56e01b..336380aee0 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -274,6 +274,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [drf-viewset-profiler][drf-viewset-profiler] - Lib to profile all methods from a viewset line by line. * [djangorestframework-features][djangorestframework-features] - Advanced schema generation and more based on named features. * [django-elasticsearch-dsl-drf][django-elasticsearch-dsl-drf] - Integrate Elasticsearch DSL with Django REST framework. Package provides views, serializers, filter backends, pagination and other handy add-ons. +* [django-api-client][django-api-client] - DRF client that groups the Endpoint response, for use in CBVs and FBV as if you were working with Django's Native Models.. [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework @@ -358,3 +359,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [drf-viewset-profiler]: https://github.com/fvlima/drf-viewset-profiler [djangorestframework-features]: https://github.com/cloudcode-hungary/django-rest-framework-features/ [django-elasticsearch-dsl-drf]: https://github.com/barseghyanartur/django-elasticsearch-dsl-drf +[django-api-client]: https://github.com/rhenter/django-api-client From 04f39c42ee28bd462fcc9088909b18f2b321501d Mon Sep 17 00:00:00 2001 From: Mohammad Amin Haghpanah Date: Wed, 9 Sep 2020 12:24:05 +0430 Subject: [PATCH 350/907] Add drf-psq package to docs (#7451) * Add drf-psq to third party packages * Add drf-psq to permissions.md this package is an extension that gives support for having action-based **permission_classes**, **serializer_class**, and **queryset** dependent on permission-based rules. Co-authored-by: Salar Nasiri --- docs/api-guide/permissions.md | 6 ++++++ docs/community/third-party-packages.md | 2 ++ 2 files changed, 8 insertions(+) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index f317c12e66..ade1462572 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -312,6 +312,11 @@ The [Django REST Framework API Key][djangorestframework-api-key] package provide The [Django Rest Framework Role Filters][django-rest-framework-role-filters] package provides simple filtering over multiple types of roles. +## Django Rest Framework PSQ + +The [Django Rest Framework PSQ][drf-psq] package is an extension that gives support for having action-based **permission_classes**, **serializer_class**, and **queryset** dependent on permission-based rules. + + [cite]: https://developer.apple.com/library/mac/#documentation/security/Conceptual/AuthenticationAndAuthorizationGuide/Authorization/Authorization.html [authentication]: authentication.md [throttling]: throttling.md @@ -328,3 +333,4 @@ The [Django Rest Framework Role Filters][django-rest-framework-role-filters] pac [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 [drf-access-policy]: https://github.com/rsinger86/drf-access-policy +[drf-psq]: https://github.com/drf-psq/drf-psq diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 336380aee0..d4359890dd 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -198,6 +198,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [rest_condition][rest-condition] - Another extension for building complex permissions in a simple and convenient way. * [dry-rest-permissions][dry-rest-permissions] - Provides a simple way to define permissions for individual api actions. * [drf-access-policy][drf-access-policy] - Declarative and flexible permissions inspired by AWS' IAM policies. +* [drf-psq][drf-psq] - An extension that gives support for having action-based **permission_classes**, **serializer_class**, and **queryset** dependent on permission-based rules. ### Serializers @@ -360,3 +361,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [djangorestframework-features]: https://github.com/cloudcode-hungary/django-rest-framework-features/ [django-elasticsearch-dsl-drf]: https://github.com/barseghyanartur/django-elasticsearch-dsl-drf [django-api-client]: https://github.com/rhenter/django-api-client +[drf-psq]: https://github.com/drf-psq/drf-psq From e17779c47bccd218cfcdf1f3eaa61c354032ff71 Mon Sep 17 00:00:00 2001 From: johnthagen Date: Mon, 14 Sep 2020 13:46:51 -0400 Subject: [PATCH 351/907] Make it more clear to add django_filters to INSTALLED_APPS in docs. (#7535) --- docs/api-guide/filtering.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 41c1341ddf..d305ede6ba 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -145,10 +145,18 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering The [`django-filter`][django-filter-docs] library includes a `DjangoFilterBackend` class which supports highly customizable field filtering for REST framework. -To use `DjangoFilterBackend`, first install `django-filter`. Then add `django_filters` to Django's `INSTALLED_APPS` +To use `DjangoFilterBackend`, first install `django-filter`. pip install django-filter +Then add `'django_filters'` to Django's `INSTALLED_APPS`: + + INSTALLED_APPS = [ + ... + 'django_filters', + ... + ] + You should now either add the filter backend to your settings: REST_FRAMEWORK = { @@ -365,4 +373,4 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter] [django-url-filter]: https://github.com/miki725/django-url-filter [drf-url-filter]: https://github.com/manjitkumar/drf-url-filters [HStoreField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#hstorefield -[JSONField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#jsonfield \ No newline at end of file +[JSONField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#jsonfield From 98761e4ebb6c963aade674a568d010660302358e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=80=D0=B8=D1=81=20=D0=92=D0=B5=D1=80=D1=85?= =?UTF-8?q?=D0=BE=D0=B2=D1=81=D0=BA=D0=B8=D0=B9?= Date: Wed, 16 Sep 2020 09:43:27 -0400 Subject: [PATCH 352/907] Correct indentation --- docs/api-guide/views.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index 45226d57b5..2224c1f3a5 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -169,7 +169,7 @@ To override the default settings, REST framework provides a set of additional de from rest_framework.throttling import UserRateThrottle class OncePerDayUserThrottle(UserRateThrottle): - rate = '1/day' + rate = '1/day' @api_view(['GET']) @throttle_classes([OncePerDayUserThrottle]) From c6e24521dab27a7af8e8637a32b868ffa03dec2f Mon Sep 17 00:00:00 2001 From: "T. Franzel" Date: Mon, 21 Sep 2020 12:39:35 +0200 Subject: [PATCH 353/907] Add third-party schema library drf-spectacular to docs (#7540) Co-authored-by: T. Franzel <13507857+tfranzel@users.noreply.github.com> --- docs/coreapi/schemas.md | 7 +++++++ docs/topics/documenting-your-api.md | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/docs/coreapi/schemas.md b/docs/coreapi/schemas.md index e7a418b803..653105a7a1 100644 --- a/docs/coreapi/schemas.md +++ b/docs/coreapi/schemas.md @@ -828,10 +828,17 @@ 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-spectacular - Sane and flexible OpenAPI 3.0 schema generation for Django REST framework + +[drf-spectacular][drf-spectacular] is a [OpenAPI 3][open-api] schema generation tool with explicit focus on extensibility, +customizability and client generation. It's usage patterns are very similar to [drf-yasg][drf-yasg]. + [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/ +[drf-spectacular]: https://github.com/tfranzel/drf-spectacular/ [open-api]: https://openapis.org/ [json-hyperschema]: https://json-schema.org/latest/json-schema-hypermedia.html [api-blueprint]: https://apiblueprint.org/ diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md index 5c5872650d..7f39a9c331 100644 --- a/docs/topics/documenting-your-api.md +++ b/docs/topics/documenting-your-api.md @@ -142,6 +142,16 @@ This also translates into a very useful interactive documentation viewer in the ![Screenshot - drf-yasg][image-drf-yasg] +#### drf-spectacular - Sane and flexible OpenAPI 3.0 schema generation for Django REST framework + +[drf-spectacular][drf-spectacular] is a [OpenAPI 3][open-api] schema generation tool with explicit focus on extensibility, +customizability and client generation. Usage patterns are very similar to [drf-yasg][drf-yasg]. + +It aims to extract as much schema information as possible, while providing decorators and extensions for easy +customization. There is explicit support for [swagger-codegen][swagger], [SwaggerUI][swagger-ui] and [Redoc][redoc], +i18n, versioning, authentication, polymorphism (dynamic requests and responses), query/path/header parameters, +documentation and more. Several popular plugins for DRF are supported out-of-the-box as well. + --- ## Self describing APIs @@ -216,6 +226,7 @@ To implement a hypermedia API you'll need to decide on an appropriate media type [image-self-describing-api]: ../img/self-describing.png [drf-yasg]: https://github.com/axnsan12/drf-yasg/ +[drf-spectacular]: https://github.com/tfranzel/drf-spectacular/ [markdown]: https://daringfireball.net/projects/markdown/syntax [open-api]: https://openapis.org/ [redoc]: https://github.com/Rebilly/ReDoc From ae649336b110afe21b9429f2554052f31a9dfaf9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 23 Sep 2020 15:39:06 +0100 Subject: [PATCH 354/907] Drop urlize_quoted_links (#7548) --- rest_framework/renderers.py | 2 +- .../templates/rest_framework/base.html | 6 +- rest_framework/templatetags/rest_framework.py | 83 +------------------ tests/test_templatetags.py | 26 +++--- 4 files changed, 21 insertions(+), 96 deletions(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 0fb74ca004..3c4be8aeb0 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -418,7 +418,7 @@ def get_content(self, renderer, data, if render_style == 'binary': return '[%d bytes of binary content]' % len(content) - return content + return content.decode('utf-8') if isinstance(content, bytes) else content def show_form_for_method(self, view, method, request, obj): """ diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 9207f049b1..a88e1591c6 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -77,7 +77,7 @@
    {% block request_forms %} - + {% if 'GET' in allowed_methods %}
    @@ -176,9 +176,9 @@

    {{ name }}

    HTTP {{ response.status_code }} {{ response.status_text }}{% for key, val in response_headers|items %}
    -{{ key }}: {{ val|break_long_headers|urlize_quoted_links }}{% endfor %}
    +{{ key }}: {{ val|break_long_headers|urlize }}{% endfor %}
     
    -{{ content|urlize_quoted_links }}
    +{{ content|urlize }}
    diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 79dd953fff..7bfa8f5995 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -4,9 +4,9 @@ from django import template from django.template import loader from django.urls import NoReverseMatch, reverse -from django.utils.encoding import force_str, iri_to_uri +from django.utils.encoding import iri_to_uri from django.utils.html import escape, format_html, smart_urlquote -from django.utils.safestring import SafeData, mark_safe +from django.utils.safestring import mark_safe from rest_framework.compat import apply_markdown, pygments_highlight from rest_framework.renderers import HTMLFormRenderer @@ -311,85 +311,6 @@ def smart_urlquote_wrapper(matched_url): return None -@register.filter(needs_autoescape=True) -def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=True): - """ - Converts any URLs in text into clickable links. - - Works on http://, https://, www. links, and also on links ending in one of - the original seven gTLDs (.com, .edu, .gov, .int, .mil, .net, and .org). - Links can have trailing punctuation (periods, commas, close-parens) and - leading punctuation (opening parens) and it'll still do the right thing. - - If trim_url_limit is not None, the URLs in link text longer than this limit - will truncated to trim_url_limit-3 characters and appended with an ellipsis. - - If nofollow is True, the URLs in link text will get a rel="nofollow" - attribute. - - If autoescape is True, the link text and URLs will get autoescaped. - """ - def trim_url(x, limit=trim_url_limit): - 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_str(text)) - for i, word in enumerate(words): - if '.' in word or '@' in word or ':' in word: - # Deal with punctuation. - lead, middle, trail = '', word, '' - for punctuation in TRAILING_PUNCTUATION: - if middle.endswith(punctuation): - middle = middle[:-len(punctuation)] - trail = punctuation + trail - for opening, closing in WRAPPING_PUNCTUATION: - if middle.startswith(opening): - middle = middle[len(opening):] - lead = lead + opening - # Keep parentheses at the end only if they're balanced. - if ( - middle.endswith(closing) and - middle.count(closing) == middle.count(opening) + 1 - ): - middle = middle[:-len(closing)] - trail = closing + trail - - # Make URL we want to point to. - url = None - nofollow_attr = ' rel="nofollow"' if nofollow else '' - if simple_url_re.match(middle): - url = smart_urlquote_wrapper(middle) - elif simple_url_2_re.match(middle): - url = smart_urlquote_wrapper('http://%s' % middle) - elif ':' not in middle and simple_email_re.match(middle): - local, domain = middle.rsplit('@', 1) - try: - domain = domain.encode('idna').decode('ascii') - except UnicodeError: - continue - url = 'mailto:%s@%s' % (local, domain) - nofollow_attr = '' - - # Make link. - if url: - trimmed = trim_url(middle) - lead, trail = conditional_escape(lead), conditional_escape(trail) - url, trimmed = conditional_escape(url), conditional_escape(trimmed) - middle = '%s' % (url, nofollow_attr, trimmed) - words[i] = '%s%s%s' % (lead, middle, trail) - else: - words[i] = conditional_escape(word) - else: - words[i] = conditional_escape(word) - return mark_safe(''.join(words)) - - @register.filter def break_long_headers(header): """ diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index 28d6b4011f..cdf10ab1e2 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -2,13 +2,14 @@ from django.template import Context, Template from django.test import TestCase +from django.utils.html import urlize from rest_framework.compat import coreapi, coreschema from rest_framework.relations import Hyperlink from rest_framework.templatetags import rest_framework from rest_framework.templatetags.rest_framework import ( add_nested_class, add_query_param, as_string, break_long_headers, - format_value, get_pagination_html, schema_links, urlize_quoted_links + format_value, get_pagination_html, schema_links ) from rest_framework.test import APIRequestFactory @@ -246,7 +247,7 @@ class Issue1386Tests(TestCase): def test_issue_1386(self): """ - Test function urlize_quoted_links with different args + Test function urlize with different args """ correct_urls = [ "asdf.com", @@ -255,7 +256,7 @@ def test_issue_1386(self): "as.d8f.ghj8.gov", ] for i in correct_urls: - res = urlize_quoted_links(i) + res = urlize(i) self.assertNotEqual(res, i) self.assertIn(i, res) @@ -264,11 +265,11 @@ def test_issue_1386(self): "asdf.netnet", ] for i in incorrect_urls: - res = urlize_quoted_links(i) + res = urlize(i) self.assertEqual(i, res) # example from issue #1386, this shouldn't raise an exception - urlize_quoted_links("asdf:[/p]zxcv.com") + urlize("asdf:[/p]zxcv.com") def test_smart_urlquote_wrapper_handles_value_error(self): def mock_smart_urlquote(url): @@ -289,7 +290,10 @@ def _urlize_dict_check(self, data): For all items in dict test assert that the value is urlized key """ for original, urlized in data.items(): - assert urlize_quoted_links(original, nofollow=False) == urlized + print('====') + print(repr(urlize(original, nofollow=False))) + print(repr(urlized)) + assert urlize(original, nofollow=False) == urlized def test_json_with_url(self): """ @@ -297,26 +301,26 @@ def test_json_with_url(self): """ data = {} data['"url": "http://api/users/1/", '] = \ - '"url": "http://api/users/1/", ' + '"url": "http://api/users/1/", ' data['"foo_set": [\n "http://api/foos/1/"\n], '] = \ - '"foo_set": [\n "http://api/foos/1/"\n], ' + '"foo_set": [\n "http://api/foos/1/"\n], ' self._urlize_dict_check(data) def test_template_render_with_autoescape(self): """ Test that HTML is correctly escaped in Browsable API views. """ - template = Template("{% load rest_framework %}{{ content|urlize_quoted_links }}") + template = Template("{% load rest_framework %}{{ content|urlize }}") rendered = template.render(Context({'content': ' http://example.com'})) assert rendered == '<script>alert()</script>' \ ' http://example.com' def test_template_render_with_noautoescape(self): """ - Test if the autoescape value is getting passed to urlize_quoted_links filter. + Test if the autoescape value is getting passed to urlize filter. """ template = Template("{% load rest_framework %}" - "{% autoescape off %}{{ content|urlize_quoted_links }}" + "{% autoescape off %}{{ content|urlize }}" "{% endautoescape %}") rendered = template.render(Context({'content': ' "http://example.com" '})) assert rendered == ' "http://example.com" ' From 9ee67bbff77638316b6f1455d5dff38637a02306 Mon Sep 17 00:00:00 2001 From: Esieboma Jeremiah <57163971+esiebomaj@users.noreply.github.com> Date: Mon, 28 Sep 2020 08:52:17 +0100 Subject: [PATCH 355/907] corrects typographical error in line 118 (#7553) --- rest_framework/serializers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 3def1946cf..49eec82591 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -115,7 +115,7 @@ def __init__(self, instance=None, data=empty, **kwargs): super().__init__(**kwargs) def __new__(cls, *args, **kwargs): - # We override this method in order to automagically create + # We override this method in order to automatically create # `ListSerializer` classes instead when `many=True` is set. if kwargs.pop('many', False): return cls.many_init(*args, **kwargs) From 6f7aad8ffa96226672eacb82ab003dcf01070ed3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 28 Sep 2020 10:47:50 +0100 Subject: [PATCH 356/907] Placeholder for 3.12 release (#7379) * Placeholder for 3.12 release * Updating release notes * Updating release notes * Updating release notes * Update release notes * Fix typo * Basic structure for release announcement * 3.12 release notes * Version 3.12.0 --- docs/community/3.12-announcement.md | 169 ++++++++++++++++++++++++++++ docs/community/release-notes.md | 49 +++++++- mkdocs.yml | 1 + rest_framework/__init__.py | 2 +- 4 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 docs/community/3.12-announcement.md diff --git a/docs/community/3.12-announcement.md b/docs/community/3.12-announcement.md new file mode 100644 index 0000000000..9d2220933c --- /dev/null +++ b/docs/community/3.12-announcement.md @@ -0,0 +1,169 @@ + + +# Django REST framework 3.12 + +REST framework 3.12 brings a handful of refinements to the OpenAPI schema +generation, plus support for Django's new database-agnostic `JSONField`, +and some improvements to the `SearchFilter` class. + +## Grouping operations with tags. + +Open API schemas will now automatically include tags, based on the first element +in the URL path. + +For example... + +Method | Path | Tags +--------------------------------|-----------------|------------- +`GET`, `PUT`, `PATCH`, `DELETE` | `/users/{id}/` | `['users']` +`GET`, `POST` | `/users/` | `['users']` +`GET`, `PUT`, `PATCH`, `DELETE` | `/orders/{id}/` | `['orders']` +`GET`, `POST` | `/orders/` | `['orders']` + +The tags used for a particular view may also be overridden... + +```python +class MyOrders(APIView): + schema = AutoSchema(tags=['users', 'orders']) + ... +``` + +See [the schema documentation](https://www.django-rest-framework.org/api-guide/schemas/#grouping-operations-with-tags) for more information. + +## Customizing the operation ID. + +REST framework automatically determines operation IDs to use in OpenAPI +schemas. The latest version provides more control for overriding the behaviour +used to generate the operation IDs. + +See [the schema documentation](https://www.django-rest-framework.org/api-guide/schemas/#operationid) for more information. + +## Support for OpenAPI components. + +In order to output more graceful OpenAPI schemes, REST framework 3.12 now +defines components in the schema, and then references them inside request +and response objects. This is in contrast with the previous approach, which +fully expanded the request and response bodies for each operation. + +The names used for a component default to using the serializer class name, [but +may be overridden if needed](https://www.django-rest-framework.org/api-guide/schemas/#components +)... + +```python +class MyOrders(APIView): + schema = AutoSchema(component_name="OrderDetails") +``` + +## More Public API + +Many methods on the `AutoSchema` class have now been promoted to public API, +allowing you to more fully customize the schema generation. The following methods +are now available for overriding... + +* `get_path_parameters` +* `get_pagination_parameters` +* `get_filter_parameters` +* `get_request_body` +* `get_responses` +* `get_serializer` +* `get_paginator` +* `map_serializer` +* `map_field` +* `map_choice_field` +* `map_field_validators` +* `allows_filters`. + +See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#per-view-customization) +for details on using custom `AutoSchema` subclasses. + +## Support for JSONField. + +Django 3.1 deprecated the existing `django.contrib.postgres.fields.JSONField` +in favour of a new database-agnositic `JSONField`. + +REST framework 3.12 now supports this new model field, and `ModelSerializer` +classes will correctly map the model field. + +## SearchFilter improvements + +There are a couple of significant improvements to the `SearchFilter` class. + +### Nested searches against JSONField and HStoreField + +The class now supports nested search within `JSONField` and `HStoreField`, using +the double underscore notation for traversing which element of the field the +search should apply to. + +```python +class SitesSearchView(generics.ListAPIView): + """ + An API view to return a list of archaeological sites, optionally filtered + by a search against the site name or location. (Location searches are + matched against the region and country names.) + """ + queryset = Sites.objects.all() + serializer_class = SitesSerializer + filter_backends = [filters.SearchFilter] + search_fields = ['site_name', 'location__region', 'location__country'] +``` + +### Searches against annotate fields + +Django allows querysets to create additional virtual fields, using the `.annotate` +method. We now support searching against annotate fields. + +```python +class PublisherSearchView(generics.ListAPIView): + """ + Search for publishers, optionally filtering the search against the average + rating of all their books. + """ + queryset = Publisher.objects.annotate(avg_rating=Avg('book__rating')) + serializer_class = PublisherSerializer + filter_backends = [filters.SearchFilter] + search_fields = ['avg_rating'] +``` + +--- + +## Funding + +REST framework is a *collaboratively funded project*. If you use +REST framework commercially we strongly encourage you to invest in its +continued development by **[signing up for a paid plan][funding]**. + +*Every single sign-up helps us make REST framework long-term financially sustainable.* + + +
    + +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), and [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship).* + +[sponsors]: https://fund.django-rest-framework.org/topics/funding/#our-sponsors +[funding]: funding.md diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index d6f56317d2..636e91d6c2 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -36,11 +36,58 @@ You can determine your currently installed version using `pip show`: ## 3.11.x series +### 3.12.0 + +* Add `--file` option to `generateschema` command. [#7130] +* Support `tags` for OpenAPI schema generation. See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#grouping-operations-with-tags). [#7184] +* Support customising the operation ID for schema generation. See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#operationid). [#7190] +* Support OpenAPI components for schema generation. See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#components). [#7124] +* The following methods on `AutoSchema` become public API: `get_path_parameters`, `get_pagination_parameters`, `get_filter_parameters`, `get_request_body`, `get_responses`, `get_serializer`, `get_paginator`, `map_serializer`, `map_field`, `map_choice_field`, `map_field_validators`, `allows_filters`. See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#autoschema) +* Add support for Django 3.1's database-agnositic `JSONField`. [#7467] +* `SearchFilter` now supports nested search on `JSONField` and `HStoreField` model fields. [#7121] +* `SearchFilter` now supports searching on `annotate()` fields. [#6240] +* The authtoken model no longer exposes the `pk` in the admin URL. [#7341] +* Add `__repr__` for Request instances. [#7239] +* UTF-8 decoding with Latin-1 fallback for basic auth credentials. [#7193] +* CharField treats surrogate characters as a validation failure. [#7026] +* Don't include callables as default values in schemas. [#7105] +* Improve `ListField` schema output to include all available child information. [#7137] +* Allow `default=False` to be included for `BooleanField` schema outputs. [#7165] +* Include `"type"` information in `ChoiceField` schema outputs. [#7161] +* Include `"type": "object"` on schema objects. [#7169] +* Don't include component in schema output for DELETE requests. [#7229] +* Fix schema types for `DecimalField`. [#7254] +* Fix schema generation for `ObtainAuthToken` view. [#7211] +* Support passing `context=...` to view `.get_serializer()` methods. [#7298] +* Pass custom code to `PermissionDenied` if permission class has one set. [#7306] +* Include "example" in schema pagination output. [#7275] +* Default status code of 201 on schema output for POST requests. [#7206] +* Use camelCase for operation IDs in schema output. [#7208] +* Warn if duplicate operation IDs exist in schema output. [#7207] +* Improve handling of decimal type when mapping `ChoiceField` to a schema output. [#7264] +* Disable YAML aliases for OpenAPI schema outputs. [#7131] +* Fix action URL names for APIs included under a namespaced URL. [#7287] +* Update jQuery version from 3.4 to 3.5. [#7313] +* Fix `UniqueTogether` handling when serializer fields use `source=...`. [#7143] +* HTTP `HEAD` requests now set `self.action` correctly on a ViewSet instance. [#7223] +* Return a valid OpenAPI schema for the case where no API schema paths exist. [#7125] +* Include tests in package distribution. [#7145] +* Allow type checkers to support annotations like `ModelSerializer[Author]`. [#7385] +* Don't include invalid `charset=None` portion in the request `Content-Type` header when using APIClient. [#7400] +* Fix `\Z`/`\z` tokens in OpenAPI regexs. [#7389] +* Fix `PrimaryKeyRelatedField` and `HyperlinkedRelatedField` when source field is actually a property. [#7142] +* `Token.generate_key` is now a class method. [#7502] +* `@action` warns if method is wrapped in a decorator that does not preserve information using `@functools.wraps`. [#7098] + +--- + +## 3.11.x series + ### 3.11.0 **Date**: 12th December 2019 -* Drop `.set_context` API [in favour of a `requires_context` marker](../3.11-announcement#validator-default-context). +* Drop `.set_context` API [in favour of a `requires_context` marker](3.11-announcement.md#validator-default-context). * Changed default widget for TextField with choices to select box. [#6892][gh6892] * Supported nested writes on non-relational fields, such as JSONField. [#6916][gh6916] * Include request/response media types in OpenAPI schemas, based on configured parsers/renderers. [#6865][gh6865] diff --git a/mkdocs.yml b/mkdocs.yml index 484971a715..573898bca0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -66,6 +66,7 @@ nav: - 'Contributing to REST framework': 'community/contributing.md' - 'Project management': 'community/project-management.md' - 'Release Notes': 'community/release-notes.md' + - '3.12 Announcement': 'community/3.12-announcement.md' - '3.11 Announcement': 'community/3.11-announcement.md' - '3.10 Announcement': 'community/3.10-announcement.md' - '3.9 Announcement': 'community/3.9-announcement.md' diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 8f2bc4466f..afb2d34292 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ """ __title__ = 'Django REST framework' -__version__ = '3.11.0' +__version__ = '3.12.0' __author__ = 'Tom Christie' __license__ = 'BSD 3-Clause' __copyright__ = 'Copyright 2011-2019 Encode OSS Ltd' From 68b23075a2d72935f1e6c428c664d49412a8af68 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 28 Sep 2020 10:54:52 +0100 Subject: [PATCH 357/907] Add date to 3.12 release. --- docs/community/release-notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 636e91d6c2..b9bccc3acb 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -38,6 +38,8 @@ You can determine your currently installed version using `pip show`: ### 3.12.0 +Date: 28th September 2020 + * Add `--file` option to `generateschema` command. [#7130] * Support `tags` for OpenAPI schema generation. See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#grouping-operations-with-tags). [#7184] * Support customising the operation ID for schema generation. See [the schema docs](https://www.django-rest-framework.org/api-guide/schemas/#operationid). [#7190] From 995188f8c5bfdd0da29a7f9ca01898dfce5f0b37 Mon Sep 17 00:00:00 2001 From: Hugo Rodger-Brown Date: Mon, 28 Sep 2020 16:16:18 +0100 Subject: [PATCH 358/907] Add missing TokenProxy migration (#7557) Fixes #7554 --- .../authtoken/migrations/0003_tokenproxy.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 rest_framework/authtoken/migrations/0003_tokenproxy.py diff --git a/rest_framework/authtoken/migrations/0003_tokenproxy.py b/rest_framework/authtoken/migrations/0003_tokenproxy.py new file mode 100644 index 0000000000..79405a7c0f --- /dev/null +++ b/rest_framework/authtoken/migrations/0003_tokenproxy.py @@ -0,0 +1,25 @@ +# Generated by Django 3.1.1 on 2020-09-28 09:34 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authtoken', '0002_auto_20160226_1747'), + ] + + operations = [ + migrations.CreateModel( + name='TokenProxy', + fields=[ + ], + options={ + 'verbose_name': 'token', + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('authtoken.token',), + ), + ] From 05b3865838bcc0e4298a2c6a75cca69aad4f51d7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 28 Sep 2020 16:18:23 +0100 Subject: [PATCH 359/907] Version 3.12.1 --- docs/community/release-notes.md | 6 ++++++ rest_framework/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index b9bccc3acb..a300162aaf 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -36,6 +36,12 @@ You can determine your currently installed version using `pip show`: ## 3.11.x series +### 3.12.1 + +Date: 28th September 2020 + +* Add `TokenProxy` migration. [#7557] + ### 3.12.0 Date: 28th September 2020 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index afb2d34292..cd35f7a041 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -8,7 +8,7 @@ """ __title__ = 'Django REST framework' -__version__ = '3.12.0' +__version__ = '3.12.1' __author__ = 'Tom Christie' __license__ = 'BSD 3-Clause' __copyright__ = 'Copyright 2011-2019 Encode OSS Ltd' From 9edd5dfe5d65b99289b7a6d0d74deab89749a512 Mon Sep 17 00:00:00 2001 From: Ali Mirlou Date: Mon, 28 Sep 2020 23:15:30 +0330 Subject: [PATCH 360/907] Fix typo in release notes --- docs/community/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index a300162aaf..26dca3e4fc 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -34,7 +34,7 @@ You can determine your currently installed version using `pip show`: --- -## 3.11.x series +## 3.12.x series ### 3.12.1 From a03c85225ff79db42319fda4dfa6e65d316b6c03 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 30 Sep 2020 09:10:36 +0100 Subject: [PATCH 361/907] Update test_templatetags.py --- tests/test_templatetags.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/test_templatetags.py b/tests/test_templatetags.py index cdf10ab1e2..4b84f66474 100644 --- a/tests/test_templatetags.py +++ b/tests/test_templatetags.py @@ -290,9 +290,6 @@ def _urlize_dict_check(self, data): For all items in dict test assert that the value is urlized key """ for original, urlized in data.items(): - print('====') - print(repr(urlize(original, nofollow=False))) - print(repr(urlized)) assert urlize(original, nofollow=False) == urlized def test_json_with_url(self): From be87eb43b348f1f079de81329218b79814b97173 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 30 Sep 2020 09:44:31 +0100 Subject: [PATCH 362/907] Update release notes with 3.11.1, 3.11.2 --- docs/community/release-notes.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 26dca3e4fc..a488887193 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -91,6 +91,18 @@ Date: 28th September 2020 ## 3.11.x series +### 3.11.2 + +**Date**: 30th September 2020 + +* **Security**: Drop `urlize_quoted_links` template tag in favour of Django's built-in `urlize`. Removes a XSS vulnerability for some kinds of content in the browsable API. + +### 3.11.1 + +**Date**: 5th August 2020 + +* Fix compat with Django 3.1 + ### 3.11.0 **Date**: 12th December 2019 From 91916a4db14cd6a06aca13fb9a46fc667f6c0682 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 30 Sep 2020 13:47:06 +0100 Subject: [PATCH 363/907] Fix NullBooleanField warning (#7565) The argument to Field is 'allow_null'. --- rest_framework/fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f218713f1a..fdfba13f26 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -749,7 +749,7 @@ def __init__(self, **kwargs): warnings.warn( "The `NullBooleanField` is deprecated and will be removed starting " "with 3.14. Instead use the `BooleanField` field and set " - "`null=True` which does the same thing.", + "`allow_null=True` which does the same thing.", RemovedInDRF314Warning, stacklevel=2 ) From 86ec3da2daa7c585ac00c9900a6b482d7e819eac Mon Sep 17 00:00:00 2001 From: Emmanuel Meric de Bellefon Date: Mon, 5 Oct 2020 20:56:42 +0200 Subject: [PATCH 364/907] Update 6-viewsets-and-routers.md --- docs/tutorial/6-viewsets-and-routers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 11e24448f9..18989a3ee9 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -2,7 +2,7 @@ REST framework includes an abstraction for dealing with `ViewSets`, that allows the developer to concentrate on modeling the state and interactions of the API, and leave the URL construction to be handled automatically, based on common conventions. -`ViewSet` classes are almost the same thing as `View` classes, except that they provide operations such as `read`, or `update`, and not method handlers such as `get` or `put`. +`ViewSet` classes are almost the same thing as `View` classes, except that they provide operations such as `retrieve`, or `update`, and not method handlers such as `get` or `put`. A `ViewSet` class is only bound to a set of method handlers at the last moment, when it is instantiated into a set of views, typically by using a `Router` class which handles the complexities of defining the URL conf for you. From a849627f88c95cf3561a51055a0d8af901430e6a Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Mon, 5 Oct 2020 21:48:07 +0200 Subject: [PATCH 365/907] Fixes #7538 - use `retrieve` action name instead of `detail` --- docs/tutorial/6-viewsets-and-routers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 11e24448f9..3ce3ae7105 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -16,7 +16,7 @@ First of all let's refactor our `UserList` and `UserDetail` views into a single class UserViewSet(viewsets.ReadOnlyModelViewSet): """ - This viewset automatically provides `list` and `detail` actions. + This viewset automatically provides `list` and `retrieve` actions. """ queryset = User.objects.all() serializer_class = UserSerializer From 6a45a6a36fedf5b2a9bfbb23cf12b8a1cc8acb3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=B8=E0=A5=8D=E0=A4=AA=E0=A4=B0=E0=A5=8D=E0=A4=B6?= Date: Tue, 6 Oct 2020 17:59:11 +0545 Subject: [PATCH 366/907] Fix broken machinalis links (#7580) #7572 #7423 #7573 all are related to this --- docs/community/tutorials-and-resources.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/community/tutorials-and-resources.md b/docs/community/tutorials-and-resources.md index 6fdac6004b..cfd3ba852e 100644 --- a/docs/community/tutorials-and-resources.md +++ b/docs/community/tutorials-and-resources.md @@ -101,12 +101,12 @@ Want your Django REST Framework talk/tutorial/article to be added to our website [django-rest-framework-course]: https://teamtreehouse.com/library/django-rest-framework [pycon-uk-2016]: https://www.youtube.com/watch?v=FjmiGh7OqVg [django-under-hood-2014]: https://www.youtube.com/watch?v=3cSsbe-tA0E -[integrating-pandas-drf-and-bokeh]: https://machinalis.com/blog/pandas-django-rest-framework-bokeh/ -[controlling-uncertainty-on-web-apps-and-apis]: https://machinalis.com/blog/controlling-uncertainty-on-web-applications-and-apis/ -[full-text-search-in-drf]: https://machinalis.com/blog/full-text-search-on-django-rest-framework/ -[oauth2-authentication-with-drf]: https://machinalis.com/blog/oauth2-authentication/ -[nested-resources-with-drf]: https://machinalis.com/blog/nested-resources-with-django/ -[image-fields-with-drf]: https://machinalis.com/blog/image-fields-with-django-rest-framework/ +[integrating-pandas-drf-and-bokeh]: https://web.archive.org/web/20180104205117/http://machinalis.com/blog/pandas-django-rest-framework-bokeh/ +[controlling-uncertainty-on-web-apps-and-apis]: https://web.archive.org/web/20180104205043/https://machinalis.com/blog/controlling-uncertainty-on-web-applications-and-apis/ +[full-text-search-in-drf]: https://web.archive.org/web/20180104205059/http://machinalis.com/blog/full-text-search-on-django-rest-framework/ +[oauth2-authentication-with-drf]: https://web.archive.org/web/20180104205054/http://machinalis.com/blog/oauth2-authentication/ +[nested-resources-with-drf]: https://web.archive.org/web/20180104205109/http://machinalis.com/blog/nested-resources-with-django/ +[image-fields-with-drf]: https://web.archive.org/web/20180104205048/http://machinalis.com/blog/image-fields-with-django-rest-framework/ [chatbot-using-drf-part1]: https://chatbotslife.com/chatbot-using-django-rest-framework-api-ai-slack-part-1-3-69c7e38b7b1e#.g2aceuncf [new-django-admin-with-drf-and-emberjs]: https://blog.levit.be/new-django-admin-with-emberjs-what-are-the-news/ [drf-schema]: https://drf-schema-adapter.readthedocs.io/en/latest/ From 5e23b559f83f2ce2a4683111590909dc475d41a6 Mon Sep 17 00:00:00 2001 From: Mopsan Date: Fri, 9 Oct 2020 11:38:11 +0300 Subject: [PATCH 367/907] Fix example override of SchemaGenerator.get_schema() --- docs/api-guide/schemas.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index e39cd21a8e..b4832b3690 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -181,8 +181,8 @@ dictionary For example you might wish to add terms of service to the [top-level ``` class TOSSchemaGenerator(SchemaGenerator): - def get_schema(self): - schema = super().get_schema() + def get_schema(self, *args, **kwargs): + schema = super().get_schema(*args, **kwargs) schema["info"]["termsOfService"] = "https://example.com/tos.html" return schema ``` From 7921e9af434f2ccfde6962cf8a1b76331cc77722 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 9 Oct 2020 10:48:03 +0100 Subject: [PATCH 368/907] Fix RemovedInDjango40Warning for middleware get_resopnse() (#7513) Fixes #7417. Fixes all these issues seen with `tox -e py38-django31`: ``` /Users/chainz/Documents/Projects/django-rest-framework/tests/test_request.py:208: RemovedInDjango40Warning: Passing None for the middleware get_response argument is deprecated. SessionMiddleware().process_request(self.wrapped_request) tests/test_requests_client.py: 1 test with warning tests/test_testing.py: 4 tests with warnings tests/test_throttling.py: 1 test with warning tests/authentication/test_authentication.py: 4 tests with warnings tests/browsable_api/test_browsable_api.py: 4 tests with warnings /Users/chainz/Documents/Projects/django-rest-framework/rest_framework/authentication.py:139: RemovedInDjango40Warning: Passing None for the middleware get_response argument is deprecated. check = CSRFCheck() ``` --- rest_framework/authentication.py | 5 ++++- tests/test_request.py | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py index a2ba53480e..9111007c09 100644 --- a/rest_framework/authentication.py +++ b/rest_framework/authentication.py @@ -136,7 +136,10 @@ def enforce_csrf(self, request): """ Enforce CSRF validation for session based authentication. """ - check = CSRFCheck() + def dummy_get_response(request): # pragma: no cover + return None + + check = CSRFCheck(dummy_get_response) # populates request.META['CSRF_COOKIE'], which is used in process_view() check.process_request(request) reason = check.process_view(request, None, (), {}) diff --git a/tests/test_request.py b/tests/test_request.py index 4425c020fb..8f55d00ed4 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -205,8 +205,12 @@ def setUp(self): # available to login and logout functions self.wrapped_request = factory.get('/') self.request = Request(self.wrapped_request) - SessionMiddleware().process_request(self.wrapped_request) - AuthenticationMiddleware().process_request(self.wrapped_request) + + def dummy_get_response(request): # pragma: no cover + return None + + SessionMiddleware(dummy_get_response).process_request(self.wrapped_request) + AuthenticationMiddleware(dummy_get_response).process_request(self.wrapped_request) User.objects.create_user('ringo', 'starr@thebeatles.com', 'yellow') self.user = authenticate(username='ringo', password='yellow') From 931b34e7deb3b54e9b8205aad3a2448ff319e2b8 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 9 Oct 2020 11:09:34 +0100 Subject: [PATCH 369/907] Remove tox-venv from Travis setup (#7585) This was added in #6139. However it seems [tox-venv is no longer maintained](https://github.com/tox-dev/tox-venv), the related [virtualenv issue has been closed](https://github.com/pypa/virtualenv/issues/355), and I suspect with the virtualenv rewrite fixed the problem with site.py and the warnings referred to for the DRF tests. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index c204c54604..2c2724bf63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,7 @@ matrix: - env: DJANGO=master install: - - pip install tox tox-venv tox-travis + - pip install tox tox-travis script: - tox From 79daf315c4504d55bfd6025567be8ec9f35d361c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kry=C5=A1tof=20Piln=C3=A1=C4=8Dek?= Date: Fri, 9 Oct 2020 13:16:15 +0200 Subject: [PATCH 370/907] Fix: authtoken.TokenProxy cannot be proxy when not installed (#7571) closes https://github.com/encode/django-rest-framework/issues/7442 --- rest_framework/authtoken/models.py | 3 ++- tests/test_authtoken.py | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index fd8a50e0e7..540049295d 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -49,5 +49,6 @@ def pk(self): return self.user.pk class Meta: - proxy = True + proxy = 'rest_framework.authtoken' in settings.INSTALLED_APPS + abstract = 'rest_framework.authtoken' not in settings.INSTALLED_APPS verbose_name = "token" diff --git a/tests/test_authtoken.py b/tests/test_authtoken.py index 036e317efd..30e416d653 100644 --- a/tests/test_authtoken.py +++ b/tests/test_authtoken.py @@ -1,10 +1,11 @@ +import importlib from io import StringIO import pytest from django.contrib.admin import site from django.contrib.auth.models import User from django.core.management import CommandError, call_command -from django.test import TestCase +from django.test import TestCase, modify_settings from rest_framework.authtoken.admin import TokenAdmin from rest_framework.authtoken.management.commands.drf_create_token import \ @@ -21,6 +22,14 @@ def setUp(self): self.user = User.objects.create_user(username='test_user') self.token = Token.objects.create(key='test token', user=self.user) + def test_authtoken_can_be_imported_when_not_included_in_installed_apps(self): + import rest_framework.authtoken.models + with modify_settings(INSTALLED_APPS={'remove': 'rest_framework.authtoken'}): + importlib.reload(rest_framework.authtoken.models) + # Set the proxy and abstract properties back to the version, + # where authtoken is among INSTALLED_APPS. + importlib.reload(rest_framework.authtoken.models) + def test_model_admin_displayed_fields(self): mock_request = object() token_admin = TokenAdmin(self.token, self.site) From 79c37d0dc3740aa1c439f8a447e5e8b64fd567be Mon Sep 17 00:00:00 2001 From: David Smith <39445562+smithdc1@users.noreply.github.com> Date: Fri, 9 Oct 2020 12:21:22 +0100 Subject: [PATCH 371/907] Return NotImplemented sooner for ErrorDetail equality test (#7531) The test suite raises warnings when tested against Python 3.9 `DeprecationWarning: NotImplemented should not be used in a boolean context` Where `r` returns `NotImplemented` then this change returns `NotImplemented` first to avoid the comparison test. --- rest_framework/exceptions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 345a405248..943dcc88c3 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -73,6 +73,8 @@ def __new__(cls, string, code=None): def __eq__(self, other): r = super().__eq__(other) + if r is NotImplemented: + return NotImplemented try: return r and self.code == other.code except AttributeError: From 563b04376828a9c3d4d0c3232c1dad342627168b Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sat, 10 Oct 2020 17:34:00 +0100 Subject: [PATCH 372/907] Error on deprecation and pending deprecation warnings (#7586) Erroring on deprecation or pending deprecation warnings means they are caught early. This will avoid the cycle of releasing with 'support for Django X', then chasing all the deprecation warnings one-by-one. Instead, when a new Django version is added to the test matrix, it will fail until all the relevant warnings are fixed. This avoids passing these warnings on to users, some of whom don't upgrade until they are all fixed. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index d5e769764b..df6387d5e1 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ DJANGO = master: djangomaster [testenv] -commands = ./runtests.py --fast --coverage {posargs} +commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning runtests.py --fast --coverage {posargs} envdir = {toxworkdir}/venvs/{envname} setenv = PYTHONDONTWRITEBYTECODE=1 From 1ef192811cee3e6953aeb3d8f724d45c3057b6e7 Mon Sep 17 00:00:00 2001 From: Thomas Riccardi Date: Sat, 10 Oct 2020 18:34:39 +0200 Subject: [PATCH 373/907] Add missing quoting for `data` keyword argument in serializer doc (#7587) --- docs/api-guide/serializers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index efa3256200..5e27058343 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -251,7 +251,7 @@ For more information see the [validators documentation](validators.md). When passing an initial object or queryset to a serializer instance, the object will be made available as `.instance`. If no initial object is passed then the `.instance` attribute will be `None`. -When passing data to a serializer instance, the unmodified data will be made available as `.initial_data`. If the data keyword argument is not passed then the `.initial_data` attribute will not exist. +When passing data to a serializer instance, the unmodified data will be made available as `.initial_data`. If the `data` keyword argument is not passed then the `.initial_data` attribute will not exist. ## Partial updates From ffde1691025761c97927f9f4bbd5a9f3ec9ea96e Mon Sep 17 00:00:00 2001 From: zach valenta Date: Sat, 10 Oct 2020 12:36:15 -0400 Subject: [PATCH 374/907] Add docs link to to_internal_value() (#7476) --- docs/api-guide/relations.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index 88b462e1a5..d3d8b30b8c 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -337,7 +337,7 @@ output representation should be generated from the model instance. To implement a custom relational field, you should override `RelatedField`, and implement the `.to_representation(self, value)` method. This method takes the target of the field as the `value` argument, and should return the representation that should be used to serialize the target. The `value` argument will typically be a model instance. -If you want to implement a read-write relational field, you must also implement the `.to_internal_value(self, data)` method. +If you want to implement a read-write relational field, you must also implement the [`.to_internal_value(self, data)` method][to_internal_value]. To provide a dynamic queryset based on the `context`, you can also override `.get_queryset(self)` instead of specifying `.queryset` on the class or when initializing the field. @@ -605,3 +605,4 @@ The [rest-framework-generic-relations][drf-nested-relations] library provides re [drf-nested-relations]: https://github.com/Ian-Foote/rest-framework-generic-relations [django-intermediary-manytomany]: https://docs.djangoproject.com/en/2.2/topics/db/models/#intermediary-manytomany [dealing-with-nested-objects]: https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects +[to_internal_value]: https://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data From eff97efa283529c6503b9ebde6506691cecb17a2 Mon Sep 17 00:00:00 2001 From: Thomas Leese Date: Sat, 10 Oct 2020 18:02:21 +0100 Subject: [PATCH 375/907] Don't catch exceptions in get_queryset (#7480) In the `to_internal_value` method of the primary key and slug related fields, `TypeError`s and `ValueError`s are caught from `self.get_queryset().get(...)` and presented to the user. This works fine for most cases, but can cause problems if the exception is coming from `self.get_queryset()` rather than from the `.get(...)`. It means errors in the `get_queryset` method can be hidden and presented back to the user as though, for example, the input provided to the `to_internal_value` was the wrong type, whereas in reality there's a bug in the `get_queryset` method and therefore it should bubble up and be exposed as a server error. I've decided to fix this because twice now I've had to debug why I'm seeing `invalid_type` errors from my serializer (errors like `wrong pk type - int` when the `pk` type on my model is `int`) when the real problem was a bug in my custom `get_queryset` method. --- rest_framework/relations.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 3cd46379da..eaf27e1d96 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -257,8 +257,9 @@ def use_pk_only_optimization(self): def to_internal_value(self, data): if self.pk_field is not None: data = self.pk_field.to_internal_value(data) + queryset = self.get_queryset() try: - return self.get_queryset().get(pk=data) + return queryset.get(pk=data) except ObjectDoesNotExist: self.fail('does_not_exist', pk_value=data) except (TypeError, ValueError): @@ -454,8 +455,9 @@ def __init__(self, slug_field=None, **kwargs): super().__init__(**kwargs) def to_internal_value(self, data): + queryset = self.get_queryset() try: - return self.get_queryset().get(**{self.slug_field: data}) + return queryset.get(**{self.slug_field: data}) except ObjectDoesNotExist: self.fail('does_not_exist', slug_name=self.slug_field, value=smart_str(data)) except (TypeError, ValueError): From 0bdd537cc454477886fefc9873353b2364d94158 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 12 Oct 2020 17:40:26 +0300 Subject: [PATCH 376/907] Ignore derivations of BrowsableAPIRenderer in OpenAPI schema (#7497) * Ignore derivations of BrowsableAPIRenderer in OpenAPI schema * Improve test_renderer_mapping test Co-authored-by: Carlton Gibson --- rest_framework/schemas/openapi.py | 2 +- tests/schemas/test_openapi.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 8a8e267e0f..4ecb7a65f1 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -595,7 +595,7 @@ def map_renderers(self, path, method): media_types = [] for renderer in self.view.renderer_classes: # BrowsableAPIRenderer not relevant to OpenAPI spec - if renderer == renderers.BrowsableAPIRenderer: + if issubclass(renderer, renderers.BrowsableAPIRenderer): continue media_types.append(renderer.media_type) return media_types diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 8ea910dc1e..542c377b15 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -10,7 +10,9 @@ from rest_framework.authtoken.views import obtain_auth_token from rest_framework.compat import uritemplate from rest_framework.parsers import JSONParser, MultiPartParser -from rest_framework.renderers import JSONRenderer, OpenAPIRenderer +from rest_framework.renderers import ( + BaseRenderer, BrowsableAPIRenderer, JSONRenderer, OpenAPIRenderer +) from rest_framework.request import Request from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator @@ -507,9 +509,16 @@ def test_renderer_mapping(self): path = '/{id}/' method = 'GET' + class CustomBrowsableAPIRenderer(BrowsableAPIRenderer): + media_type = 'image/jpeg' # that's a wild API renderer + + class TextRenderer(BaseRenderer): + media_type = 'text/plain' + format = 'text' + class View(generics.CreateAPIView): serializer_class = views.ExampleSerializer - renderer_classes = [JSONRenderer] + renderer_classes = [JSONRenderer, TextRenderer, BrowsableAPIRenderer, CustomBrowsableAPIRenderer] view = create_view( View, @@ -524,8 +533,8 @@ class View(generics.CreateAPIView): # schema support is there success_response = responses['200'] - assert len(success_response['content'].keys()) == 1 - assert 'application/json' in success_response['content'] + # Check that the API renderers aren't included, but custom renderers are + assert set(success_response['content']) == {'application/json', 'text/plain'} def test_openapi_yaml_rendering_without_aliases(self): renderer = OpenAPIRenderer() From 95f0b0867a7542f7304401bfb6ae74c6e804934b Mon Sep 17 00:00:00 2001 From: Pawel Date: Tue, 13 Oct 2020 08:32:38 +0200 Subject: [PATCH 377/907] Documentation: improve the action decorator documentation (#7316) (#7380) * Documentation: improve the action decorator documentation (#7316) --- docs/api-guide/viewsets.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index d7aa7ad3bb..22cc3d8aa9 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -171,11 +171,6 @@ A more complete example of extra actions: serializer = self.get_serializer(recent_users, many=True) return Response(serializer.data) -The decorator can additionally take extra arguments that will be set for the routed view only. For example: - - @action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf]) - def set_password(self, request, pk=None): - ... The `action` decorator will route `GET` requests by default, but may also accept other HTTP methods by setting the `methods` argument. For example: @@ -183,7 +178,14 @@ The `action` decorator will route `GET` requests by default, but may also accept def unset_password(self, request, pk=None): ... -The two new actions will then be available at the urls `^users/{pk}/set_password/$` and `^users/{pk}/unset_password/$` + +The decorator allows you to override any viewset-level configuration such as `permission_classes`, `serializer_class`, `filter_backends`...: + + @action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf]) + def set_password(self, request, pk=None): + ... + +The two new actions will then be available at the urls `^users/{pk}/set_password/$` and `^users/{pk}/unset_password/$`. Use the `url_path` and `url_name` parameters to change the URL segement and the reverse URL name of the action. To view all extra actions, call the `.get_extra_actions()` method. From 3799633cdeb679eec681451bd318867dbd292114 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 13 Oct 2020 14:17:53 +0100 Subject: [PATCH 378/907] Version 3.12.2 --- docs/community/release-notes.md | 8 ++++++++ rest_framework/__init__.py | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index a488887193..c981b9ac92 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -36,6 +36,14 @@ You can determine your currently installed version using `pip show`: ## 3.12.x series +### 3.12.2 + +Date: 13th October 2020 + +* Fix issue if `rest_framework.authtoken.models` is imported, but `rest_framework.authtoken` is not in INSTALLED_APPS. [#7571] +* Ignore subclasses of BrowsableAPIRenderer in OpenAPI schema. [#7497] +* Narrower exception catching in serilizer fields, to ensure that any errors in broken `get_queryset()` methods are not masked. [#7480] + ### 3.12.1 Date: 28th September 2020 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 05d5adc465..7ff188a5ad 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -10,7 +10,7 @@ import django __title__ = 'Django REST framework' -__version__ = '3.12.1' +__version__ = '3.12.2' __author__ = 'Tom Christie' __license__ = 'BSD 3-Clause' __copyright__ = 'Copyright 2011-2019 Encode OSS Ltd' From 04e0c2b9ab6616a28148ce32e2d19858ccfe6c69 Mon Sep 17 00:00:00 2001 From: David Smith <39445562+smithdc1@users.noreply.github.com> Date: Tue, 13 Oct 2020 17:27:08 +0100 Subject: [PATCH 379/907] Bumped Markdown version to 3.3 (#7590) --- requirements/requirements-optionals.txt | 3 +- tests/test_description.py | 372 ++++++++++++------------ 2 files changed, 182 insertions(+), 193 deletions(-) diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index 2b7a18a13f..739555667e 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -1,6 +1,7 @@ # Optional packages which may be used with REST framework. psycopg2-binary>=2.8.5, <2.9 -markdown==3.1.1 +markdown==3.3;python_version>="3.6" +markdown==3.2.2;python_version=="3.5" pygments==2.4.2 django-guardian==2.2.0 django-filter>=2.2.0, <2.3 diff --git a/tests/test_description.py b/tests/test_description.py index ae00fe4a97..9e7e4dc322 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -1,192 +1,180 @@ -from django.test import TestCase - -from rest_framework.compat import apply_markdown -from rest_framework.utils.formatting import dedent -from rest_framework.views import APIView - -# We check that docstrings get nicely un-indented. -DESCRIPTION = """an example docstring -==================== - -* list -* list - -another header --------------- - - code block - -indented - -# hash style header # - -``` json -[{ - "alpha": 1, - "beta: "this is a string" -}] -```""" - - -# If markdown is installed we also test it's working -# (and that our wrapped forces '=' to h2 and '-' to h3) -MARKED_DOWN_HILITE = """ -
    [{
    "alpha": 1,
    \ - "beta: "this\ - is a \ -string"
    }]
    - -


    """ - -MARKED_DOWN_NOT_HILITE = """ -

    json -[{ - "alpha": 1, - "beta: "this is a string" -}]

    """ - -# We support markdown < 2.1 and markdown >= 2.1 -MARKED_DOWN_lt_21 = """

    an example docstring

    -
      -
    • list
    • -
    • list
    • -
    -

    another header

    -
    code block
    -
    -

    indented

    -

    hash style header

    %s""" - -MARKED_DOWN_gte_21 = """

    an example docstring

    -
      -
    • list
    • -
    • list
    • -
    -

    another header

    -
    code block
    -
    -

    indented

    -

    hash style header

    %s""" - - -class TestViewNamesAndDescriptions(TestCase): - def test_view_name_uses_class_name(self): - """ - Ensure view names are based on the class name. - """ - class MockView(APIView): - pass - assert MockView().get_view_name() == 'Mock' - - def test_view_name_uses_name_attribute(self): - class MockView(APIView): - name = 'Foo' - assert MockView().get_view_name() == 'Foo' - - def test_view_name_uses_suffix_attribute(self): - class MockView(APIView): - suffix = 'List' - assert MockView().get_view_name() == 'Mock List' - - def test_view_name_preferences_name_over_suffix(self): - class MockView(APIView): - name = 'Foo' - suffix = 'List' - assert MockView().get_view_name() == 'Foo' - - def test_view_description_uses_docstring(self): - """Ensure view descriptions are based on the docstring.""" - class MockView(APIView): - """an example docstring - ==================== - - * list - * list - - another header - -------------- - - code block - - indented - - # hash style header # - - ``` json - [{ - "alpha": 1, - "beta: "this is a string" - }] - ```""" - - assert MockView().get_view_description() == DESCRIPTION - - def test_view_description_uses_description_attribute(self): - class MockView(APIView): - description = 'Foo' - assert MockView().get_view_description() == 'Foo' - - def test_view_description_allows_empty_description(self): - class MockView(APIView): - """Description.""" - description = '' - assert MockView().get_view_description() == '' - - def test_view_description_can_be_empty(self): - """ - Ensure that if a view has no docstring, - then it's description is the empty string. - """ - class MockView(APIView): - pass - assert MockView().get_view_description() == '' - - def test_view_description_can_be_promise(self): - """ - Ensure a view may have a docstring that is actually a lazily evaluated - class that can be converted to a string. - - See: https://github.com/encode/django-rest-framework/issues/1708 - """ - # use a mock object instead of gettext_lazy to ensure that we can't end - # up with a test case string in our l10n catalog - - class MockLazyStr: - def __init__(self, string): - self.s = string - - def __str__(self): - return self.s - - class MockView(APIView): - __doc__ = MockLazyStr("a gettext string") - - assert MockView().get_view_description() == 'a gettext string' - - def test_markdown(self): - """ - Ensure markdown to HTML works as expected. - """ - if apply_markdown: - md_applied = apply_markdown(DESCRIPTION) - gte_21_match = ( - md_applied == ( - MARKED_DOWN_gte_21 % MARKED_DOWN_HILITE) or - md_applied == ( - MARKED_DOWN_gte_21 % MARKED_DOWN_NOT_HILITE)) - lt_21_match = ( - md_applied == ( - MARKED_DOWN_lt_21 % MARKED_DOWN_HILITE) or - md_applied == ( - MARKED_DOWN_lt_21 % MARKED_DOWN_NOT_HILITE)) - assert gte_21_match or lt_21_match - - -def test_dedent_tabs(): - result = 'first string\n\nsecond string' - assert dedent(" first string\n\n second string") == result - assert dedent("first string\n\n second string") == result - assert dedent("\tfirst string\n\n\tsecond string") == result - assert dedent("first string\n\n\tsecond string") == result +import sys + +import pytest +from django.test import TestCase + +from rest_framework.compat import apply_markdown +from rest_framework.utils.formatting import dedent +from rest_framework.views import APIView + +# We check that docstrings get nicely un-indented. +DESCRIPTION = """an example docstring +==================== + +* list +* list + +another header +-------------- + + code block + +indented + +# hash style header # + +``` json +[{ + "alpha": 1, + "beta: "this is a string" +}] +```""" + + +# If markdown is installed we also test it's working +# (and that our wrapped forces '=' to h2 and '-' to h3) +MARKDOWN_BASE = """

    an example docstring

    +
      +
    • list
    • +
    • list
    • +
    +

    another header

    +
    code block
    +
    +

    indented

    +

    hash style header

    %s""" + +MARKDOWN_gte_33 = """ +
    [{
    \ + "alpha":\ + 1,
    \ + "beta: "this\ + is a \ +string"
    }]\ +
    +


    """ + +MARKDOWN_lt_33 = """ +
    [{
    \ + "alpha":\ + 1,
    \ + "beta: "this\ + is a\ + string"
    }]\ +
    + +


    """ + + +class TestViewNamesAndDescriptions(TestCase): + def test_view_name_uses_class_name(self): + """ + Ensure view names are based on the class name. + """ + class MockView(APIView): + pass + assert MockView().get_view_name() == 'Mock' + + def test_view_name_uses_name_attribute(self): + class MockView(APIView): + name = 'Foo' + assert MockView().get_view_name() == 'Foo' + + def test_view_name_uses_suffix_attribute(self): + class MockView(APIView): + suffix = 'List' + assert MockView().get_view_name() == 'Mock List' + + def test_view_name_preferences_name_over_suffix(self): + class MockView(APIView): + name = 'Foo' + suffix = 'List' + assert MockView().get_view_name() == 'Foo' + + def test_view_description_uses_docstring(self): + """Ensure view descriptions are based on the docstring.""" + class MockView(APIView): + """an example docstring + ==================== + + * list + * list + + another header + -------------- + + code block + + indented + + # hash style header # + + ``` json + [{ + "alpha": 1, + "beta: "this is a string" + }] + ```""" + + assert MockView().get_view_description() == DESCRIPTION + + def test_view_description_uses_description_attribute(self): + class MockView(APIView): + description = 'Foo' + assert MockView().get_view_description() == 'Foo' + + def test_view_description_allows_empty_description(self): + class MockView(APIView): + """Description.""" + description = '' + assert MockView().get_view_description() == '' + + def test_view_description_can_be_empty(self): + """ + Ensure that if a view has no docstring, + then it's description is the empty string. + """ + class MockView(APIView): + pass + assert MockView().get_view_description() == '' + + def test_view_description_can_be_promise(self): + """ + Ensure a view may have a docstring that is actually a lazily evaluated + class that can be converted to a string. + + See: https://github.com/encode/django-rest-framework/issues/1708 + """ + # use a mock object instead of gettext_lazy to ensure that we can't end + # up with a test case string in our l10n catalog + + class MockLazyStr: + def __init__(self, string): + self.s = string + + def __str__(self): + return self.s + + class MockView(APIView): + __doc__ = MockLazyStr("a gettext string") + + assert MockView().get_view_description() == 'a gettext string' + + @pytest.mark.skipif(not apply_markdown, reason="Markdown is not installed") + def test_markdown(self): + """ + Ensure markdown to HTML works as expected. + """ + # Markdown 3.3 is only supported on Python 3.6 and higher + if sys.version_info >= (3, 6): + assert apply_markdown(DESCRIPTION) == MARKDOWN_BASE % MARKDOWN_gte_33 + else: + assert apply_markdown(DESCRIPTION) == MARKDOWN_BASE % MARKDOWN_lt_33 + + +def test_dedent_tabs(): + result = 'first string\n\nsecond string' + assert dedent(" first string\n\n second string") == result + assert dedent("first string\n\n second string") == result + assert dedent("\tfirst string\n\n\tsecond string") == result + assert dedent("first string\n\n\tsecond string") == result From a0115e66d4583905613346f65ff6cbb98092e3c4 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 13 Oct 2020 21:30:05 +0200 Subject: [PATCH 380/907] restore the transifex configuration This reverts commit 6957aaae8594b42cbf368d361031ce5fb9eb7246. --- .tx/config | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .tx/config diff --git a/.tx/config b/.tx/config new file mode 100644 index 0000000000..e151a7e6ff --- /dev/null +++ b/.tx/config @@ -0,0 +1,9 @@ +[main] +host = https://www.transifex.com +lang_map = sr@latin:sr_Latn, zh-Hans:zh_Hans, zh-Hant:zh_Hant + +[django-rest-framework.djangopo] +file_filter = rest_framework/locale//LC_MESSAGES/django.po +source_file = rest_framework/locale/en_US/LC_MESSAGES/django.po +source_lang = en_US +type = PO From 47cfbdac97c8bafd88a1eea796301e61f9bb9f36 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Tue, 13 Oct 2020 21:40:33 +0200 Subject: [PATCH 381/907] Translations updated from transifex and compiled --- .../locale/ar/LC_MESSAGES/django.mo | Bin 6525 -> 12150 bytes .../locale/ar/LC_MESSAGES/django.po | 462 +++++++++----- .../locale/az/LC_MESSAGES/django.mo | Bin 0 -> 10428 bytes .../locale/az/LC_MESSAGES/django.po | 573 +++++++++++++++++ .../locale/bg/LC_MESSAGES/django.mo | Bin 0 -> 13083 bytes .../locale/bg/LC_MESSAGES/django.po | 572 +++++++++++++++++ .../locale/ca/LC_MESSAGES/django.mo | Bin 9605 -> 9300 bytes .../locale/ca/LC_MESSAGES/django.po | 379 ++++++++---- .../locale/cs/LC_MESSAGES/django.mo | Bin 8990 -> 10519 bytes .../locale/cs/LC_MESSAGES/django.po | 419 ++++++++----- .../locale/da/LC_MESSAGES/django.mo | Bin 10360 -> 9955 bytes .../locale/da/LC_MESSAGES/django.po | 387 ++++++++---- .../locale/de/LC_MESSAGES/django.mo | Bin 10885 -> 10490 bytes .../locale/de/LC_MESSAGES/django.po | 389 ++++++++---- .../locale/el/LC_MESSAGES/django.mo | Bin 13466 -> 12933 bytes .../locale/el/LC_MESSAGES/django.po | 395 ++++++++---- .../locale/en/LC_MESSAGES/django.mo | Bin 10185 -> 12285 bytes .../locale/en/LC_MESSAGES/django.po | 387 ++++++++---- .../locale/en_US/LC_MESSAGES/django.po | 371 +++++++---- .../locale/es/LC_MESSAGES/django.mo | Bin 11023 -> 10627 bytes .../locale/es/LC_MESSAGES/django.po | 392 ++++++++---- .../locale/et/LC_MESSAGES/django.mo | Bin 8691 -> 10096 bytes .../locale/et/LC_MESSAGES/django.po | 420 ++++++++----- .../locale/fa/LC_MESSAGES/django.mo | Bin 466 -> 11976 bytes .../locale/fa/LC_MESSAGES/django.po | 541 +++++++++++------ .../locale/fa_IR/LC_MESSAGES/django.mo | Bin 479 -> 11989 bytes .../locale/fa_IR/LC_MESSAGES/django.po | 541 +++++++++++------ .../locale/fi/LC_MESSAGES/django.mo | Bin 10508 -> 10197 bytes .../locale/fi/LC_MESSAGES/django.po | 414 ++++++++----- .../locale/fr/LC_MESSAGES/django.mo | Bin 10973 -> 10662 bytes .../locale/fr/LC_MESSAGES/django.po | 441 +++++++++----- .../locale/hu/LC_MESSAGES/django.mo | Bin 9082 -> 10844 bytes .../locale/hu/LC_MESSAGES/django.po | 433 ++++++++----- .../locale/hy/LC_MESSAGES/django.mo | Bin 0 -> 12885 bytes .../locale/hy/LC_MESSAGES/django.po | 573 +++++++++++++++++ .../locale/id/LC_MESSAGES/django.mo | Bin 469 -> 5188 bytes .../locale/id/LC_MESSAGES/django.po | 457 +++++++++----- .../locale/it/LC_MESSAGES/django.mo | Bin 10318 -> 10480 bytes .../locale/it/LC_MESSAGES/django.po | 413 ++++++++----- .../locale/ja/LC_MESSAGES/django.mo | Bin 12254 -> 11759 bytes .../locale/ja/LC_MESSAGES/django.po | 387 ++++++++---- .../locale/ko_KR/LC_MESSAGES/django.mo | Bin 11774 -> 11698 bytes .../locale/ko_KR/LC_MESSAGES/django.po | 396 ++++++++---- .../locale/lt/LC_MESSAGES/django.mo | Bin 0 -> 5056 bytes .../locale/lt/LC_MESSAGES/django.po | 573 +++++++++++++++++ .../locale/lv/LC_MESSAGES/django.mo | Bin 10800 -> 10423 bytes .../locale/lv/LC_MESSAGES/django.po | 387 ++++++++---- .../locale/mk/LC_MESSAGES/django.mo | Bin 12619 -> 12121 bytes .../locale/mk/LC_MESSAGES/django.po | 387 ++++++++---- .../locale/nb/LC_MESSAGES/django.mo | Bin 10317 -> 9928 bytes .../locale/nb/LC_MESSAGES/django.po | 387 ++++++++---- .../locale/ne_NP/LC_MESSAGES/django.mo | Bin 0 -> 15636 bytes .../locale/ne_NP/LC_MESSAGES/django.po | 574 ++++++++++++++++++ .../locale/nl/LC_MESSAGES/django.mo | Bin 10567 -> 10163 bytes .../locale/nl/LC_MESSAGES/django.po | 390 ++++++++---- .../locale/pl/LC_MESSAGES/django.mo | Bin 11080 -> 10673 bytes .../locale/pl/LC_MESSAGES/django.po | 389 ++++++++---- .../locale/pt/LC_MESSAGES/django.mo | Bin 476 -> 10382 bytes .../locale/pt/LC_MESSAGES/django.po | 540 ++++++++++------ .../locale/pt_BR/LC_MESSAGES/django.mo | Bin 10804 -> 10397 bytes .../locale/pt_BR/LC_MESSAGES/django.po | 390 ++++++++---- .../locale/ro/LC_MESSAGES/django.mo | Bin 10854 -> 10701 bytes .../locale/ro/LC_MESSAGES/django.po | 398 ++++++++---- .../locale/ru/LC_MESSAGES/django.mo | Bin 13400 -> 13160 bytes .../locale/ru/LC_MESSAGES/django.po | 393 ++++++++---- .../locale/ru_RU/LC_MESSAGES/django.mo | Bin 0 -> 5208 bytes .../locale/ru_RU/LC_MESSAGES/django.po | 573 +++++++++++++++++ .../locale/sk/LC_MESSAGES/django.mo | Bin 9370 -> 9164 bytes .../locale/sk/LC_MESSAGES/django.po | 381 ++++++++---- .../locale/sl/LC_MESSAGES/django.mo | Bin 10349 -> 9985 bytes .../locale/sl/LC_MESSAGES/django.po | 387 ++++++++---- .../locale/sv/LC_MESSAGES/django.mo | Bin 10584 -> 10204 bytes .../locale/sv/LC_MESSAGES/django.po | 387 ++++++++---- .../locale/th/LC_MESSAGES/django.mo | Bin 0 -> 8880 bytes .../locale/th/LC_MESSAGES/django.po | 573 +++++++++++++++++ .../locale/tr/LC_MESSAGES/django.mo | Bin 10509 -> 10073 bytes .../locale/tr/LC_MESSAGES/django.po | 389 ++++++++---- .../locale/tr_TR/LC_MESSAGES/django.mo | Bin 10481 -> 10292 bytes .../locale/tr_TR/LC_MESSAGES/django.po | 396 ++++++++---- .../locale/uk/LC_MESSAGES/django.mo | Bin 13437 -> 13245 bytes .../locale/uk/LC_MESSAGES/django.po | 411 ++++++++----- .../locale/vi/LC_MESSAGES/django.mo | Bin 469 -> 2179 bytes .../locale/vi/LC_MESSAGES/django.po | 409 ++++++++----- .../locale/zh_CN/LC_MESSAGES/django.mo | Bin 10301 -> 9915 bytes .../locale/zh_CN/LC_MESSAGES/django.po | 387 ++++++++---- .../locale/zh_Hans/LC_MESSAGES/django.mo | Bin 10321 -> 9938 bytes .../locale/zh_Hans/LC_MESSAGES/django.po | 389 ++++++++---- .../locale/zh_Hant/LC_MESSAGES/django.mo | Bin 488 -> 4809 bytes .../locale/zh_Hant/LC_MESSAGES/django.po | 464 +++++++++----- 89 files changed, 14399 insertions(+), 5297 deletions(-) create mode 100644 rest_framework/locale/az/LC_MESSAGES/django.mo create mode 100644 rest_framework/locale/az/LC_MESSAGES/django.po create mode 100644 rest_framework/locale/bg/LC_MESSAGES/django.mo create mode 100644 rest_framework/locale/bg/LC_MESSAGES/django.po create mode 100644 rest_framework/locale/hy/LC_MESSAGES/django.mo create mode 100644 rest_framework/locale/hy/LC_MESSAGES/django.po create mode 100644 rest_framework/locale/lt/LC_MESSAGES/django.mo create mode 100644 rest_framework/locale/lt/LC_MESSAGES/django.po create mode 100644 rest_framework/locale/ne_NP/LC_MESSAGES/django.mo create mode 100644 rest_framework/locale/ne_NP/LC_MESSAGES/django.po create mode 100644 rest_framework/locale/ru_RU/LC_MESSAGES/django.mo create mode 100644 rest_framework/locale/ru_RU/LC_MESSAGES/django.po create mode 100644 rest_framework/locale/th/LC_MESSAGES/django.mo create mode 100644 rest_framework/locale/th/LC_MESSAGES/django.po diff --git a/rest_framework/locale/ar/LC_MESSAGES/django.mo b/rest_framework/locale/ar/LC_MESSAGES/django.mo index 19a41dfd6e6ccc0c4ebc3b5cce8325eecd65fb65..f793d7c73b74b37eee04252018de72e7505a305d 100644 GIT binary patch literal 12150 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 B_B#Lo delta 1987 zcmY+^TWnNC7{KvaC{QZ5mP;>`GA+GOx@>o=m9`WE5-jTS_w3&t!#`G(j4RqYj#`eAX-e(HU#4@SbJfgc8i(*A$|vez&q;>sPy*Y6R8zf;h_X1_IAlVpLN|x3hcz6ULo%1FrtE#F8M+Ei;OUdVQmCTl=iGe<9nY#(W|4OPQ2Cw3s68fwr^ zQpe&c9o7fK15QNu#p46fa4cX}mNu9JrKj_)vwc33D67xkDeLl?_?%bgZg!#r5xvhD zNJSG#6Pp_{BlEYI(FOkLTcbzJ>GI?4v8dX{ocrSGSR|mHNJOFuC-$7$9!@3?#S;;= zJ(?JFl1V2X(~)S*G4E8Yn7K3EH|V6y+J&dgxkZ!9lF@|P#YuSseOY_x;I?t1;Wx>-ye^~ zqW!6GB$+R%eBM{7-51@L-BEj9yQA()_EYKF^8vG`YLoe<>U!Q*`+YmyF>S9hp40Y{ z&2o`-$Nbt(*qr^)X6(D#p0#;2i#$NCy%M#@&cf@Se zYt3gXj+)P#2HJQD8Jr-BjLj+%8L<;nQ8PoHo2P7+pxrU^eapDHwsN^CZ*4Ty?X|rD zMJ`$Ufs#, 2017 # Bashar Al-Abdulhadi, 2016-2017 # Eyad Toma , 2015,2017 +# zak zak , 2020 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-10-18 09:51+0000\n" -"Last-Translator: Andrew Ayoub \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Arabic (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ar/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -21,40 +22,40 @@ msgstr "" "Language: ar\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." -msgstr "" +msgstr "رأس أساسي غير صالح, لم تقدم اي بيانات." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." -msgstr "" +msgstr "رأس أساسي غير صالح, سلسلة البيانات لا يجب أن تحتوي على أي أحرف مسافات" -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." -msgstr "" +msgstr "رأس أساسي غير صالح, البيانات ليست مرمّزة بصحة على أساس64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "اسم المستخدم/كلمة السر غير صحيحين." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "المستخدم غير مفعل او تم حذفه." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." -msgstr "" +msgstr "رمز الراْس المميّز غير صالح, لم تقدم أي بيانات." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." -msgstr "" +msgstr "رمز الراْس المميّز غير صالح, سلسلة الرمز المميّز لا يجب أن تحتوي على أي أحرف مسافات." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." -msgstr "" +msgstr "رمز الراْس المميّز غير صالح, سلسلة الرمز المميّز لا يجب أن تحتوي على أي أحرف غير صالحة." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "رمز غير صحيح." @@ -62,382 +63,515 @@ msgstr "رمز غير صحيح." msgid "Auth Token" msgstr "رمز التفويض" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "المفتاح" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "المستخدم" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "أنشئ" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "الرمز" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "الرموز" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "اسم المستخدم" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "كلمة المرور" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "حساب المستخدم غير مفعل." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "تعذر تسجيل الدخول بالبيانات التي ادخلتها." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "يجب أن تتضمن \"اسم المستخدم\" و \"كلمة المرور\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "حدث خطأ في المخدم." -#: exceptions.py:84 -msgid "Malformed request." +#: exceptions.py:142 +msgid "Invalid input." msgstr "" -#: exceptions.py:89 +#: exceptions.py:161 +msgid "Malformed request." +msgstr "الطلب صيغ بشكل سيء." + +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "بيانات الدخول غير صحيحة." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "لم يتم تزويد بيانات الدخول." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "ليس لديك صلاحية للقيام بهذا الإجراء." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "غير موجود." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." -msgstr "طلب غير مسموح به" +msgstr "الطريقة \"{method}\" غير مسموح بها." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." -msgstr "" +msgstr "لم نتمكن من تلبية الرٱس Accept في الطلب." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." -msgstr "" +msgstr "الوسيط \"{media_type}\" الموجود في الطلب غير معتمد." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." +msgstr "تم تقييد الطلب." + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." msgstr "" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "هذا الحقل مطلوب." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "لا يمكن لهذا الحقل ان يكون فارغاً null." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" ليس قيمة منطقية." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." msgstr "لا يمكن لهذا الحقل ان يكون فارغاً." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "تأكد ان الحقل لا يزيد عن {max_length} محرف." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "تأكد ان الحقل {min_length} محرف على الاقل." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "عليك ان تدخل بريد إلكتروني صالح." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "هذه القيمة لا تطابق النمط المطلوب." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." +msgstr "أدخل \"slug\" صالح يحتوي على حروف، أرقام، شُرط سفلية أو واصلات." + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." msgstr "" -#: fields.py:747 +#: fields.py:854 msgid "Enter a valid URL." msgstr "الرجاء إدخال رابط إلكتروني صالح." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." +#: fields.py:867 +msgid "Must be a valid UUID." msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "برجاء إدخال عنوان IPV4 أو IPV6 صحيح" +msgstr "أدخِل عنوان IPV4 أو IPV6 صحيح." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "الرجاء إدخال رقم صحيح صالح." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "تأكد ان القيمة أقل أو تساوي {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "تأكد ان القيمة أكبر أو تساوي {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." -msgstr "القيمه اكبر من المسموح" +msgstr "السلسلة اطول من القيمة المسموح بها." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "الرجاء إدخال رقم صالح." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "تأكد ان القيمة لا تحوي أكثر من {max_digits} رقم." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." -msgstr "" +msgstr "تأكد انه لا يوجد اكثر من {max_decimal_places} منازل عشرية." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." -msgstr "" +msgstr "تأكد انه لا يوجد اكثر من {max_whole_digits} أرقام قبل النقطة العشرية." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "صيغة التاريخ و الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "متوقع تاريخ و وقت و وجد تاريخ فقط" -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "صيغة التاريخ غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "متوقع تاريخ فقط و وجد تاريخ ووقت" -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "صيغة الوقت غير صحيحة. عليك أن تستخدم واحدة من هذه الصيغ التالية: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." -msgstr "صيغة المده غير صحيحه, برجاء إستخدام أحد هذه الصيغ {format}" +msgstr "صيغة المدة غير صحيحه, يرجى إستخدام إحدى هذه الصيغ: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" ليست واحدة من الخيارات الصالحة." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "أكثر من {count} عنصر..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." -msgstr "" +msgstr "المتوقع وجود قائمة عناصر لكن وجد النوع \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." -msgstr "" +msgstr "هذا التحديد لا يجب أن يكون فارغا." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." -msgstr "" +msgstr "{input} كإختيار مسار غير صالح." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "لم يتم إرسال أي ملف." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." -msgstr "" +msgstr "المعطيات المرسولة ليست ملف. إفحص نوع الترميز في النموذج." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." -msgstr "" +msgstr "ما من إسم ملف أمكن تحديده." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "الملف الذي تم إرساله فارغ." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "تأكد ان اسم الملف لا يحوي أكثر من {max_length} محرف (الإسم المرسل يحوي {length} محرف)." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." -msgstr "" +msgstr "الرجاء تحميل صورة صالحة. الملف الذي تم تحميله إما لم يكن صورة او انه كان صورة تالفة." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." +msgstr "القائمة يجب أن لا تكون فارغة." + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." msgstr "" -#: fields.py:1502 +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "المتوقع كان قاموس عناصر و لكن النوع المتحصل عليه هو \"{input_type}\"." + +#: fields.py:1683 +msgid "This dictionary may not be empty." msgstr "" -#: fields.py:1549 +#: fields.py:1755 msgid "Value must be valid JSON." +msgstr "القيمة يجب أن تكون JSON صالح." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "بحث" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "أرسل" +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "الترتيب" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" msgstr "تصاعدي" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "تنازلي" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "صفحة غير صحيحة." -#: pagination.py:427 -msgid "Invalid cursor" +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." msgstr "" -#: relations.py:207 +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "مؤشر غير صالح" + +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "معرف العنصر \"{pk_value}\" غير صالح - العنصر غير موجود." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." -msgstr "" +msgstr "نوع خاطئ. المتوقع قيمة من pk، لكن المتحصل عليه {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." -msgstr "" +msgstr "إرتباط تشعبي غير صالح - لا مطابقة لURL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." -msgstr "" +msgstr "إرتباط تشعبي غير صالح - مطابقة خاطئة لURL." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." -msgstr "" +msgstr "إرتباط تشعبي غير صالح - عنصر غير موجود." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." -msgstr "" +msgstr "نوع خاطئ. المتوقع سلسلة URL، لكن المتحصل عليه {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." -msgstr "" +msgstr "عنصر ب {slug_name}={value} غير موجود." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "قيمة غير صالحة." -#: serializers.py:326 -msgid "Invalid data. Expected a dictionary, but got {datatype}." +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." msgstr "" +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "معطيات غير صالحة. المتوقع هو قاموس، لكن المتحصل عليه {datatype}." + #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "مرشحات" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "مرشحات الحقول" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "الترتيب" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "بحث" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "لا شيء" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." -msgstr "" +msgstr "ما من عناصر للتحديد." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "هذا الحقل يجب أن يكون فريد" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." +msgstr "الحقول {field_names} يجب أن تشكل مجموعة فريدة." + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." msgstr "" -#: validators.py:245 +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." -msgstr "" +msgstr "الحقل يجب ان يكون فريد للتاريخ {date_field}." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." -msgstr "" +msgstr "الحقل يجب ان يكون فريد للشهر {date_field}." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." -msgstr "" +msgstr "الحقل يجب ان يكون فريد للعام {date_field}." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." -msgstr "" +msgstr "إصدار غير صالح في الرٱس \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." -msgstr "" +msgstr "إصدار غير صالح في المسار URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr " إصدار غير صالح في المسار URL. لا يطابق أي إصدار من مساحة الإسم." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." -msgstr "" +msgstr "إصدار غير صالح في اسم المضيف." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." -msgstr "" - -#: views.py:88 -msgid "Permission denied." -msgstr "ليس لديك صلاحية." +msgstr "إصدار غير صالح في معلمة الإستعلام." diff --git a/rest_framework/locale/az/LC_MESSAGES/django.mo b/rest_framework/locale/az/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..08648351e8ed72361f7148ed5f2e38e412dea206 GIT binary patch literal 10428 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 OM2QKMStIZS>i+>T@S}zR literal 0 HcmV?d00001 diff --git a/rest_framework/locale/az/LC_MESSAGES/django.po b/rest_framework/locale/az/LC_MESSAGES/django.po new file mode 100644 index 0000000000..43a224259b --- /dev/null +++ b/rest_framework/locale/az/LC_MESSAGES/django.po @@ -0,0 +1,573 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Emin Mastizada , 2020 +msgid "" +msgstr "" +"Project-Id-Version: Django REST framework\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" +"Language-Team: Azerbaijani (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/az/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: az\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: authentication.py:70 +msgid "Invalid basic header. No credentials provided." +msgstr "Xətalı sadə başlıq. İstifadəçi məlumatları təchiz edilməyib." + +#: authentication.py:73 +msgid "Invalid basic header. Credentials string should not contain spaces." +msgstr "Xətalı sadə başlıq. İstifadəçi məlumatlarında boşluq olmamalıdır." + +#: authentication.py:83 +msgid "Invalid basic header. Credentials not correctly base64 encoded." +msgstr "Xətalı sadə başlıq. İstifadəçi məlumatları base64 ilə düzgün şifrələnməyib." + +#: authentication.py:101 +msgid "Invalid username/password." +msgstr "Xətalı istifadəçi adı/parol." + +#: authentication.py:104 authentication.py:206 +msgid "User inactive or deleted." +msgstr "İstifadəçi aktiv deyil və ya silinib." + +#: authentication.py:184 +msgid "Invalid token header. No credentials provided." +msgstr "Xətalı token başlığı. İstifadəçi məlumatları təchiz edilməyib." + +#: authentication.py:187 +msgid "Invalid token header. Token string should not contain spaces." +msgstr "Xətalı token başlığı. Token mətnində boşluqlar olmamalıdır." + +#: authentication.py:193 +msgid "" +"Invalid token header. Token string should not contain invalid characters." +msgstr "Xətalı token başlığı. Token mətnində xətalı simvollar olmamalıdır." + +#: authentication.py:203 +msgid "Invalid token." +msgstr "Xətalı token." + +#: authtoken/apps.py:7 +msgid "Auth Token" +msgstr "Təsdiqləmə Tokeni" + +#: authtoken/models.py:13 +msgid "Key" +msgstr "Açar" + +#: authtoken/models.py:16 +msgid "User" +msgstr "İstifadəçi" + +#: authtoken/models.py:18 +msgid "Created" +msgstr "Yaradılıb" + +#: authtoken/models.py:27 authtoken/serializers.py:19 +msgid "Token" +msgstr "Token" + +#: authtoken/models.py:28 +msgid "Tokens" +msgstr "Tokenlər" + +#: authtoken/serializers.py:9 +msgid "Username" +msgstr "İstifadəçi adı" + +#: authtoken/serializers.py:13 +msgid "Password" +msgstr "Parol" + +#: authtoken/serializers.py:35 +msgid "Unable to log in with provided credentials." +msgstr "Təchiz edilən istifadəçi məlumatları ilə daxil olmaq mümkün olmadı." + +#: authtoken/serializers.py:38 +msgid "Must include \"username\" and \"password\"." +msgstr "Mütləq \"username\" və \"password\" olmalıdır." + +#: exceptions.py:102 +msgid "A server error occurred." +msgstr "Server xətası yaşandı." + +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 +msgid "Malformed request." +msgstr "Qüsurlu istək." + +#: exceptions.py:167 +msgid "Incorrect authentication credentials." +msgstr "Səhv təsdiqləmə məlumatları." + +#: exceptions.py:173 +msgid "Authentication credentials were not provided." +msgstr "Təsdiqləmə məlumatları təchiz edilməyib." + +#: exceptions.py:179 +msgid "You do not have permission to perform this action." +msgstr "Bu əməliyyat üçün icazəniz yoxdur." + +#: exceptions.py:185 +msgid "Not found." +msgstr "Tapılmadı." + +#: exceptions.py:191 +#, python-brace-format +msgid "Method \"{method}\" not allowed." +msgstr "\"{method}\" yöntəminə icazə verilmir." + +#: exceptions.py:202 +msgid "Could not satisfy the request Accept header." +msgstr "İstəyin Accept başlığını qane etmək mümkün olmadı." + +#: exceptions.py:212 +#, python-brace-format +msgid "Unsupported media type \"{media_type}\" in request." +msgstr "İstəkdə dəstəklənməyən \"{media_type}\" mediya növü." + +#: exceptions.py:223 +msgid "Request was throttled." +msgstr "İstək nəzərə alınmadı." + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "Bu sahə tələb edilir." + +#: fields.py:317 +msgid "This field may not be null." +msgstr "Bu sahə null ola bilməz." + +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 +msgid "This field may not be blank." +msgstr "Bu sahə boş ola bilməz." + +#: fields.py:768 fields.py:1881 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} characters." +msgstr "Bu sahənin ən çox {max_length} simvolu olduğuna əmin olun." + +#: fields.py:769 +#, python-brace-format +msgid "Ensure this field has at least {min_length} characters." +msgstr "Bu sahənin ən az {min_length} simvolu olduğuna əmin olun." + +#: fields.py:816 +msgid "Enter a valid email address." +msgstr "Keçərli e-poçt ünvanı daxil edin." + +#: fields.py:827 +msgid "This value does not match the required pattern." +msgstr "Bu dəyər tələb edilən formaya uyğun gəlmir." + +#: fields.py:838 +msgid "" +"Enter a valid \"slug\" consisting of letters, numbers, underscores or " +"hyphens." +msgstr "Hərf, rəqəm, alt xətt və defislərdən ibarət keçərli \"slug\" daxil edin." + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 +msgid "Enter a valid URL." +msgstr "Keçərli URL daxil edin." + +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" + +#: fields.py:903 +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "Keçərli IPv4 və ya IPv6 ünvanı daxil edin." + +#: fields.py:931 +msgid "A valid integer is required." +msgstr "Keçərli tam ədəd tələb edilir." + +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format +msgid "Ensure this value is less than or equal to {max_value}." +msgstr "Bu dəyərin uzunluğunun ən çox {max_value} olduğuna əmin olun." + +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format +msgid "Ensure this value is greater than or equal to {min_value}." +msgstr "Bu dəyərin uzunluğunun ən az {min_value} olduğuna əmin olun." + +#: fields.py:934 fields.py:971 fields.py:1010 +msgid "String value too large." +msgstr "Yazı dəyəri çox uzundur." + +#: fields.py:968 fields.py:1004 +msgid "A valid number is required." +msgstr "Keçərli rəqəm tələb edilir." + +#: fields.py:1007 +#, python-brace-format +msgid "Ensure that there are no more than {max_digits} digits in total." +msgstr "Ən çox {max_digits} rəqəm olduğuna əmin olun." + +#: fields.py:1008 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_decimal_places} decimal places." +msgstr "Ən çox {max_decimal_places} onluq kəsr hissəsi olduğuna əmin olun." + +#: fields.py:1009 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_whole_digits} digits before the " +"decimal point." +msgstr "Onluq kərsdən əvvəl ən çox {max_whole_digits} rəqəm olduğuna əmin olun." + +#: fields.py:1148 +#, python-brace-format +msgid "Datetime has wrong format. Use one of these formats instead: {format}." +msgstr "Datetime dəyəri səhvdir. Əvəzinə bu formatlardan birini işlədin: {format}." + +#: fields.py:1149 +msgid "Expected a datetime but got a date." +msgstr "Datetime gözlənirdi amma date gəldi." + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "Date dəyəri səhvdir. Əvəzinə bu formatlardan birini işlədin: {format}." + +#: fields.py:1237 +msgid "Expected a date but got a datetime." +msgstr "Date gözlənirdi amma datetime gəldi." + +#: fields.py:1303 +#, python-brace-format +msgid "Time has wrong format. Use one of these formats instead: {format}." +msgstr "Vaxt formatı səhvdir. Əvəzinə bu formatlardan birini işlədin: {format}." + +#: fields.py:1365 +#, python-brace-format +msgid "Duration has wrong format. Use one of these formats instead: {format}." +msgstr "Müddət formatı səhvdir. Əvəzinə bu formatlardan birini işlədin: {format}." + +#: fields.py:1399 fields.py:1456 +#, python-brace-format +msgid "\"{input}\" is not a valid choice." +msgstr "\"{input}\" keçərli seçim deyil." + +#: fields.py:1402 +#, python-brace-format +msgid "More than {count} items..." +msgstr "{count} elementdən daha çoxdur..." + +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format +msgid "Expected a list of items but got type \"{input_type}\"." +msgstr "Elementlər siyahısı gözlənirdi, amma \"{input_type}\" növü gəldi." + +#: fields.py:1458 +msgid "This selection may not be empty." +msgstr "Bu seçim boş ola bilməz." + +#: fields.py:1495 +#, python-brace-format +msgid "\"{input}\" is not a valid path choice." +msgstr "\"{input}\" keçərli yol seçimi deyil." + +#: fields.py:1514 +msgid "No file was submitted." +msgstr "Heç bir fayl göndərilmədi." + +#: fields.py:1515 +msgid "" +"The submitted data was not a file. Check the encoding type on the form." +msgstr "Göndərilən məlumat fayl deyildi. Anketin şifrələmə (encoding) növünü yoxlayın." + +#: fields.py:1516 +msgid "No filename could be determined." +msgstr "Faylın adı təyin edilə bilmədi." + +#: fields.py:1517 +msgid "The submitted file is empty." +msgstr "Göndərilən fayl boşdur." + +#: fields.py:1518 +#, python-brace-format +msgid "" +"Ensure this filename has at most {max_length} characters (it has {length})." +msgstr "Fayl adının ən çox {max_length} simvoldan ibarət olduğuna əmin olun (hazırki: {length})." + +#: fields.py:1566 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "Keçərli şəkil yükləyin. Yüklədiyiniz fayl ya şəkil deyil, ya da ola bilsin zədələnib." + +#: fields.py:1604 relations.py:486 serializers.py:571 +msgid "This list may not be empty." +msgstr "Bu siyahı boş ola bilməz." + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format +msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "Elementlərin kitabçası (dictionary) gözlənirdi amma \"{input_type}\" növü gəldi." + +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 +msgid "Value must be valid JSON." +msgstr "Dəyər keçərli JSON olmalıdır." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Axtarış" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Sıralama" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 +msgid "ascending" +msgstr "artan" + +#: filters.py:288 +msgid "descending" +msgstr "azalan" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 +msgid "Invalid page." +msgstr "Xətalı səhifə." + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "Xətalı kursor" + +#: relations.py:246 +#, python-brace-format +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "Xətalı pk \"{pk_value}\" - obyekt mövcud deyil." + +#: relations.py:247 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "Xətalı növ. PK dəyəri gözlənirdi, {data_type} alındı." + +#: relations.py:280 +msgid "Invalid hyperlink - No URL match." +msgstr "Xətalı hiperkeçid - Heç bir URL uyğun gəlmir." + +#: relations.py:281 +msgid "Invalid hyperlink - Incorrect URL match." +msgstr "Xətalı hiperkeçid - Xətalı URL uyğunluğu." + +#: relations.py:282 +msgid "Invalid hyperlink - Object does not exist." +msgstr "Xətalı hiperkeçid - Obyekt mövcud deyil." + +#: relations.py:283 +#, python-brace-format +msgid "Incorrect type. Expected URL string, received {data_type}." +msgstr "Xətalı növ. URL yazısı gözlənirdi, {data_type} gəldi." + +#: relations.py:448 +#, python-brace-format +msgid "Object with {slug_name}={value} does not exist." +msgstr "{slug_name}={value} üçün uyğun obyekt mövcud deyil." + +#: relations.py:449 +msgid "Invalid value." +msgstr "Xətalı dəyər." + +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "Xətalı məlumat. Kitabça (dictionary) gözlənirdi, {datatype} gəldi." + +#: templates/rest_framework/admin.html:116 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 +msgid "Filters" +msgstr "Filterlər" + +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" + +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" + +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "Heç nə" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "Seçiləcək element yoxdur." + +#: validators.py:39 +msgid "This field must be unique." +msgstr "Bu sahə unikal olmalıdır." + +#: validators.py:89 +#, python-brace-format +msgid "The fields {field_names} must make a unique set." +msgstr "{field_names} sahələri birlikdə unikal dəst olmalıdırlar." + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" date." +msgstr "Bu sahə \"{date_field}\" günü üçün unikal olmalıdır." + +#: validators.py:258 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" month." +msgstr "Bu sahə \"{date_field}\" ayı üçün unikal olmalıdır." + +#: validators.py:271 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" year." +msgstr "Bu sahə \"{date_field}\" ili üçün unikal olmalıdır." + +#: versioning.py:40 +msgid "Invalid version in \"Accept\" header." +msgstr "\"Accept\" başlığında xətalı versiya." + +#: versioning.py:71 +msgid "Invalid version in URL path." +msgstr "URL yolunda xətalı versiya." + +#: versioning.py:116 +msgid "Invalid version in URL path. Does not match any version namespace." +msgstr "URL yolunda xətalı versiya. Heç bir versiya namespace-inə uyğun gəlmir." + +#: versioning.py:148 +msgid "Invalid version in hostname." +msgstr "Hostname-də xətalı versiya." + +#: versioning.py:170 +msgid "Invalid version in query parameter." +msgstr "Sorğu parametrində xətalı versiya." diff --git a/rest_framework/locale/bg/LC_MESSAGES/django.mo b/rest_framework/locale/bg/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..71a0d8a74863adbce4792dd85f71dd318fa09d8c GIT binary patch literal 13083 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 Z)7DY7BZU1%)5`JoOQlb%aFFP}{9nBZ#RUKW literal 0 HcmV?d00001 diff --git a/rest_framework/locale/bg/LC_MESSAGES/django.po b/rest_framework/locale/bg/LC_MESSAGES/django.po new file mode 100644 index 0000000000..56e1c9ef84 --- /dev/null +++ b/rest_framework/locale/bg/LC_MESSAGES/django.po @@ -0,0 +1,572 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: Django REST framework\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" +"Language-Team: Bulgarian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/bg/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: bg\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: authentication.py:70 +msgid "Invalid basic header. No credentials provided." +msgstr "Невалиден header за базово удостоверение (basic authentication). Не се предоставени удостоверения." + +#: authentication.py:73 +msgid "Invalid basic header. Credentials string should not contain spaces." +msgstr "Невалиден header за базово удостоверение (basic authentication). Носителите на удостоверение не трябва да съдържат интервали." + +#: authentication.py:83 +msgid "Invalid basic header. Credentials not correctly base64 encoded." +msgstr "Невалиден header за базово удостоверение (basic authentication). Носителите на удостоверение не са кодирани в base64." + +#: authentication.py:101 +msgid "Invalid username/password." +msgstr "Невалидни потребителско име/парола." + +#: authentication.py:104 authentication.py:206 +msgid "User inactive or deleted." +msgstr "Потребителят е неактивен или изтрит." + +#: authentication.py:184 +msgid "Invalid token header. No credentials provided." +msgstr "Невалиден token header. Не са предоставени носители на удостоверение." + +#: authentication.py:187 +msgid "Invalid token header. Token string should not contain spaces." +msgstr "Невалиден token header. Жетона (token-a) не трябва да съдържа интервали." + +#: authentication.py:193 +msgid "" +"Invalid token header. Token string should not contain invalid characters." +msgstr "Невалиден token header. Жетона (token-a) не трябва да съдържа невалидни символи." + +#: authentication.py:203 +msgid "Invalid token." +msgstr "Невалиден жетон." + +#: authtoken/apps.py:7 +msgid "Auth Token" +msgstr "Жетон за удостоверение" + +#: authtoken/models.py:13 +msgid "Key" +msgstr "Ключ" + +#: authtoken/models.py:16 +msgid "User" +msgstr "Потребител" + +#: authtoken/models.py:18 +msgid "Created" +msgstr "Създаден" + +#: authtoken/models.py:27 authtoken/serializers.py:19 +msgid "Token" +msgstr "Жетон" + +#: authtoken/models.py:28 +msgid "Tokens" +msgstr "Жетони" + +#: authtoken/serializers.py:9 +msgid "Username" +msgstr "Потребителско име" + +#: authtoken/serializers.py:13 +msgid "Password" +msgstr "Парола" + +#: authtoken/serializers.py:35 +msgid "Unable to log in with provided credentials." +msgstr "Не е възможен вход с предоставените удостоверения." + +#: authtoken/serializers.py:38 +msgid "Must include \"username\" and \"password\"." +msgstr "Трябва да съдържа \"username\" (потребителско име) и \"password\" (парола)." + +#: exceptions.py:102 +msgid "A server error occurred." +msgstr "Сървърна грешка." + +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 +msgid "Malformed request." +msgstr "Неправилно оформена заявка." + +#: exceptions.py:167 +msgid "Incorrect authentication credentials." +msgstr "Невалидни носители на удостоверение." + +#: exceptions.py:173 +msgid "Authentication credentials were not provided." +msgstr "Носители на удостоверение не са предоставени." + +#: exceptions.py:179 +msgid "You do not have permission to perform this action." +msgstr "Нямате права да се направи това действие." + +#: exceptions.py:185 +msgid "Not found." +msgstr "Обектът не е намерен." + +#: exceptions.py:191 +#, python-brace-format +msgid "Method \"{method}\" not allowed." +msgstr "Метод \"{method}\" не е позволен." + +#: exceptions.py:202 +msgid "Could not satisfy the request Accept header." +msgstr "Не може да бъде удовлетворен Accept header." + +#: exceptions.py:212 +#, python-brace-format +msgid "Unsupported media type \"{media_type}\" in request." +msgstr "Неподдържан тип на документа \"{media_type}\" в заявката." + +#: exceptions.py:223 +msgid "Request was throttled." +msgstr "Заявката е ограничена." + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "Това поле е задължително." + +#: fields.py:317 +msgid "This field may not be null." +msgstr "Това поле не може да има празна стойност." + +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 +msgid "This field may not be blank." +msgstr "Това поле не може да е празно." + +#: fields.py:768 fields.py:1881 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} characters." +msgstr "Това поле не трябва да съдържа повече от {max_length} символа." + +#: fields.py:769 +#, python-brace-format +msgid "Ensure this field has at least {min_length} characters." +msgstr "Това поле трябва да съдържа поде {min_length} символа." + +#: fields.py:816 +msgid "Enter a valid email address." +msgstr "Въведете валиден имейл адрес." + +#: fields.py:827 +msgid "This value does not match the required pattern." +msgstr "Тази стойност не съответства на изисквания шаблон." + +#: fields.py:838 +msgid "" +"Enter a valid \"slug\" consisting of letters, numbers, underscores or " +"hyphens." +msgstr "Въведете валиден \"slug\" съдържащ латински букви, цифри, долни черти или тирета." + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 +msgid "Enter a valid URL." +msgstr "Въведете валиден URL." + +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" + +#: fields.py:903 +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "Въведете валиден IPv4 или IPv6 адрес." + +#: fields.py:931 +msgid "A valid integer is required." +msgstr "Необходимо е валидно цяло число." + +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format +msgid "Ensure this value is less than or equal to {max_value}." +msgstr "Тази стойност трябва да е не повече от {max_value}." + +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format +msgid "Ensure this value is greater than or equal to {min_value}." +msgstr "Тази стойност трябва да е поне {min_value}." + +#: fields.py:934 fields.py:971 fields.py:1010 +msgid "String value too large." +msgstr "Низът е прекалено голям." + +#: fields.py:968 fields.py:1004 +msgid "A valid number is required." +msgstr "Необходимо е валидно число." + +#: fields.py:1007 +#, python-brace-format +msgid "Ensure that there are no more than {max_digits} digits in total." +msgstr "Допустими са не повече от {max_digits} цифри." + +#: fields.py:1008 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_decimal_places} decimal places." +msgstr "Допустими са не повече от {max_decimal_places} цифри след десетичната запетая." + +#: fields.py:1009 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_whole_digits} digits before the " +"decimal point." +msgstr "Допустими са не повече от {max_whole_digits} цифри преди десетичната запетая." + +#: fields.py:1148 +#, python-brace-format +msgid "Datetime has wrong format. Use one of these formats instead: {format}." +msgstr "Датата и часът са в невалиден формат. Валидни са следните формати: {format}." + +#: fields.py:1149 +msgid "Expected a datetime but got a date." +msgstr "Очаква се дата и час, но е намерена само дата." + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "Дата е в невалиден формат. Валидни са следните формати: {format}." + +#: fields.py:1237 +msgid "Expected a date but got a datetime." +msgstr "Очаква се дата, но са подадени дата и час." + +#: fields.py:1303 +#, python-brace-format +msgid "Time has wrong format. Use one of these formats instead: {format}." +msgstr "Времето е в невалиден формат. Валидни са следните формати: {format}." + +#: fields.py:1365 +#, python-brace-format +msgid "Duration has wrong format. Use one of these formats instead: {format}." +msgstr "Отрязъкът от време е в невалиден формат. Валидни са следните формати: {format}." + +#: fields.py:1399 fields.py:1456 +#, python-brace-format +msgid "\"{input}\" is not a valid choice." +msgstr "\"{input}\" не е валиден избор." + +#: fields.py:1402 +#, python-brace-format +msgid "More than {count} items..." +msgstr "Повече от {count} неща..." + +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format +msgid "Expected a list of items but got type \"{input_type}\"." +msgstr "Очаква се списък от неща, но е получен тип \"{input_type}\"." + +#: fields.py:1458 +msgid "This selection may not be empty." +msgstr "Този избор не може да е празен." + +#: fields.py:1495 +#, python-brace-format +msgid "\"{input}\" is not a valid path choice." +msgstr "\"{input}\" не е валиден избор за път." + +#: fields.py:1514 +msgid "No file was submitted." +msgstr "Не е подаден файл." + +#: fields.py:1515 +msgid "" +"The submitted data was not a file. Check the encoding type on the form." +msgstr "Подадените данни не са файл. Проверете кодировката на формата." + +#: fields.py:1516 +msgid "No filename could be determined." +msgstr "Не може да бъде определено името на файла." + +#: fields.py:1517 +msgid "The submitted file is empty." +msgstr "Подадения файл е празен." + +#: fields.py:1518 +#, python-brace-format +msgid "" +"Ensure this filename has at most {max_length} characters (it has {length})." +msgstr "Името на файла може да е до {max_length} символа (то е {length})." + +#: fields.py:1566 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "Невалидно изображение. Подадения файл не е изображение или е повредено изображение." + +#: fields.py:1604 relations.py:486 serializers.py:571 +msgid "This list may not be empty." +msgstr "Този списък не може да е празен." + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format +msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "Очаква се асоциативен масив, но е получен \"{input_type}\"." + +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 +msgid "Value must be valid JSON." +msgstr "Стойността трябва да е валиден JSON." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Търсене" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Подредба" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 +msgid "ascending" +msgstr "в нарастващ ред" + +#: filters.py:288 +msgid "descending" +msgstr "в намаляващ ред" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 +msgid "Invalid page." +msgstr "Невалидна страница." + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "Невалиден курсор" + +#: relations.py:246 +#, python-brace-format +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "Невалиден идентификатор \"{pk_value}\" - обектът не съществува." + +#: relations.py:247 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "Неправилен тип. Очакван е тип за основен ключ, получен е {data_type}." + +#: relations.py:280 +msgid "Invalid hyperlink - No URL match." +msgstr "Невалидна връзка (hyperlink) - няма намерен URL." + +#: relations.py:281 +msgid "Invalid hyperlink - Incorrect URL match." +msgstr "Невалидна връзка (hyperlink) - неправилно съвпадение на URL." + +#: relations.py:282 +msgid "Invalid hyperlink - Object does not exist." +msgstr "Невалидна връзка (hyperlink) - обектът не съществува." + +#: relations.py:283 +#, python-brace-format +msgid "Incorrect type. Expected URL string, received {data_type}." +msgstr "Невалиден тип. Очаква се URL низ, получен е {data_type}." + +#: relations.py:448 +#, python-brace-format +msgid "Object with {slug_name}={value} does not exist." +msgstr "Обект с {slug_name}={value} не съществува." + +#: relations.py:449 +msgid "Invalid value." +msgstr "Невалидна стойност." + +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "Невалидни данни. Очаква се асоциативен масив, но е получен {datatype}." + +#: templates/rest_framework/admin.html:116 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 +msgid "Filters" +msgstr "Филтри" + +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" + +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" + +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "Нищо" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "Няма неща за избиране." + +#: validators.py:39 +msgid "This field must be unique." +msgstr "Това поле трябва да е уникално." + +#: validators.py:89 +#, python-brace-format +msgid "The fields {field_names} must make a unique set." +msgstr "Полетата {field_names} трябва да образуват уникална комбинация." + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" date." +msgstr "Това поле трябва да е уникално за \"{date_field}\" дата." + +#: validators.py:258 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" month." +msgstr "Това поле трябва да е уникално за \"{date_field}\" месец." + +#: validators.py:271 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" year." +msgstr "Това поле трябва да е уникално за \"{date_field}\" година." + +#: versioning.py:40 +msgid "Invalid version in \"Accept\" header." +msgstr "Невалидна версия в \"Accept\" header." + +#: versioning.py:71 +msgid "Invalid version in URL path." +msgstr "Невалидна версия в URL пътя." + +#: versioning.py:116 +msgid "Invalid version in URL path. Does not match any version namespace." +msgstr "Невалидна версия в URL пътя. Няма съвпадение с пространството от имена на версии." + +#: versioning.py:148 +msgid "Invalid version in hostname." +msgstr "Невалидна версия в името на сървъра (hostname)." + +#: versioning.py:170 +msgid "Invalid version in query parameter." +msgstr "Невалидна версия в GET параметър." diff --git a/rest_framework/locale/ca/LC_MESSAGES/django.mo b/rest_framework/locale/ca/LC_MESSAGES/django.mo index 0f72ec03947a137debd0497f8e67a4d84ca95ac7..7da9971a8ec201a5e8907883c09508c10db7c4d9 100644 GIT binary patch delta 1850 zcmYk+-A`0U7{~F4H&5*8 z`|uCnT`SGrrT+y+%@Vdq;|LD|*=D=&Lu|nd*p0tpGnV9d{~y2@{jYHgE@2*~2fYau zqMq0J9zp%yBsSm;R3Oi>nfF^>u2~ZgdaxDG;9gwBYRp+>wi?5zah+I!r?C{T;|{!! zHTXB`dlh`C3;S_5euMnkZ+yIoFVyG#mi>k|S%?aA+`2~@2026UkM$54AbiH&#xwN(%N zeipM+L=n`uespjg_2P4=T)2ys7+{vQSf9`O570QlgJQgmb@&voq>+RLW^d8|zR>%@ zN4S;#pQz`>grgOC2ld0pP!pWS{rD3ez#!qq@F?p4SACb(vHluZNJ?vuKEg0w#>4mo z6=^lItH+PA4`;9nU!qoMBjGCt!dQ*ReUliae+P96GFTqv&VFpc@dS+y8uNGvUt<)z z_|*uWMRvu0L5+KYdU44nFN?cTk&mKQZWf#HA!>X!%M-?4Bu#7@H{&J0pSVRs$Kf9G zZhMLRS&&hB(GFC<-**BP!FePoTR=_VDY96Wm6jMa`h<7%HmeqT|^@RfHt7doh7qn713LI=R%{YWPLTsIrHu)l_b@Qnf;y zLia}13DW!Bs2fnrq@v}~60301+>*(sR!N1Vx9wEr!D?!hsvJ3l{qQr`6+Nl37; delta 2103 zcmaLXTWl0n9LMof3cYaYg>5OV)D9~a#kRDR(%OrxU8uztini4Vi8$>}>`1#?y1Sqd z-9*zE600T~V+au{5)(r-sY@a+Xtd%BF&Hfm8pP;>vGGBLMB|I``ZPw4+90uxIKRZ0L)gEJf3%W(W7cCSTG)(t;t<}BM{zBlK;8d7 z-iBAO9)HFVgr7RyYN@+!uDdG!~4zCbgJ-0oxn4wnarVPaMkyQ zf4;aRJHuvF3VLuOI=BXB(89AAz)QFkzeX0zT*uA0h$$^qWoh=tI;`M0jE%S-m714O z16lBY|II%x!@o7ArLkds>n_}7<@D;T1yzf=4<@k61_iARR73)U5&#qwox6zs5#Dn-L z?!jV8x*s1${eI5(2h??yj7m9-;JtVThw&P=;ufaej8EYZp29l3i5jrQa;QeSQgj;V zjQPHfA&x&oZIgViR&njY4m^T8F@uBnCx&o$wK1LeGLlwv8Fk$?)Qf{mTSY#Ant2kn zlBqZ8w9#2W-LR@AJEK9Q-pma0XWsCSFQOjwIWk6b6S>tCTiF*ippN(Z9z{*y1TuN^ z32Fe}B8!sHhDQ_~C7^a0Vs71hr zb@O5LmRxnD#)6vr32kwuV5jo0rPZ?OgP=61>1+1yMxl5`SdJ95*Vy{E+THZJiGE@Q z!FKmjM<&bwp*jf@1%!5u()KXnt&@sFg{Ia^1iW7MlWBs-+~{O7@odm?<5o15ux#tF zJ?=)Vkyvcpv7-&?3wgcO!DF0EI{&?DczD;2%wKuW7nbgIrY78Y+>J%8h!b_4Nanec zp4@;vIvPtx6MA~YjoTyRP9%LXaJHzawZ5^fzOmVA+S=aImX4KGSVLp62|I3u$EMtP z!gZ|9L~J5NjQS@sZ\n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Catalan (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ca/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -17,40 +17,40 @@ msgstr "" "Language: ca\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Header Basic invàlid. No hi ha disponibles les credencials." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Header Basic invàlid. Les credencials no poden contenir espais." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Header Basic invàlid. Les credencials no estan codificades correctament en base64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Usuari/Contrasenya incorrectes." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Usuari inactiu o esborrat." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Token header invàlid. No s'han indicat les credencials." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Token header invàlid. El token no ha de contenir espais." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Token header invàlid. El token no pot contenir caràcters invàlids." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Token invàlid." @@ -58,382 +58,515 @@ msgstr "Token invàlid." msgid "Auth Token" msgstr "" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Compte d'usuari desactivat." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "No es possible loguejar-se amb les credencials introduïdes." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "S'ha d'incloure \"username\" i \"password\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "S'ha produït un error en el servidor." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Request amb format incorrecte." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Credencials d'autenticació incorrectes." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Credencials d'autenticació no disponibles." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "No té permisos per realitzar aquesta acció." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "No trobat." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Mètode \"{method}\" no permès." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "No s'ha pogut satisfer l'Accept header de la petició." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Media type \"{media_type}\" no suportat." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "La petició ha estat limitada pel número màxim de peticions definit." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Aquest camp és obligatori." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Aquest camp no pot ser nul." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" no és un booleà." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." msgstr "Aquest camp no pot estar en blanc." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Aquest camp no pot tenir més de {max_length} caràcters." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Aquest camp ha de tenir un mínim de {min_length} caràcters." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Introdueixi una adreça de correu vàlida." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Aquest valor no compleix el patró requerit." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Introdueix un \"slug\" vàlid consistent en lletres, números, guions o guions baixos." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Introdueixi una URL vàlida." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" no és un UUID vàlid." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Introdueixi una adreça IPv4 o IPv6 vàlida." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Es requereix un nombre enter vàlid." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Aquest valor ha de ser menor o igual a {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Aquest valor ha de ser més gran o igual a {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Valor del text massa gran." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Es requereix un nombre vàlid." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "No pot haver-hi més de {max_digits} dígits en total." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "No pot haver-hi més de {max_decimal_places} decimals." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "No pot haver-hi més de {max_whole_digits} dígits abans del punt decimal." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "El Datetime té un format incorrecte. Utilitzi un d'aquests formats: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "S'espera un Datetime però s'ha rebut un Date." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "El Date té un format incorrecte. Utilitzi un d'aquests formats: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "S'espera un Date però s'ha rebut un Datetime." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "El Time té un format incorrecte. Utilitzi un d'aquests formats: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "La durada té un format incorrecte. Utilitzi un d'aquests formats: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" no és una opció vàlida." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "" -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "S'espera una llista d'ítems però s'ha rebut el tipus \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Aquesta selecció no pot estar buida." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" no és un path vàlid." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "No s'ha enviat cap fitxer." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Les dades enviades no són un fitxer. Comproveu l'encoding type del formulari." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "No s'ha pogut determinar el nom del fitxer." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "El fitxer enviat està buit." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "El nom del fitxer ha de tenir com a màxim {max_length} caràcters (en té {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Envieu una imatge vàlida. El fitxer enviat no és una imatge o és una imatge corrompuda." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Aquesta llista no pot estar buida." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "S'espera un diccionari però s'ha rebut el tipus \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:336 +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Cursor invàlid." -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "PK invàlida \"{pk_value}\" - l'objecte no existeix." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Tipus incorrecte. S'espera el valor d'una PK, s'ha rebut {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Hyperlink invàlid - Cap match d'URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Hyperlink invàlid - Match d'URL incorrecta." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Hyperlink invàlid - L'objecte no existeix." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Tipus incorrecte. S'espera una URL, s'ha rebut {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "L'objecte amb {slug_name}={value} no existeix." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Valor invàlid." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Dades invàlides. S'espera un diccionari però s'ha rebut {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Cap" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Cap opció seleccionada." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Aquest camp ha de ser únic." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Aquests camps {field_names} han de constituir un conjunt únic." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Aquest camp ha de ser únic per a la data \"{date_field}\"." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Aquest camp ha de ser únic per al mes \"{date_field}\"." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Aquest camp ha de ser únic per a l'any \"{date_field}\"." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Versió invàlida al header \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Versió invàlida a la URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Versió invàlida al hostname." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Versió invàlida al paràmetre de consulta." - -#: views.py:88 -msgid "Permission denied." -msgstr "Permís denegat." diff --git a/rest_framework/locale/cs/LC_MESSAGES/django.mo b/rest_framework/locale/cs/LC_MESSAGES/django.mo index 1561cd98c570a634e4abd2fcbda46ab764e56ca7..ebf7db5aa7b4cb3f8bc3c7e406e4246ea92501ce 100644 GIT binary patch delta 3414 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\n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Czech (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/cs/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: cs\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Chybná hlavička. Nebyly poskytnuty přihlašovací údaje." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Chybná hlavička. Přihlašovací údaje by neměly obsahovat mezery." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Chybná hlavička. Přihlašovací údaje nebyly správně zakódovány pomocí base64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Chybné uživatelské jméno nebo heslo." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Uživatelský účet je neaktivní nebo byl smazán." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Chybná hlavička tokenu. Nebyly zadány přihlašovací údaje." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Chybná hlavička tokenu. Přihlašovací údaje by neměly obsahovat mezery." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." -msgstr "" +msgstr "Chybná hlavička s tokenem. Token nesmí obsahovat nevalidní znaky." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Chybný token." #: authtoken/apps.py:7 msgid "Auth Token" -msgstr "" +msgstr "Autentizační token" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" -msgstr "" +msgstr "Klíč" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "Uživatel" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" -msgstr "" +msgstr "Vytvořeno" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" -msgstr "" +msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" -msgstr "" +msgstr "Tokeny" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "Uživatelské jméno" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" +msgstr "Heslo" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Uživatelský účet je uzamčen." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Zadanými údaji se nebylo možné přihlásit." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Musí obsahovat \"uživatelské jméno\" a \"heslo\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Chyba na straně serveru." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Neplatný formát požadavku." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Chybné přihlašovací údaje." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Nebyly zadány přihlašovací údaje." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "K této akci nemáte oprávnění." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Nenalezeno." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Metoda \"{method}\" není povolena." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Nelze vyhovět požadavku v hlavičce Accept." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Nepodporovaný media type \"{media_type}\" v požadavku." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Požadavek byl limitován kvůli omezení počtu požadavků za časovou periodu." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Toto pole je vyžadováno." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Toto pole nesmí být prázdné (null)." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" nelze použít jako typ boolean." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." msgstr "Toto pole nesmí být prázdné." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Zkontrolujte, že toto pole není delší než {max_length} znaků." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Zkontrolujte, že toto pole obsahuje alespoň {min_length} znaků." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Vložte platnou e-mailovou adresu." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Hodnota v tomto poli neodpovídá požadovanému formátu." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Vložte platnou \"zkrácenou formu\" obsahující pouze malá písmena, čísla, spojovník nebo podtržítko." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Vložte platný odkaz." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" není platné UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "" +msgstr "Vložte platnou IPv4 nebo IPv6 adresu." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Je vyžadováno celé číslo." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Zkontrolujte, že hodnota je menší nebo rovna {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Zkontrolujte, že hodnota je větší nebo rovna {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Řetězec je příliš dlouhý." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Je vyžadováno číslo." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Zkontrolujte, že číslo neobsahuje více než {max_digits} čislic." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Zkontrolujte, že číslo nemá více než {max_decimal_places} desetinných míst." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Zkontrolujte, že číslo neobsahuje více než {max_whole_digits} čislic před desetinnou čárkou." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Chybný formát data a času. Použijte jeden z těchto formátů: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Bylo zadáno pouze datum bez času." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Chybný formát data. Použijte jeden z těchto formátů: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Bylo zadáno datum a čas, místo samotného data." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Chybný formát času. Použijte jeden z těchto formátů: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "Trvání má nesprávný formát. Použijte jeden z následujících: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" není platnou možností." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." -msgstr "" +msgstr "Více než {count} položek..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Byl očekáván seznam položek ale nalezen \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." -msgstr "" +msgstr "Tento výběr by neměl být prázdný." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." -msgstr "" +msgstr "\"{input}\" není validní cesta k souboru." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Nebyl zaslán žádný soubor." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Zaslaná data neobsahují soubor. Zkontrolujte typ kódování ve formuláři." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Nebylo možné zjistit jméno souboru." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Zaslaný soubor je prázdný." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Zajistěte, aby jméno souboru obsahovalo maximálně {max_length} znaků (teď má {length} znaků)." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Nahrajte platný obrázek. Nahraný soubor buď není obrázkem nebo je poškozen." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." +msgstr "Tento list by neměl být prázdný." + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." msgstr "" -#: fields.py:1502 +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Byl očekáván slovník položek ale nalezen \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." +msgstr "Hodnota musí být platná hodnota JSON." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Hledat" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Řazení" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" -msgstr "" +msgstr "vzestupně" -#: filters.py:337 +#: filters.py:288 msgid "descending" +msgstr "sestupně" + +#: pagination.py:174 +msgid "A page number within the paginated result set." msgstr "" -#: pagination.py:193 +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." +msgstr "Nevalidní strana." + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." msgstr "" -#: pagination.py:427 +#: pagination.py:583 msgid "Invalid cursor" -msgstr "Chybný kurzor." +msgstr "Chybný kurzor" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Chybný primární klíč \"{pk_value}\" - objekt neexistuje." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Chybný typ. Byl přijat typ {data_type} místo hodnoty primárního klíče." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Chybný odkaz - nebyla nalezena žádní shoda." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Chybný odkaz - byla nalezena neplatná shoda." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Chybný odkaz - objekt neexistuje." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Chybný typ. Byl přijat typ {data_type} místo očekávaného odkazu." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objekt s {slug_name}={value} neexistuje." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Chybná hodnota." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Chybná data. Byl přijat typ {datatype} místo očekávaného slovníku." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" +msgstr "Filtry" + +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:78 +msgid "request form" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:157 +msgid "main content" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 -msgid "None" +#: templates/rest_framework/base.html:173 +msgid "request info" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 -msgid "No items to select." +#: templates/rest_framework/base.html:177 +msgid "response info" msgstr "" -#: validators.py:43 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "Neuvedeno" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "Žádné položky k výběru." + +#: validators.py:39 msgid "This field must be unique." msgstr "Tato položka musí být unikátní." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Položka {field_names} musí tvořit unikátní množinu." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Tato položka musí být pro datum \"{date_field}\" unikátní." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Tato položka musí být pro měsíc \"{date_field}\" unikátní." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Tato položka musí být pro rok \"{date_field}\" unikátní." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Chybné číslo verze v hlavičce Accept." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Chybné číslo verze v odkazu." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "Nevalidní verze v URL cestě. Neodpovídá žádnému z jmenných prostorů pro verze." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Chybné číslo verze v hostname." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Chybné čislo verze v URL parametru." - -#: views.py:88 -msgid "Permission denied." -msgstr "" diff --git a/rest_framework/locale/da/LC_MESSAGES/django.mo b/rest_framework/locale/da/LC_MESSAGES/django.mo index 77fd7c2abbe3eda1b7b4e4862469a3970ef24175..d70bc13a51ec471f0bb2093aac13daead653e7e2 100644 GIT binary patch delta 2193 zcmYk-e@xVM9LMqZb({hkBEQZ{!7qgz5*(mFh^U7ll7OJ30#Yc-3R7;eN%BX#TpN=% z=GOJQRvR15wOy;-@{hLG)@sg{Te@tP(^+k8%?(?Bm|MB^e0}fB)@QtVd_LdfzTeO1 z^L~H6-0S_VJAN}S<8`AA6KjY9zgZlYFX0z0Gt}4y*8c zT#EN`I|j1M+Ofm)Ebi2!exwuSgOY5s{Wy$|;d$JQ3)qR3%gmZ_67~6~*oA-KBiOjy zEDr}!6FY&r{)*?vsQX>P2F!U-6J)7vbhNTA)B`5436r=Df5*qMJjX18$B-X;n_v37 zb6A1DU@2y=V6s?+eANzMEskOY-@qsE8{DF;UXh!=VK+X+c*q;SfLg&cYDFKRw(^2E z&R&`RJdA#>ccONn2TO1Sb-&XX#`mxYzeWeIV?3YEzjQJ%mv6J%R){s|AnCJS&jHj8 zMo{H3g+Y7?Yw;~4r*;uF;g#H6pGQ#Rc2un#!A9ia@pP`e3k#?juPaFZVH6$4L#PS8 zh}wZ92JlbRMDF6FcpvLwmBdcQA`?d=<4*x8ihE%@y3W z4G*El^QhvxfjTzD>{~M)#ZCAD?#7=`#a6-oGFR(E<^C9!;|wbIpI{WP;bsgmYwcWo zAD#7dhOh~zu@%2W&Dh7?I&e2?Yu`i)-7cbTbPe@@g0<;W)PkDO2y)VF7B%sAkWIF$ z*on81=a7Gg6TpQjWO6p+jX%UP#^+JjucN+r4_UOem8M@bhfph-MRwU7#Ae|={2dFyPMN}&PYI_M4jq;~|8cItOp(8d-1PHYjVw>Ab|5Do2 zdI{A?m{65JK&W<_i4KB)8E&zH^$`aM9eW)~x2YgBSWDC-W;2Tu-(^Npp{%`#1KbTwx~ZFL<#8=bmM&Gik5WH4CPe|U0ibi(PGI2wEYJx+ZUqZ`2`98QbvjfYNTo{p1BumTt*OzniWFKvTaYRjx1C|L>~@yyPHTji z4aOIEs%a1vqKSzRBTsCiNW_p3O*GNOXu<+Jl~w5)4&(kk$`3&O%JnWBO!64%ksI9&{kNj%|Ii=YfD^cSn zR1pneBOXE3z;rU=TGvfSUMmwD4?- zP6eGSSdTYwJxAe1a?R2C7DKxY&-Xuoa`o2{h+XsZHY(co|jwx3B{%Dzg(m zh+7$7K&Adqq?l7?rIr1`M%056sAKmQYGPNBqipV=R&XELK2yIm`yS{)J?Ip2!pwDK zac0UN=aKhij7w41n{hkt!E&Adcj>I*!l$T}+(EX_{Ej+iMSPG1vlgqd4=G?XhAh&Y zL{6%CA60CZQ4_w3s)esmwelAhU@o)KM2oPJ=bLgmdDx0rq8t$Z3uidn+GQa)xgrZRs(N5wFX`FIm&;%(Ff?qVla zvOi%QLVf=wDl^~s{)##c#iU7@S%|gRgj&!c-#E@-yhr=5YSha^fB2wricCMD^rP%L6wL-Bab3Awcsg@s!y0>`*G>iqw%-Mf3b z(>n_9=M_0I+d349c}^mk?kcX#FWKcJMk2{%B<@IC9u&kGS^ea5PyPPlVIomzB;~;96cJ77N*4W;MyEHFoV>!j4Q# zFQ_7dOCK)FNm!9+)Uh1T3cnPyBVo_VEUji?b$(I*wM$_~YfCukfr`QWIURO5o0u6R7= diff --git a/rest_framework/locale/da/LC_MESSAGES/django.po b/rest_framework/locale/da/LC_MESSAGES/django.po index 9006956490..574066f2a5 100644 --- a/rest_framework/locale/da/LC_MESSAGES/django.po +++ b/rest_framework/locale/da/LC_MESSAGES/django.po @@ -9,9 +9,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Mads Jensen \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Danish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/da/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,40 +19,40 @@ msgstr "" "Language: da\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Ugyldig basic header. Ingen legitimation angivet." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Ugyldig basic header. Legitimationsstrenge må ikke indeholde mellemrum." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Ugyldig basic header. Legitimationen er ikke base64 encoded på korrekt vis." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Ugyldigt brugernavn/kodeord." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Inaktiv eller slettet bruger." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Ugyldig token header." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Ugyldig token header. Token-strenge må ikke indeholde mellemrum." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Ugyldig token header. Token streng bør ikke indeholde ugyldige karakterer." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Ugyldigt token." @@ -60,382 +60,515 @@ msgstr "Ugyldigt token." msgid "Auth Token" msgstr "" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Nøgle" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Bruger" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Oprettet" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokens" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Brugernavn" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Kodeord" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Brugerkontoen er deaktiveret." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Kunne ikke logge ind med den angivne legitimation." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Skal indeholde \"username\" og \"password\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Der er sket en serverfejl." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Misdannet forespørgsel." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Ugyldig legitimation til autentificering." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Legitimation til autentificering blev ikke angivet." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Du har ikke lov til at udføre denne handling." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Ikke fundet." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Metoden \"{method}\" er ikke tilladt." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Kunne ikke efterkomme forespørgslens Accept header." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Forespørgslens media type, \"{media_type}\", er ikke understøttet." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Forespørgslen blev neddroslet." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Dette felt er påkrævet." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Dette felt må ikke være null." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" er ikke en tilladt boolsk værdi." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Dette felt må ikke være tomt." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Tjek at dette felt ikke indeholder flere end {max_length} tegn." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Tjek at dette felt indeholder mindst {min_length} tegn." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Angiv en gyldig e-mailadresse." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Denne værdi passer ikke med det påkrævede mønster." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Indtast en gyldig \"slug\", bestående af bogstaver, tal, bund- og bindestreger." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Indtast en gyldig URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" er ikke et gyldigt UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Indtast en gyldig IPv4 eller IPv6 adresse." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Et gyldigt heltal er påkrævet." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Tjek at værdien er mindre end eller lig med {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Tjek at værdien er større end eller lig med {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Strengværdien er for stor." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Et gyldigt tal er påkrævet." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Tjek at der ikke er flere end {max_digits} cifre i alt." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Tjek at der ikke er flere end {max_decimal_places} cifre efter kommaet." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Tjek at der ikke er flere end {max_whole_digits} cifre før kommaet." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datotid har et forkert format. Brug i stedet et af disse formater: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Forventede en datotid, men fik en dato." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Dato har et forkert format. Brug i stedet et af disse formater: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Forventede en dato men fik en datotid." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Klokkeslæt har forkert format. Brug i stedet et af disse formater: {format}. " -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Varighed har forkert format. Brug istedet et af følgende formater: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" er ikke et gyldigt valg." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Flere end {count} objekter..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Forventede en liste, men fik noget af typen \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Dette valg kan være tomt." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" er ikke et gyldigt valg af adresse." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Ingen medsendt fil." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Det medsendte data var ikke en fil. Tjek typen af indkodning på formularen." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Filnavnet kunne ikke afgøres." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Den medsendte fil er tom." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Sørg for at filnavnet er højst {max_length} langt (det er {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Medsend et gyldigt billede. Den medsendte fil var enten ikke et billede eller billedfilen var ødelagt." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Denne liste er muligvis ikke tom." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Forventede en dictionary, men fik noget af typen \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Værdi skal være gyldig JSON." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Indsend." +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Søg" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Sortering" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "stigende" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "faldende" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Ugyldig side" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Ugyldig cursor" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Ugyldig primærnøgle \"{pk_value}\" - objektet findes ikke." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Ugyldig type. Forventet værdi er primærnøgle, fik {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Ugyldigt hyperlink - intet URL match." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Ugyldigt hyperlink - forkert URL match." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Ugyldigt hyperlink - objektet findes ikke." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Forkert type. Forventede en URL-streng, fik {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Object med {slug_name}={value} findes ikke." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Ugyldig værdi." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Ugyldig data. Forventede en dictionary, men fik {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtre" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Søgefiltre" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Sortering" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Søg" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Ingen" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Intet at vælge." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Dette felt skal være unikt." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Felterne {field_names} skal udgøre et unikt sæt." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Dette felt skal være unikt for \"{date_field}\"-datoen." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Dette felt skal være unikt for \"{date_field}\"-måneden." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Dette felt skal være unikt for \"{date_field}\"-året." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Ugyldig version i \"Accept\" headeren." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Ugyldig version i URL-stien." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Ugyldig version in URLen. Den stemmer ikke overens med nogen versionsnumre." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Ugyldig version i hostname." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Ugyldig version i forespørgselsparameteren." - -#: views.py:88 -msgid "Permission denied." -msgstr "Adgang nægtet." diff --git a/rest_framework/locale/de/LC_MESSAGES/django.mo b/rest_framework/locale/de/LC_MESSAGES/django.mo index 0042572ef9e2c518221cf6536bbbb57b11626fb4..98688039789f0300dd6aed554c84050365f9991c 100644 GIT binary patch delta 2243 zcmYk-drXye9LMo5aq)npDFU5PIZ((&I33i3aDY7CLr8=GB`Y{fmy~(r5?SU>3(?g^ zYo0M{6StakU0j!)+FDzGgl+Xlx>j?e&6(L)ZLZl||D-E>f1c;D^&8*w`u(2soagyn zzP}&i-8VWCmlkGB8l|6DtVlN-lUaOFDtu-Eti&?hf_LM7)cvnu5uU~zoW>eFht+r! z=U_#)**Xm2CLDH6*ymI>b0LjiN3jKa@Ks!mKVu{M=bE)*H%9O+)b|&$4HxB_-HS0? zh~uaZzTsW})bmHw^Zv%LCCnP`bbA;_jqo|t3#PChXK)QJm}j;gJ1~fo$e(@1hwl3U zD=>GySqX-*3b!MVu$M7}uVWCu#1`Ife^6=0$X)IO58yqtk9zI5Q6u{pHKGfssl4vB z!wcN+J29Q>qo^5p0hi(g>Un3d9KXf_yoQMYl`QVg!*a~PMr774iuYqHvW#}n^DycG z6R0#fg@yP&hVU$EN&iH3xQU1B`(9Lg5S25p4XLw6jTlEkzKu#+|4&A3=RTh5Fqz(li6%WMt%#m=Cw=Qj^10t*8f|K;8H&@@Ic}?V?imV5vdgVLMSBKj^imQQ3V3wFEh=la?SK^_(hH zF12Ab?!i*+|4Hw{XUHIJ7Bv$YEa-fkiyA=@>cOq3B7+$mw$nK+7B_#$c- zjA1i=hY35qN0&cptAK0a;n-ds1aYoG)yOyG+OW5Wiu6xT1BUv zc8@yMNT?Gk4TPSreCa3p2)&1rFb*-NXhd4~M~NQiiu*yQos3?E>P&!8d4$lcbr8yQ zCe4}dR#eddR30XLPR*TFPHCrTH&ko=|6kV9==CnFL}u98hFVW0lZs9_Cd3{iGKdJF zj4vRR$STU4PNJJ2o$PT!+n8PMln42Y61jSR2~kC;kSosqS6yp)yJ!n42{#eN-o+YJ zM(_06mDujpweB1NPTArq^p9sfp+rxenYDc_Rh zMPD#gnVpuIs-L$wGx=m*UFuX`d3tiT;6lraP)#j=D+9F+kyYX4AxchLYZ^H9tyM%?TQaR`+T73K-{fG_YUlfj}F9wPsE=|{Zp8imTdN~P966L Yvr^v`|CE_LTNX+AmYq*eUaw643q5Y%YybcN delta 2567 zcmZYATWnNC9LMo#x0i*=Z3}Hd+a61ia%pXWvb9`#p-@UI6lg&T0^9D{?Xk-)+1&yG zVV^J(h*2A&Viiq{iSdDlVijXz0QJQniAG~$qGBLMB|M-{1f$>I*+YDA!oQ!H*>jos zpPAv_)|3629|L)34JAT6LiG5IIfc&_@Q3o2-2;f5@0- z96NpUd=p^shl>qK!ZLbbXl715;EM;tN=UlW5^JEXF&S zhq*NPDy9%0#{jYnOuJ_{a;fPHD;7UA=`o>qh_C3@D8s=oL>p_e5FlvCO(9HTP!e*=|9I(A_c zHPg#D7r*fQ88y>;UfaiXmeDRp*01SBWo7_h%Q41Az2G0VMl~+sVfs|PScPfST6iTx zMT_GS>cU^pO)=AAawdQ)aUp8k?8Pk@!TmUieOOAG`Z0!^@d`eLvq+=X(lhuZUc~*F zTW-vH%nVWKpz0pKMY;y=Tu$OQ(PI~@{+Gh2Y*=>6qHSo_+DZY+m+kA&@ zcpJ4F>a6TQqPR)>{{t#Peo#KNc$%;kyHP1VgW46JqQ2=JWMP{M^4N&=sQV+R8NP`; z*xW{47bNf8V!BZ65v;}ISjqd%`&2g2@ul0rAegRp!!p#222h{EMm^{#YEh2kQv3*c zm$~J&{Tvn~$26ic(T6i|H);YSsOP?pe%^21rNRa<7m)0mtC){BP$~Qsb;IAd7X6QL zR$wP;K<82SUG~~PdFC*S_4Lp8bg+Z=1+?%l%;*7gSb>^B5S7va)S`M3^YIoI;2)?} zeHYhY#Ui#E?m<6(j>^neXyNb3DQt=sXERcUTFgN#!uVqHubCaCBL_9BWL|QBew&E)^}1J;Z*Z zlhCYnVyWnC*JqhVY#+)y-|ICRaGh7TP>W?Z zkxN7frFI9Q#iimSN{JR?7ooy`8SY{YVVEc+RP>p(`X3aVN*TqcV@~|wX!>}S<)o~5B5j4NL!p=xwgwZ4m>r7Ojn()!mRB94 zbJYI-RsH>49TRcCFTco+g{_DaOWVoRM0{FhexTP*9&l1AClR;8cHFVUbz^4&!Gi6h zg9n`S#FapeZ+2*CC@~sO>(yZ=6&j4`qMLJmnBEYqt#7KWUv4$5Xl`s8yH-+K*)uv4 zN?GkrYG^nS8MKp8J87*xV7DGlB#+d^oC$wvan4*Tdi9f7+R<-2L&M2o+exPm+sSm@ zn7?dOp*M@MnPnw;?!bG?>I%QWVL_D2f, 2015 -# datKater , 2017 +# 5a85a00218ad0559ac6870a4179f4dbc, 2017 # Lukas Bischofberger , 2017 # Mads Jensen , 2015 # Niklas P , 2015-2016 @@ -15,9 +15,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Lukas Bischofberger \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: German (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/de/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -25,40 +25,40 @@ msgstr "" "Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Ungültiger basic header. Keine Zugangsdaten angegeben." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Ungültiger basic header. Zugangsdaten sollen keine Leerzeichen enthalten." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Ungültiger basic header. Zugangsdaten sind nicht korrekt mit base64 kodiert." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Ungültiger Benutzername/Passwort" -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Benutzer inaktiv oder gelöscht." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Ungültiger token header. Keine Zugangsdaten angegeben." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Ungültiger token header. Zugangsdaten sollen keine Leerzeichen enthalten." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Ungültiger Token Header. Tokens dürfen keine ungültigen Zeichen enthalten." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Ungültiges Token" @@ -66,382 +66,515 @@ msgstr "Ungültiges Token" msgid "Auth Token" msgstr "Auth Token" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Schlüssel" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Benutzer" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Erzeugt" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokens" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Benutzername" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Passwort" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Benutzerkonto ist gesperrt." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Die angegebenen Zugangsdaten stimmen nicht." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "\"username\" und \"password\" sind erforderlich." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Ein Serverfehler ist aufgetreten." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Fehlerhafte Anfrage." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Falsche Anmeldedaten." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Anmeldedaten fehlen." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Sie sind nicht berechtigt diese Aktion durchzuführen." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Nicht gefunden." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Methode \"{method}\" nicht erlaubt." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Kann die Accept Kopfzeile der Anfrage nicht erfüllen." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Nicht unterstützter Medientyp \"{media_type}\" in der Anfrage." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Die Anfrage wurde gedrosselt." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Dieses Feld ist erforderlich." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Dieses Feld darf nicht null sein." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" ist kein gültiger Wahrheitswert." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Dieses Feld darf nicht leer sein." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Stelle sicher, dass dieses Feld nicht mehr als {max_length} Zeichen lang ist." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Stelle sicher, dass dieses Feld mindestens {min_length} Zeichen lang ist." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Gib eine gültige E-Mail Adresse an." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Dieser Wert passt nicht zu dem erforderlichen Muster." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Gib ein gültiges \"slug\" aus Buchstaben, Ziffern, Unterstrichen und Minuszeichen ein." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Gib eine gültige URL ein." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" ist keine gültige UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Geben Sie eine gültige IPv4 oder IPv6 Adresse an" -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Eine gültige Ganzzahl ist erforderlich." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Stelle sicher, dass dieser Wert kleiner oder gleich {max_value} ist." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Stelle sicher, dass dieser Wert größer oder gleich {min_value} ist." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Zeichenkette zu lang." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Eine gültige Zahl ist erforderlich." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Stelle sicher, dass es insgesamt nicht mehr als {max_digits} Ziffern lang ist." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Stelle sicher, dass es nicht mehr als {max_decimal_places} Nachkommastellen lang ist." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Stelle sicher, dass es nicht mehr als {max_whole_digits} Stellen vor dem Komma lang ist." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datums- und Zeitangabe hat das falsche Format. Nutze stattdessen eines dieser Formate: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Erwarte eine Datums- und Zeitangabe, erhielt aber ein Datum." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Datum hat das falsche Format. Nutze stattdessen eines dieser Formate: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Erwarte ein Datum, erhielt aber eine Datums- und Zeitangabe." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Zeitangabe hat das falsche Format. Nutze stattdessen eines dieser Formate: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Laufzeit hat das falsche Format. Benutze stattdessen eines dieser Formate {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" ist keine gültige Option." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Mehr als {count} Ergebnisse" -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Erwarte eine Liste von Elementen, erhielt aber den Typ \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Diese Auswahl darf nicht leer sein" -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" ist ein ungültiger Pfad." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Es wurde keine Datei übermittelt." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Die übermittelten Daten stellen keine Datei dar. Prüfe den Kodierungstyp im Formular." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Der Dateiname konnte nicht ermittelt werden." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Die übermittelte Datei ist leer." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Stelle sicher, dass dieser Dateiname höchstens {max_length} Zeichen lang ist (er hat {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Lade ein gültiges Bild hoch. Die hochgeladene Datei ist entweder kein Bild oder ein beschädigtes Bild." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Diese Liste darf nicht leer sein." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Erwartete ein Dictionary mit Elementen, erhielt aber den Typ \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Wert muss gültiges JSON sein." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Abschicken" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Suche" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Sortierung" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "Aufsteigend" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "Absteigend" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Ungültige Seite." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Ungültiger Zeiger" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Ungültiger pk \"{pk_value}\" - Object existiert nicht." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Falscher Typ. Erwarte pk Wert, erhielt aber {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Ungültiger Hyperlink - entspricht keiner URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Ungültiger Hyperlink - URL stimmt nicht überein." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Ungültiger Hyperlink - Objekt existiert nicht." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Falscher Typ. Erwarte URL Zeichenkette, erhielt aber {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objekt mit {slug_name}={value} existiert nicht." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Ungültiger Wert." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Ungültige Daten. Dictionary erwartet, aber {datatype} erhalten." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filter" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Feldfilter" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Sortierung" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Suche" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Nichts" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Keine Elemente zum Auswählen." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Dieses Feld muss eindeutig sein." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Die Felder {field_names} müssen eine eindeutige Menge bilden." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Dieses Feld muss bezüglich des \"{date_field}\" Datums eindeutig sein." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Dieses Feld muss bezüglich des \"{date_field}\" Monats eindeutig sein." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Dieses Feld muss bezüglich des \"{date_field}\" Jahrs eindeutig sein." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Ungültige Version in der \"Accept\" Kopfzeile." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Ungültige Version im URL Pfad." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Ungültige Version im URL-Pfad. Entspricht keinem Versions-Namensraum." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Ungültige Version im Hostname." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Ungültige Version im Anfrageparameter." - -#: views.py:88 -msgid "Permission denied." -msgstr "Zugriff verweigert." diff --git a/rest_framework/locale/el/LC_MESSAGES/django.mo b/rest_framework/locale/el/LC_MESSAGES/django.mo index b44b9ea9c3855c92d569c77a7613d233ab511c95..f434e6fc9067325f42f365893ffa52e35dd577bc 100644 GIT binary patch delta 2203 zcmY+_ZA@EL7{KwTv|uS48*eQWbOnYCgi^*jR@kKC7{YKcl?hYKV4G4$fRcezXR5>c z!Gz50?uGd>U*^jPm$}#vVidE*W%0w*%uLPVvSi;d5@Mpu#Kh?Tx%axvlb-uK=d``| zoaa2JeJz^U6~CJ8IxLi4Y9ZC-6p7gQXr0G~h)?#C*e z#7g`e`Kruf6aI!9u{PZ_F3-|vVc;xAFrB+>#Wt+Ncd!93<0j0@5UIyL)c4=OcD#fo zShPqa8=Fxl*o`_qW_t|velr--r7qCW8CEa0R#=DnfL>gSQ`m?X@jlGT6bWD#@{*Uh z=;sVvi5IX4uVV%JmWc3GX~H0OVgO&nRt@!O8td^djy||2iKk9<^+5JQw@n6NjTMWGE5cwW;D@IsW zC!WG${0BE-F{$5*L#QF1##;Od_h1H3(FMGK2k~{=0D}kUpT!B>!s?#FAL2AL2O`8v zZ*T@*#?T!i3H$()Co9BEn9Fi{YfMkas_+vI_md3 z?jkBUjWG<9kpv?yAJJIHzlBZUcnvM$gI}$d^tfQjMsDk3pjgvJbEyJuVN~CIb&&9 zh}~F%kKjH$jh9d-p5O?y;~^w(W7B{iKVkWgAtaw1|2SmBcsy2eu+PCl63bC9`a?jp*|^;^xmIiT%Gq^vv*U z4z$|uWZ3HJbq{shHrr#5q2^65)k)RdAfV>4B?rx)ZjFXU;8ChoSigqcP2E7%{ra!y zUN=)Yf#&}M`FXSGHr!e?;~&0dK*QfgB{j_QAXl>8%oE)rcGUdOXfWeXNB38Yo`goT zWnI$kb|-31knH9uNx=eYJ@tNS7L}h4vuO5fUXaH(%W_&?s-H@l5T56vLnC8*jJ@t` zJ(IaASXINT)?X8@UmY?cKA*q4Z#=qd)Za0> zb2v8kjDOwUVXGAxj1CQtNBfdb`+^SRYW~jT__BZ}`DMvDN6NP|Ml6_P6qYYezFYdF h)9xn=%Rg|WJYSqVSMjsM_^NUw*;JLAW}K{@{}(#=?XLg; delta 2634 zcmZYAeN0tl9Ki7hQBV-ji-Jb#5g`@i%Bvun$cy*_z9eG4^y0m6n};s<8u!v%#4d=4 zG#A-5b2Y7*HM6D3^(B^eH~+}3wR2{hTWQ*Cu33MyX69tNG-&pL}|QIgV>nBg|uU=QZw-d)G&;>cm}8AHJpNfqCD>yr&Jcs z$0TgP0^Er6a5pC6X{=Eyq(;cpbK`++El(|{{0c6`uW=)$Co5HsZMX=JVgug9O3WJ{ z``v?$ln>)9{23jXniA{aOqBaeEJI4U$w-5HunaFDf9ek|($j?0*oae6UQ~(|=*KEN zj@Fdq+M5nh_i{BI_6j~g=5HFSR} zHrckLjC8;*zlOz>PvGG=rEcH?%3T}*S*l@NhreP8R?}EEcA{+7Lnupo8)Ye+A%-Cp zS7Q)+QAT#3%j-Ci;fcp^2+NtCKD>-=*f34bkW%MRo?k$#d+;Od$Kn}EJ&&KFjyalA zNAOi#hM@wsr1UU=vL*+y1#hAkYxuDfPhkh3K_mD-NO=)kPUta>5kBqKc;vb~CHqyjsWSyXMf8T(KgxPd)b&s+U?8D+-G=PI=r z*CX3N4PiAN!&3Yasj9L#dots7I9}#%CmET6eq4mdu?lZv6XvjEcvkI1Ii5#xIbK28 zGgIfqUN{}=DVNygmr#!B5Hcz12r@Qx31tR;!ZO~k9*~iW#RahuR^ywLPhbt!@K!lC z{U{wghw>%7i#srnE$_y+u?7D|*^F);T8QtWl&_&|)+ucLeq4*8F=VciNx< z3vF4cjH@h*Bx!sVA?qheI=7OL&d(rNDQgcb#dU-v4uq<+a}q^Ag(0ZB}vwS1D-2v$XHAx06B zvWR9GzkCQJ#S=1}O9)v8NfQX!&D#iB+eAVVr&CR|at|-E8J{7>6S6!U6t$YDBwU1? zf(?Wu*@#aNAvrRVWP`{t`){hEzz5FSy>kC)LiS0H6iC$s-ytiN+p;IE?}HR8i1kE+ z6i6$GG%FYTijtkhB6sP&9i74MY)ubnK7UYiX)n6GdaLI4`@J5Q&l$dxxMEs%7dJaS z|F6~Dys|Fh92-9-+2d{1p3}WSPe&l)O!;oKW1Xj?T@M6wzfWuR_;gRJGd#{wp3v0k zZr6j6D#x7oNv@U_f2S`fuWr=?F1J@Ixh8+Htf;)8u%e)_L@O$-EUU;bBqcSv0>Od} z9WGzM>k9fiDzy#U{q3%RR=d4J4+M3Owm9f-UrMytHzMAQ!nma9!;$zi_a`4SqMALL z@XMK6LR7Pc@K0~(&lomNb3GqU%MYf!Pis-`M2*jklab#1jD+>%qDF)| zJZROtz*D<4bATHs%|3HCZP9~M#zo__F``jLI&{%E%kL2*YBe}woHs5QA9FWq?sA61 HMR)%JHvf<} diff --git a/rest_framework/locale/el/LC_MESSAGES/django.po b/rest_framework/locale/el/LC_MESSAGES/django.po index 18eb371c9f..65459fc7b2 100644 --- a/rest_framework/locale/el/LC_MESSAGES/django.po +++ b/rest_framework/locale/el/LC_MESSAGES/django.po @@ -3,14 +3,16 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Christos Barkonikos , 2020 +# Panagiotis Pavlidis , 2019 # Serafeim Papastefanos , 2016 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Greek (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/el/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -18,40 +20,40 @@ msgstr "" "Language: el\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Λανθασμένη επικεφαλίδα basic. Δεν υπάρχουν διαπιστευτήρια." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." -msgstr "Λανθασμένη επικεφαλίδα basic. Τα διαπιστευτήρια δε μπορεί να περιέχουν κενά." +msgstr "Λανθασμένη επικεφαλίδα basic. Τα διαπιστευτήρια δεν πρέπει να περιέχουν κενά." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Λανθασμένη επικεφαλίδα basic. Τα διαπιστευτήρια δεν είναι κωδικοποιημένα κατά base64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Λανθασμένο όνομα χρήστη/κωδικός." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Ο χρήστης είναι ανενεργός ή διεγραμμένος." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Λανθασμένη επικεφαλίδα token. Δεν υπάρχουν διαπιστευτήρια." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." -msgstr "Λανθασμένη επικεφαλίδα token. Το token δε πρέπει να περιέχει κενά." +msgstr "Λανθασμένη επικεφαλίδα token. Το token δεν πρέπει να περιέχει κενά." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Λανθασμένη επικεφαλίδα token. Το token περιέχει μη επιτρεπτούς χαρακτήρες." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Λανθασμένο token" @@ -59,382 +61,515 @@ msgstr "Λανθασμένο token" msgid "Auth Token" msgstr "Token πιστοποίησης" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Κλειδί" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Χρήστης" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Δημιουργήθηκε" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokens" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Όνομα χρήστη" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Κωδικός" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Ο λογαριασμός χρήστη είναι απενεργοποιημένος." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Δεν είναι δυνατή η σύνδεση με τα διαπιστευτήρια." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Πρέπει να περιέχει \"όνομα χρήστη\" και \"κωδικό\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Σφάλμα διακομιστή." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Λανθασμένο αίτημα." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Λάθος διαπιστευτήρια." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Δεν δόθηκαν διαπιστευτήρια." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Δεν έχετε δικαίωματα για αυτή την ενέργεια." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Δε βρέθηκε." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." -msgstr "Η μέθοδος \"{method\"} δεν επιτρέπεται." +msgstr "Η μέθοδος \"{method}\" δεν επιτρέπεται." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Δεν ήταν δυνατή η ικανοποίηση της επικεφαλίδας Accept της αίτησης." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Δεν υποστηρίζεται το media type \"{media_type}\" της αίτησης." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Το αίτημα έγινε throttle." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Το πεδίο είναι απαραίτητο." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Το πεδίο δε μπορεί να είναι null." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "Το \"{input}\" δεν είναι έγκυρο boolean." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Το πεδίο δε μπορεί να είναι κενό." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Επιβεβαιώσατε ότι το πεδίο δεν έχει περισσότερους από {max_length} χαρακτήρες." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Επιβεβαιώσατε ότι το πεδίο έχει τουλάχιστον {min_length} χαρακτήρες." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Συμπληρώσατε μια έγκυρη διεύθυνση e-mail." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Η τιμή δε ταιριάζει με το pattern." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Εισάγετε ένα έγκυρο \"slug\" που αποτελείται από γράμματα, αριθμούς παύλες και κάτω παύλες." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Εισάγετε έγκυρο URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "Το \"{value}\" δεν είναι έγκυρο UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Εισάγετε μια έγκυρη διεύθυνση IPv4 ή IPv6." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Ένας έγκυρος ακέραιος είναι απαραίτητος." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Επιβεβαιώσατε ότι η τιμή είναι μικρότερη ή ίση του {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Επιβεβαιώσατε ότι η τιμή είναι μεγαλύτερη ή ίση του {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Το κείμενο είναι πολύ μεγάλο." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Ένας έγκυρος αριθμός είναι απαραίτητος." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Επιβεβαιώσατε ότι δεν υπάρχουν παραπάνω από {max_digits} ψηφία." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Επιβεβαιώσατε ότι δεν υπάρχουν παραπάνω από {max_decimal_places} δεκαδικά ψηφία." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Επιβεβαιώσατε ότι δεν υπάρχουν παραπάνω από {max_whole_digits} ακέραια ψηφία." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Η ημερομηνία έχεi λάθος μορφή. Χρησιμοποιήστε μια από τις ακόλουθες μορφές: {format}" -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Αναμένεται ημερομηνία και ώρα αλλά δόθηκε μόνο ημερομηνία." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Η ημερομηνία έχεi λάθος μορφή. Χρησιμοποιήστε μια από τις ακόλουθες μορφές: {format}" -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Αναμένεται ημερομηνία αλλά δόθηκε ημερομηνία και ώρα." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Η ώρα έχει λάθος μορφή. Χρησιμοποιήστε μια από τις ακόλουθες μορφές: {format}" -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Η διάρκεια έχει λάθος μορφή. Χρησιμοποιήστε μια από τις ακόλουθες μορφές: {format}" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "Το \"{input}\" δεν είναι έγκυρη επιλογή." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Περισσότερα από {count} αντικείμενα..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Αναμένεται μια λίστα αντικειμένον αλλά δόθηκε ο τύπος \"{input_type}\"" -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Η επιλογή δε μπορεί να είναι κενή." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "Το \"{input}\" δεν είναι έγκυρη επιλογή διαδρομής." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Δεν υποβλήθηκε αρχείο." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Τα δεδομένα που υποβλήθηκαν δεν ήταν αρχείο. Ελέγξατε την κωδικοποίηση της φόρμας." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Δε βρέθηκε όνομα αρχείου." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Το αρχείο που υποβλήθηκε είναι κενό." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Επιβεβαιώσατε ότι το όνομα αρχείου έχει ως {max_length} χαρακτήρες (έχει {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Ανεβάστε μια έγκυρη εικόνα. Το αρχείο που ανεβάσατε είτε δεν είναι εικόνα είτε έχει καταστραφεί." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Η λίστα δε μπορεί να είναι κενή." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Αναμένεται ένα λεξικό αντικείμενων αλλά δόθηκε ο τύπος \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Η τιμή πρέπει να είναι μορφής JSON." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Υποβολή" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Αναζήτηση" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Ταξινόμηση" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Λάθος σελίδα." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Λάθος cursor." -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Λάθος κλειδί \"{pk_value}\" - το αντικείμενο δεν υπάρχει." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Λάθος τύπος. Αναμένεται τιμή κλειδιού, δόθηκε {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Λάθος σύνδεση - δε ταιριάζει κάποιο URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Λάθος σύνδεση - δε ταιριάζει κάποιο URL." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Λάθος σύνδεση - το αντικείμενο δεν υπάρχει." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Λάθος τύπος. Αναμένεται URL, δόθηκε {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Το αντικείμενο {slug_name}={value} δεν υπάρχει." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Λάθος τιμή." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Λάθος δεδομένα. Αναμένεται λεξικό αλλά δόθηκε {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Φίλτρα" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Φίλτρα πεδίων" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Ταξινόμηση" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Αναζήτηση" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "None" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Δεν υπάρχουν αντικείμενα προς επιλογή." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Το πεδίο πρέπει να είναι μοναδικό" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Τα πεδία {field_names} πρέπει να αποτελούν ένα μοναδικό σύνολο." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Το πεδίο πρέπει να είναι μοναδικό για την ημερομηνία \"{date_field}\"." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Το πεδίο πρέπει να είναι μοναδικό για το μήνα \"{date_field}\"." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Το πεδίο πρέπει να είναι μοναδικό για το έτος \"{date_field}\"." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Λάθος έκδοση στην επικεφαλίδα \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Λάθος έκδοση στη διαδρομή URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Λάθος έκδοση στο hostname." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Λάθος έκδοση στην παράμετρο" - -#: views.py:88 -msgid "Permission denied." -msgstr "Απόρριψη πρόσβασης" diff --git a/rest_framework/locale/en/LC_MESSAGES/django.mo b/rest_framework/locale/en/LC_MESSAGES/django.mo index 68e5600ae29759ff2dd8be760cfe9bbfbc6ffa2b..0770a9d5e7d6269e71717c100aa98d02275cfa7e 100644 GIT binary patch literal 12285 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 YP5-Y$2gv?EmaLz;`G42^|9|TCe^7Qb5&!@I delta 2718 zcmdVa>2Fk37{~E5T`07rKtU)iQ!Z=S3JgL^OE=oGN!i;Xh^5ocOu5?5n9e{*SSFZA zAgEDqcp*^{R7gyWN)bU5B9a&lkSJ&{(clu_U{nk*j1nY%fA?O(-{47~`#I;%-Of4B z(|@;qu_k?^Fzc|P#fWi4OQtagarYn&wATk4GXamGg%@!O{(_~LJH(hGoQ}G`5le6@ z=Hf0a$Ne}9Ph$@LgpJ0e%}qK>IWdc?WfGU+N7#TFdB(J36IS98T!dF~B^KlxQ;nNZ zzaPdHyn>Ukc&IUjScm$+b*S@k*R(M&(a{6X-~zmj{F!M5-j`OOW?YAQQ3My_QLM*r zu?dTY88aKVAg9bB)OAO28eT)K8&x%*BnU2OPi^_#xI}V1)O;dMswV z+8^7fne0T(>=k55%{hO3*Z+Mi)6e95EovnebI{hcVp)!II3!%OViOz zPoQRc5%r)SupEEISy;q0RW?gdGg^hJmKS`lqgJw%hcsXe+wmk;;yql16}(JU(~s4d zK2Jv%{DUf+P|!=Ny{NrBj{Iak@XufM{R{QLVy3eI+fh~XEb2p#pk{m=^`g&F)sQ*f ztC||5YSLym9cBF|sIs|*y6_&VY$`2pDLYYR^EB#3@1Tp{k~Yb+5%J>UnP< zi(^jt<147D`5wpdeq$zjOBzJYv=eo~9?ZmJsFipNnS?oudf;^|!`nz&m|||SFofi@ zS&z!47-~zOL?z`8R4$#6djGq0^n$af2VFvC>CZ?WnVbGNFxi{&VANh$p+0yY>blqb z@fl>h%{8pRTc|Cb%6@3vfttW!OzXn;=va6e^@YDWz&iJpctxb`cPH#GH$|4 zn1xeV4)WE^@NGuTw8J0wpsHpcs%lP`vHn_`^PC9aMxvU~^5qftn@UADs1nq4&;9nW z-%%2>6K<>Fs4Q3hs69$NLSz%9m77OefyLVYb#&B7XtTuad8N07ekZYv(5#hgYDy~p zkGM&=6j>~{HFInwx(RLPazbs5hr9Gz4YlbW=5g)+T7RG=ZSwmTYAYTiGKd(Vt!gKf zgld_D_Pw6aHmD6J_@`*XM1;s8)Fu!k+@5!cg0?KZl|w$UieMMaYGM)5Ma(DK2se3f zJ)zQ4t0OkMy$6Q|x=P?l{;3WeL`?R_$|)tRno67&a%)#fqgF+1AoPz&P4zs??Rkl# zuJcuHaZXnv(HjlNoiB4soQBfb!HK(egnPF~_e`|xq!mx3tgyuiJ7Tr9t!Q@ME^Tw( z&-;6z#!3a8rwZmfHwwNU*gI)qAb+VH?TuJ5yEhf>9~e62WWYH$GT-@g>JtNYS!0G1 z8ND+*me?MTI4{mYO!mYI7seWOp\n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: English (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/en/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -17,40 +17,40 @@ msgstr "" "Language: en\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Invalid basic header. No credentials provided." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Invalid basic header. Credentials string should not contain spaces." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Invalid basic header. Credentials not correctly base64 encoded." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Invalid username/password." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "User inactive or deleted." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Invalid token header. No credentials provided." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Invalid token header. Token string should not contain spaces." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Invalid token header. Token string should not contain invalid characters." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Invalid token." @@ -58,382 +58,515 @@ msgstr "Invalid token." msgid "Auth Token" msgstr "Auth Token" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Key" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "User" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Created" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokens" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Username" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Password" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "User account is disabled." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Unable to log in with provided credentials." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Must include \"username\" and \"password\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "A server error occurred." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "Invalid input." + +#: exceptions.py:161 msgid "Malformed request." msgstr "Malformed request." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Incorrect authentication credentials." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Authentication credentials were not provided." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "You do not have permission to perform this action." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Not found." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Method \"{method}\" not allowed." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Could not satisfy the request Accept header." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Unsupported media type \"{media_type}\" in request." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Request was throttled." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "Expected available in {wait} second." + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "Expected available in {wait} seconds." + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "This field is required." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "This field may not be null." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" is not a valid boolean." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "Must be a valid boolean." + +#: fields.py:766 +msgid "Not a valid string." +msgstr "Not a valid string." -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." msgstr "This field may not be blank." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Ensure this field has no more than {max_length} characters." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Ensure this field has at least {min_length} characters." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Enter a valid email address." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "This value does not match the required pattern." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Enter a valid \"slug\" consisting of letters, numbers, underscores or hyphens." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, or hyphens." + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Enter a valid URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" is not a valid UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "Must be a valid UUID." -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Enter a valid IPv4 or IPv6 address." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "A valid integer is required." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Ensure this value is less than or equal to {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Ensure this value is greater than or equal to {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "String value too large." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "A valid number is required." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Ensure that there are no more than {max_digits} digits in total." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Ensure that there are no more than {max_decimal_places} decimal places." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Ensure that there are no more than {max_whole_digits} digits before the decimal point." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datetime has wrong format. Use one of these formats instead: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Expected a datetime but got a date." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "Invalid datetime for the timezone \"{timezone}\"." + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "Datetime value out of range." + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Date has wrong format. Use one of these formats instead: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Expected a date but got a datetime." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Time has wrong format. Use one of these formats instead: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Duration has wrong format. Use one of these formats instead: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" is not a valid choice." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "More than {count} items..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Expected a list of items but got type \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "This selection may not be empty." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" is not a valid path choice." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "No file was submitted." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "The submitted data was not a file. Check the encoding type on the form." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "No filename could be determined." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "The submitted file is empty." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Ensure this filename has at most {max_length} characters (it has {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Upload a valid image. The file you uploaded was either not an image or a corrupted image." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "This list may not be empty." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "Ensure this field has at least {min_length} elements." + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "Ensure this field has no more than {max_length} elements." + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Expected a dictionary of items but got type \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "This dictionary may not be empty." + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Value must be valid JSON." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Submit" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Search" -#: filters.py:336 +#: filters.py:50 +msgid "A search term." +msgstr "A search term." + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Ordering" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "Which field to use when ordering the results." + +#: filters.py:287 msgid "ascending" msgstr "ascending" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "descending" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "A page number within the paginated result set." + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "Number of results to return per page." + +#: pagination.py:189 msgid "Invalid page." msgstr "Invalid page." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "The initial index from which to return the results." + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "The pagination cursor value." + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Invalid cursor" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Invalid pk \"{pk_value}\" - object does not exist." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Incorrect type. Expected pk value, received {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Invalid hyperlink - No URL match." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Invalid hyperlink - Incorrect URL match." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Invalid hyperlink - Object does not exist." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Incorrect type. Expected URL string, received {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Object with {slug_name}={value} does not exist." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Invalid value." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "unique integer value" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "UUID string" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "unique value" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "A {value_type} identifying this {name}." + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Invalid data. Expected a dictionary, but got {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "Extra Actions" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filters" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Field filters" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "navbar" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Ordering" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "content" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Search" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "request form" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "main content" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "request info" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "response info" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "None" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "No items to select." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "This field must be unique." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "The fields {field_names} must make a unique set." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "Surrogate characters are not allowed: U+{code_point:X}." + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "This field must be unique for the \"{date_field}\" date." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "This field must be unique for the \"{date_field}\" month." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "This field must be unique for the \"{date_field}\" year." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Invalid version in \"Accept\" header." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Invalid version in URL path." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Invalid version in URL path. Does not match any version namespace." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Invalid version in hostname." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Invalid version in query parameter." - -#: views.py:88 -msgid "Permission denied." -msgstr "Permission denied." diff --git a/rest_framework/locale/en_US/LC_MESSAGES/django.po b/rest_framework/locale/en_US/LC_MESSAGES/django.po index 3733a1e33d..c9dd2d633d 100644 --- a/rest_framework/locale/en_US/LC_MESSAGES/django.po +++ b/rest_framework/locale/en_US/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,40 +17,40 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "" -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "" -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "" -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "" -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "" -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "" -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "" -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "" -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "" @@ -58,380 +58,513 @@ msgstr "" msgid "Auth Token" msgstr "" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "" - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "" -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "" -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "" -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "" -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "" -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "" -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "" -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "" -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "" -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "" -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "" -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "" -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "" -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." msgstr "" -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "" -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "" -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "" -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "" -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "" -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "" -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." +#: fields.py:867 +msgid "Must be a valid UUID." msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "" -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "" -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "" -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "" -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "" -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "" -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "" -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "" -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "" -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "" -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "" -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "" -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "" -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "" -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "" -#: fields.py:1359 +#: fields.py:1515 msgid "The submitted data was not a file. Check the encoding type on the form." msgstr "" -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "" -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "" -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "" -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "" -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "" -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:336 +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "" -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "" -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "" -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "" -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "" -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "" -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "" -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "" -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "" #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "" -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "" -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "" -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "" -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "" -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "" -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "" -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "" -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "" - -#: views.py:88 -msgid "Permission denied." -msgstr "" diff --git a/rest_framework/locale/es/LC_MESSAGES/django.mo b/rest_framework/locale/es/LC_MESSAGES/django.mo index 6efb9bdd15c6d6714283b652ecec46e2b8d56669..16df627fcc901ed5fb7307de1b195c5672dad071 100644 GIT binary patch delta 2273 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, 2015 +# Ernesto Rico Schmidt , 2015 # José Padilla , 2015 +# Leo Prada , 2019 # Miguel Gonzalez , 2015 # Miguel Gonzalez , 2016 # Miguel Gonzalez , 2015-2016 @@ -13,9 +14,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Miguel Gonzalez \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Spanish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/es/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -23,40 +24,40 @@ msgstr "" "Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Cabecera básica inválida. Las credenciales no fueron suministradas." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Cabecera básica inválida. La cadena con las credenciales no debe contener espacios." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." -msgstr "Cabecera básica inválida. Las credenciales incorrectamente codificadas en base64." +msgstr "Cabecera básica inválida. Las credenciales no fueron codificadas correctamente en base64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Nombre de usuario/contraseña inválidos." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Usuario inactivo o borrado." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Cabecera token inválida. Las credenciales no fueron suministradas." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Cabecera token inválida. La cadena token no debe contener espacios." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Cabecera token inválida. La cadena token no debe contener caracteres inválidos." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Token inválido." @@ -64,382 +65,515 @@ msgstr "Token inválido." msgid "Auth Token" msgstr "Token de autenticación" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Clave" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Usuario" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Fecha de creación" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokens" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Nombre de usuario" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Contraseña" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Cuenta de usuario está deshabilitada." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "No puede iniciar sesión con las credenciales proporcionadas." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Debe incluir \"username\" y \"password\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Se ha producido un error en el servidor." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Solicitud con formato incorrecto." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Credenciales de autenticación incorrectas." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Las credenciales de autenticación no se proveyeron." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Usted no tiene permiso para realizar esta acción." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "No encontrado." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Método \"{method}\" no permitido." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "No se ha podido satisfacer la solicitud de cabecera de Accept." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Tipo de medio \"{media_type}\" incompatible en la solicitud." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Solicitud fue regulada (throttled)." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Este campo es requerido." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Este campo no puede ser nulo." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" no es un booleano válido." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Este campo no puede estar en blanco." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Asegúrese de que este campo no tenga más de {max_length} caracteres." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Asegúrese de que este campo tenga al menos {min_length} caracteres." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Introduzca una dirección de correo electrónico válida." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Este valor no coincide con el patrón requerido." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Introduzca un \"slug\" válido consistente en letras, números, guiones o guiones bajos." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Introduzca una URL válida." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" no es un UUID válido." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Introduzca una dirección IPv4 o IPv6 válida." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Introduzca un número entero válido." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Asegúrese de que este valor es menor o igual a {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Asegúrese de que este valor es mayor o igual a {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Cadena demasiado larga." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Se requiere un número válido." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Asegúrese de que no haya más de {max_digits} dígitos en total." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Asegúrese de que no haya más de {max_decimal_places} decimales." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Asegúrese de que no haya más de {max_whole_digits} dígitos en la parte entera." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Fecha/hora con formato erróneo. Use uno de los siguientes formatos en su lugar: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Se esperaba un fecha/hora en vez de una fecha." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Fecha con formato erróneo. Use uno de los siguientes formatos en su lugar: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Se esperaba una fecha en vez de una fecha/hora." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Hora con formato erróneo. Use uno de los siguientes formatos en su lugar: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Duración con formato erróneo. Use uno de los siguientes formatos en su lugar: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" no es una elección válida." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Más de {count} elementos..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Se esperaba una lista de elementos en vez del tipo \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Esta selección no puede estar vacía." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" no es una elección de ruta válida." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "No se envió ningún archivo." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "La información enviada no era un archivo. Compruebe el tipo de codificación del formulario." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "No se pudo determinar un nombre de archivo." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "El archivo enviado está vació." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Asegúrese de que el nombre de archivo no tenga más de {max_length} caracteres (tiene {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Adjunte una imagen válida. El archivo adjunto o bien no es una imagen o bien está dañado." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Esta lista no puede estar vacía." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Se esperaba un diccionario de elementos en vez del tipo \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "El valor debe ser JSON válido." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Enviar" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Buscar" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Ordenamiento" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "ascendiente" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "descendiente" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Página inválida." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Cursor inválido" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Clave primaria \"{pk_value}\" inválida - objeto no existe." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Tipo incorrecto. Se esperaba valor de clave primaria y se recibió {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Hiperenlace inválido - No hay URL coincidentes." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Hiperenlace inválido - Coincidencia incorrecta de la URL." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Hiperenlace inválido - Objeto no existe." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Tipo incorrecto. Se esperaba una URL y se recibió {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objeto con {slug_name}={value} no existe." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Valor inválido." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Datos inválidos. Se esperaba un diccionario pero es un {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtros" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Filtros de campo" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Ordenamiento" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Buscar" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Ninguno" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "No hay elementos para seleccionar." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Este campo debe ser único." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Los campos {field_names} deben formar un conjunto único." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Este campo debe ser único para el día \"{date_field}\"." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Este campo debe ser único para el mes \"{date_field}\"." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Este campo debe ser único para el año \"{date_field}\"." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Versión inválida en la cabecera \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Versión inválida en la ruta de la URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "La versión especificada en la ruta de la URL no es válida. No coincide con ninguna del espacio de nombres de versiones." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Versión inválida en el nombre de host." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Versión inválida en el parámetro de consulta." - -#: views.py:88 -msgid "Permission denied." -msgstr "Permiso denegado." diff --git a/rest_framework/locale/et/LC_MESSAGES/django.mo b/rest_framework/locale/et/LC_MESSAGES/django.mo index 8deba1eb0a8adddd04f1465445c7d5d3083accf8..e14ea9e270d7b06ac9ad497bf06c55378c98c170 100644 GIT binary patch delta 3094 zcmZ|QZEPGz8Nl(eo!mKbY{zd&9LLQ#q^;e?m!y6naqQY|(geT6cHk z6+&W^x!=t0dUocSXEyI0y)(A-yV?y`6yEbbJHCPY@ShmP)=f%1ga`0A`URKNk7=CXLJ5C8fI~QgU%(II+js;UE0h|+ zY3#voquhTJN3nj3QXfErwfI?-30^H;Un%-)l=1$BU1~|G!JE)Q?Aclv z@Cn>W|5CC49h8;5gtDSvp@j0U#eP>!;r=Ns<9ZGy0#D*yco}89AK<7(UE&UtHgnJoW?w>*FpGV1= zFV_+OZW=FeA-~rs{l@yjj3-bYOrpk1C=>cVN(64**Or!xChBcHH{L1v-n(zQu8P?uORisR+PPe7$wA0DC79ZuBgk! z>)*mQ`mdry>JRuJ-bRT~`x1ZEOCv;S zd6d0gL-JSs1AB4PZl&(QjgSU(Q z8kQla;BI72Y6$yquGoJbW#y|VIq^p%J=9;2cc}=2$$0nhBRMpLJLUW@(vSzfit^%@ zQTA#L`L?KEpq%61qU1z`SxAo5;C5_6dA*vR`; z!(KiTxF09*5}v|aD51NbuM&$;r%@*QBua>{;e&VsWdd?hvO0=#3bvx0f?k}&O!59} zD3SU(F7Xvq@6li~`Kz*rd$}7ygHcRi36@busT}L~%L!VtDk<`nlgLa_C3j>JQe?kL zk9<@f$Fo#PKFU)5xJavu$_Gh(lsZFQFT6XyicXa9YpRq-sSi^hp>i1VpKghwoC+zj zby8&I5)~=OsB&zj$U^tY{{MFwq;sZtp#up_{#Z$vWpAZO*0)kWM%_T|p~|;|PlP&2 zl_DoXws4v%k$sFRN#016@*(N~b&I@T_Er*13R|8(&(fAHSTBcYNY);wN)AYod`T4v@P+gEwD*3INh z%8m0nlQpw)-)1Uz*p>&sS}; z^Hs635oghUt*Uc}mvwZIFd2Pu)(bPiC$t+lX+IXTYgO@Wg^mt9?K>%FHi)S)FXPy@ z>fZh1*|?K+GjnRf`2Gbi8&{{C^P%Gh`hxKpG3y0E%8AF+q+_zPiRFvcH%q!xF89-B zF+a(3j&{-u!D38JC0xJw2AS*sK45!l!Yzfc=6`+r!hxyGIKrhTJ!rs{zM?FaeSp${JJ>Flxx>$dCDCg(a? z&FZ}K;iB$+f|W^YAmwJfoNJa7bu}gST783kroPF(UO%~U^*Xb+TN{QWqn;NJ0LMPn?5y45ZehOVRdM|`B29&Q<-PIp|2)Q%J{1*8KXvBch2YqlbR3p z0uQhOOgpB=1E1z{-(9;)2Sz<7a8jYnIPem#_EIS$3*&LaGIn8v2efy|RR8Qr% zKo#Jm-OxX=ms{GZMvNZ@X5M7nw5w+9;O_cLnR|W<^2zaXOMi9J_&JkPNy8a%a_T>U C&JaZa delta 2055 zcmZY9TWl0n7{Kwf%cU1->6Tu$QtW9Hvag8R17m)_qpp9=D(ZmNH1fvEXG$9)Q-*%dKImx%bIWs#m z=X~dz^V{w_{mCElSI#QhAW=$$-AdVbaV0;rJ*$-}$45|O4DZ9!=*1Mueb;b3{)F|o zDnqHw7{ES!2Djr|_z>R2UZs+1OQuqfaUzDTxPV*Hou$-H?8Xi}fpUEgcj2FykAvAu z19%cE=^t@?1LZlFuo?ftDr~xE>3IjRjrXfr4tluo74~E98l`-=7jyAAw&4tJ!g-X8 z+{QXA$Wh9VJt+50Vi&%Na{u>uFaGTGGdL*&Ey7aXuR7!ahn)*?l$pJTlIr)d1{bgh zZ==SnwMsE5Rfi4OjB?$^VmyX2u+u0hpF??1PTtb>I!wxmJ`QBLe z8I*gLUet-QreVAvKg34-4j;!HMqh%Xj>mBe{j+$^MW#{Hj~0-B8Nf`z(f}?XlT-63 zGyewVLBBhFPoYw^^!?b42T*2q1|{V`q71x@G)R9KWl3jImiSwI3QI`OPJFJI{O{)A zGAH)qI!aoqI?Ru(@k=NVIPZ81Tj)11DtX``l!3m3QfzZ519OossrFu!|GkWUyn-ZC z{ehjBl_c-g9BjvWoWRX^7TfVE%8cBk=>hD(b$ARbaK`DUP>SjbZopfPS#+d!YLFzV zhp`NU*nr6wIbbmAW9Pym@*${SQL6hck{ngdH01%^CVV0mKb=)5P1anRERCDs zOH#drvs5&3dQ0fTX)8z6TQTUb0W2} z*yAdiWNv!g9^=7!+#0hZL2GK-XR^K8yFAl>{v|!le(#WZ*V}DAuPQVvN@A&0$*b;E zxHRBOo!?-&Tt1UoHg9f}oimI2J@aXKi@8%CFas6iX0f8RcvmPiYUxQUd^n*ezj%8p t6d6r~g1%IHWtrQ&UUkUiRe$Cgk4EBA9gXN%f_Wx_KC@6=Yc|$&{SE3a8fO3i diff --git a/rest_framework/locale/et/LC_MESSAGES/django.po b/rest_framework/locale/et/LC_MESSAGES/django.po index cc2c2e3f05..d9c4b184f1 100644 --- a/rest_framework/locale/et/LC_MESSAGES/django.po +++ b/rest_framework/locale/et/LC_MESSAGES/django.po @@ -3,14 +3,15 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: -# Tõnis Kärdi , 2015 +# Erlend Eelmets , 2020 +# Tõnis Kärdi , 2015,2019 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Estonian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/et/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -18,423 +19,556 @@ msgstr "" "Language: et\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Sobimatu lihtpäis. Kasutajatunnus on esitamata." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Sobimatu lihtpäis. Kasutajatunnus ei tohi sisaldada tühikuid." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Sobimatu lihtpäis. Kasutajatunnus pole korrektselt base64-kodeeritud." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Sobimatu kasutajatunnus/salasõna." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Kasutaja on inaktiivne või kustutatud." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Sobimatu lubakaardi päis. Kasutajatunnus on esitamata." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Sobimatu lubakaardi päis. Loa sõne ei tohi sisaldada tühikuid." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." -msgstr "" +msgstr "Sobimatu lubakaardi päis. Loa sõne ei tohi sisaldada sobimatuid märke." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Sobimatu lubakaart." #: authtoken/apps.py:7 msgid "Auth Token" -msgstr "" +msgstr "Autentimistähis" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" -msgstr "" +msgstr "Võti" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "Kasutaja" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" -msgstr "" +msgstr "Loodud" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" -msgstr "" +msgstr "Tähis" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" -msgstr "" +msgstr "Tähised" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "Kasutajanimi" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" +msgstr "Salasõna" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Kasutajakonto on suletud." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Sisselogimine antud tunnusega ebaõnnestus." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Peab sisaldama \"kasutajatunnust\" ja \"slasõna\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Viga serveril." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Väändunud päring." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Ebakorrektne autentimistunnus." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Autentimistunnus on esitamata." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Teil puuduvad piisavad õigused selle tegevuse teostamiseks." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Ei leidnud." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Meetod \"{method}\" pole lubatud." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Päringu Accept-päist ei suutnud täita." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Meedia tüüpi {media_type} päringus ei toetata." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Liiga palju päringuid." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Väli on kohustuslik." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Väli ei tohi olla tühi." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" pole kehtiv kahendarv." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." msgstr "See väli ei tohi olla tühi." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Veendu, et see väli poleks pikem kui {max_length} tähemärki." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Veendu, et see väli oleks vähemalt {min_length} tähemärki pikk." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Sisestage kehtiv e-posti aadress." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Väärtus ei ühti etteantud mustriga." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Sisestage kehtiv \"slug\", mis koosneks tähtedest, numbritest, ala- või sidekriipsudest." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Sisestage korrektne URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" pole kehtiv UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "" +msgstr "Sisesta valiidne IPv4 või IPv6 aadress" -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Sisendiks peab olema täisarv." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Veenduge, et väärtus on väiksem kui või võrdne väärtusega {max_value}. " -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Veenduge, et väärtus on suurem kui või võrdne väärtusega {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Sõne on liiga pikk." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Sisendiks peab olema arv." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Veenduge, et kokku pole rohkem kui {max_digits} numbit." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Veenduge, et komakohti pole rohkem kui {max_decimal_places}. " -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Veenduge, et täiskohti poleks rohkem kui {max_whole_digits}." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Valesti formaaditud kuupäev-kellaaeg. Kasutage mõnda neist: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Ootasin kuupäev-kellaaeg andmetüüpi, kuid sain hoopis kuupäeva." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Valesti formaaditud kuupäev. Kasutage mõnda neist: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Ootasin kuupäeva andmetüüpi, kuid sain hoopis kuupäev-kellaaja." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Valesti formaaditud kellaaeg. Kasutage mõnda neist: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "Valesti formaaditud kestvus. Kasutage mõnda neist: {format}" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" on sobimatu valik." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." -msgstr "" +msgstr "Kirjeid on rohkem kui {count} ... " -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Ootasin kirjete järjendit, kuid sain \"{input_type}\" - tüübi." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." -msgstr "" +msgstr "Valik ei tohi olla määramata." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." -msgstr "" +msgstr "\"{input}\" on sobimatu valik." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Ühtegi faili ei esitatud." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Esitatud andmetes ei olnud faili. Kontrollige vormi kodeeringut." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Ei suutnud tuvastada failinime." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Esitatud fail oli tühi." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Veenduge, et failinimi oleks maksimaalselt {max_length} tähemärki pikk (praegu on {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Laadige üles kehtiv pildifail. Üles laetud fail ei olnud pilt või oli see katki." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." +msgstr "Loetelu ei tohi olla määramata." + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." msgstr "" -#: fields.py:1502 +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Ootasin kirjete sõnastikku, kuid sain \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." +msgstr "Väärtus peab olema valiidne JSON." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Otsing" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Järjestus" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" -msgstr "" +msgstr "kasvav" -#: filters.py:337 +#: filters.py:288 msgid "descending" +msgstr "kahanev" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." msgstr "" -#: pagination.py:193 +#: pagination.py:189 msgid "Invalid page." +msgstr "Sobimatu lehekülg." + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." msgstr "" -#: pagination.py:427 +#: pagination.py:583 msgid "Invalid cursor" -msgstr "Sobimatu kursor." +msgstr "Sobimatu kursor" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Sobimatu primaarvõti \"{pk_value}\" - objekti pole olemas." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Sobimatu andmetüüp. Ootasin primaarvõtit, sain {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Sobimatu hüperlink - ei leidnud URLi vastet." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Sobimatu hüperlink - vale URLi vaste." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Sobimatu hüperlink - objekti ei eksisteeri." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Sobimatu andmetüüp. Ootasin URLi sõne, sain {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objekti {slug_name}={value} ei eksisteeri." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Sobimatu väärtus." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Sobimatud andmed. Ootasin sõnastikku, kuid sain {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" +msgstr "Filtrid" + +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:78 +msgid "request form" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:157 +msgid "main content" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 -msgid "None" +#: templates/rest_framework/base.html:173 +msgid "request info" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 -msgid "No items to select." +#: templates/rest_framework/base.html:177 +msgid "response info" msgstr "" -#: validators.py:43 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "Puudub" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "Pole midagi valida." + +#: validators.py:39 msgid "This field must be unique." msgstr "Selle välja väärtus peab olema unikaalne." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Veerud {field_names} peavad moodustama unikaalse hulga." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Selle välja väärtus peab olema unikaalne veerus \"{date_field}\" märgitud kuupäeval." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Selle välja väärtus peab olema unikaalneveerus \"{date_field}\" märgitud kuul." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Selle välja väärtus peab olema unikaalneveerus \"{date_field}\" märgitud aastal." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Sobimatu versioon \"Accept\" päises." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Sobimatu versioon URLi rajas." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "Sobimatu versioon URLis - see ei vasta ühelegi teadaolevale." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Sobimatu versioon hostinimes." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Sobimatu versioon päringu parameetris." - -#: views.py:88 -msgid "Permission denied." -msgstr "" diff --git a/rest_framework/locale/fa/LC_MESSAGES/django.mo b/rest_framework/locale/fa/LC_MESSAGES/django.mo index 0e73156d43628c002f3232811c478971ac5af52e..099318e69a178c32d30b3ddcefbec1a8579d8d55 100644 GIT binary patch literal 11976 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 delta 136 zcmX>Rdx_cNo)F7a1|VPrVi_P-0b*t#)&XJ=umIv2KuJp=4N?OGlQYG__zcZ-4a{{7 zjT8(`tqe^jpA{2V2+7FLO)OS$&M3+(F3C()uqnyUbpYaIsK8_v@sE6l)(QnVrA3K3 Jwg%Q*3;^V|9OD20 diff --git a/rest_framework/locale/fa/LC_MESSAGES/django.po b/rest_framework/locale/fa/LC_MESSAGES/django.po index 0aa9ae4c62..6a5b99acf6 100644 --- a/rest_framework/locale/fa/LC_MESSAGES/django.po +++ b/rest_framework/locale/fa/LC_MESSAGES/django.po @@ -3,437 +3,574 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Ali Mahdiyar , 2020 +# Aryan Baghi , 2020 +# Omid Zarin , 2019 +# Xavier Ordoquy , 2020 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2016-07-12 15:14+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:58+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Persian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/fa/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fa\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." -msgstr "" +msgstr "هدر اولیه نامعتبر است. اطلاعات هویتی ارائه نشده است." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." -msgstr "" +msgstr "هدر اولیه نامعتبر است. رشته ی اطلاعات هویتی نباید شامل فاصله باشد." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." -msgstr "" +msgstr "هدر اولیه نامعتبر است. اطلاعات هویتی با متد base64 به درستی رمزنگاری نشده است." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." -msgstr "" +msgstr "نام کاربری/رمز‌عبور نامعتبر است." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." -msgstr "" +msgstr "کاربر غیر فعال یا حذف شده است." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." -msgstr "" +msgstr "توکن هدر نامعتبر است. اطلاعات هویتی ارائه نشده است. " -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." -msgstr "" +msgstr "توکن هدر نامعتبر است. توکن نباید شامل فضای خالی باشد." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." -msgstr "" +msgstr "توکن هدر نامعتبر است. توکن شامل کاراکتر‌های نامعتبر است." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." -msgstr "" +msgstr "توکن هدر نامعتبر است. " #: authtoken/apps.py:7 msgid "Auth Token" -msgstr "" +msgstr "توکن اعتبار‌سنجی" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" -msgstr "" +msgstr "کلید" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "کاربر" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" -msgstr "" +msgstr "ایجاد‌شد" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" -msgstr "" +msgstr "توکن" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" -msgstr "" +msgstr "توکن‌ها" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "نام‌کاربری" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" - -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "" +msgstr "رمز‌عبور" -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." -msgstr "" +msgstr "با اطلاعات ارسال شده نمیتوان وارد شد." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." -msgstr "" +msgstr "باید شامل نام‌کاربری و رمز‌عبود باشد." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." +msgstr "خطای سمت سرور رخ داده است." + +#: exceptions.py:142 +msgid "Invalid input." msgstr "" -#: exceptions.py:84 +#: exceptions.py:161 msgid "Malformed request." -msgstr "" +msgstr "درخواست ناقص." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." -msgstr "" +msgstr "اطلاعات احراز هویت صحیح نیست." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." -msgstr "" +msgstr "اطلاعات برای اعتبارسنجی ارسال نشده است." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." -msgstr "" +msgstr "شما اجازه انجام این دستور را ندارید." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." -msgstr "" +msgstr "یافت نشد." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." -msgstr "" +msgstr "متد {method} مجاز نیست." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." -msgstr "" +msgstr "نوع محتوای درخواستی در هدر قابل قبول نیست." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." -msgstr "" +msgstr "نوع رسانه {media_type} در درخواست پشتیبانی نمیشود." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." +msgstr "تعداد درخواست‌های شما محدود شده است." + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." msgstr "" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 -msgid "This field is required." +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." msgstr "" -#: fields.py:270 +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "این مقدار لازم است." + +#: fields.py:317 msgid "This field may not be null." +msgstr "این مقدار نباید توهی باشد." + +#: fields.py:701 +msgid "Must be a valid boolean." msgstr "" -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." +#: fields.py:766 +msgid "Not a valid string." msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." -msgstr "" +msgstr "این مقدار نباید خالی باشد." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." -msgstr "" +msgstr "مطمعن شوید طول این مقدار بیشتر از {max_length} نیست." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." -msgstr "" +msgstr "مطمعن شوید طول این مقدار حداقل {min_length} است." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." -msgstr "" +msgstr "پست الکترونیکی صحبح وارد کنید." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." -msgstr "" +msgstr "مقدار وارد شده با الگو مطابقت ندارد." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." +msgstr "یک \"slug\" معتبر شامل حروف، اعداد، آندرلاین یا خط فاصله وارد کنید" + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." msgstr "" -#: fields.py:747 +#: fields.py:854 msgid "Enter a valid URL." -msgstr "" +msgstr "یک URL معتبر وارد کنید" -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." +#: fields.py:867 +msgid "Must be a valid UUID." msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "" +msgstr "یک آدرس IPv4 یا IPv6 معتبر وارد کنید." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." -msgstr "" +msgstr "یک مقدار عددی معتبر لازم است." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." -msgstr "" +msgstr "این مقدار باید کوچکتر یا مساوی با {max_value} باشد." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." -msgstr "" +msgstr "این مقدار باید بزرگتر یا مساوی با {min_value} باشد." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." -msgstr "" +msgstr "رشته بسیار طولانی است." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." -msgstr "" +msgstr "یک عدد معتبر نیاز است." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." -msgstr "" +msgstr "بیشتر از {max_digits} رقم نباید وجود داشته باشد." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." -msgstr "" +msgstr "بیشتر از {max_decimal_places} ممیز اعشار نباید وجود داشته باشد" -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." -msgstr "" +msgstr "بیشتر از {max_whole_digits} رقم نباید قبل از ممیز اعشار باشد." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "فرمت Datetime اشتباه است. از یکی از این فرمت ها استفاده کنید: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." +msgstr "باید datetime باشد اما date دریافت شد." + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." msgstr "" -#: fields.py:1103 -msgid "Date has wrong format. Use one of these formats instead: {format}." +#: fields.py:1151 +msgid "Datetime value out of range." msgstr "" -#: fields.py:1104 +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "فرمت تاریخ اشتباه است. از یکی از این فرمت ها استفاده کنید: {format}." + +#: fields.py:1237 msgid "Expected a date but got a datetime." -msgstr "" +msgstr "باید date باشد اما datetime دریافت شد." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "فرمت Time اشتباه است. از یکی از این فرمت ها استفاده کنید: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "فرمت Duration اشتباه است. از یکی از این فرمت ها استفاده کنید: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." -msgstr "" +msgstr "\"{input}\" یک انتخاب معتبر نیست." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." -msgstr "" +msgstr "بیشتر از {count} آیتم..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." -msgstr "" +msgstr "باید یک لیست از مقادیر ارسال شود اما یک «{input_type}» دریافت شد." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." -msgstr "" +msgstr "این بخش نمی‌تواند خالی باشد." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." -msgstr "" +msgstr "\"{input}\" یک مسیر انتخاب معتبر نیست." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." -msgstr "" +msgstr "فایلی ارسال نشده است." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." -msgstr "" +msgstr "دیتای ارسال شده فایل نیست. encoding type فرم را چک کنید." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." -msgstr "" +msgstr "اسم فایل مشخص نیست." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." -msgstr "" +msgstr "فایل ارسال شده خالی است." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." -msgstr "" +msgstr "نام این فایل باید حداکثر {max_length} کاراکتر باشد ({length} کاراکتر دارد)." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." -msgstr "" +msgstr "یک عکس معتبر آپلود کنید. فایلی که ارسال کردید عکس یا عکس خراب شده نیست" -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." +msgstr "این لیست نمی تواند خالی باشد" + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." msgstr "" -#: fields.py:1502 +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "باید دیکشنری از آیتم ها ارسال می شد، اما \"{input_type}\" ارسال شده است." + +#: fields.py:1683 +msgid "This dictionary may not be empty." msgstr "" -#: fields.py:1549 +#: fields.py:1755 msgid "Value must be valid JSON." +msgstr "مقدار باید JSON معتبر باشد." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "جستجو" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "ترتیب" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" -msgstr "" +msgstr "صعودی" -#: filters.py:337 +#: filters.py:288 msgid "descending" +msgstr "نزولی" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." msgstr "" -#: pagination.py:193 +#: pagination.py:189 msgid "Invalid page." +msgstr "صفحه نامعتبر" + +#: pagination.py:374 +msgid "The initial index from which to return the results." msgstr "" -#: pagination.py:427 -msgid "Invalid cursor" +#: pagination.py:581 +msgid "The pagination cursor value." msgstr "" -#: relations.py:207 +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "مکان نمای نامعتبر" + +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "" +msgstr "pk نامعتبر \"{pk_value}\" - این Object وجود ندارد" -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." -msgstr "" +msgstr "تایپ نامعتبر. باید pk ارسال می شد اما {data_type} ارسال شده است." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." -msgstr "" +msgstr "هایپرلینک نامعتبر - URL مطابق وجود ندارد" -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." -msgstr "" +msgstr "هایپرلینک نامعتبر - خطا در تطابق URL" -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." -msgstr "" +msgstr "هایپرلینک نامعبتر - Object وجود ندارد." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." -msgstr "" +msgstr "داده نامعتبر. باید رشته ی URL باشد، اما {data_type} دریافت شد." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." -msgstr "" +msgstr "Object با {slug_name}={value} وجود ندارد." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." +msgstr "مقدار نامعتبر." + +#: schemas/utils.py:32 +msgid "unique integer value" msgstr "" -#: serializers.py:326 -msgid "Invalid data. Expected a dictionary, but got {datatype}." +#: schemas/utils.py:34 +msgid "UUID string" msgstr "" +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "داده نامعتبر. باید دیکشنری ارسال می شد، اما {datatype} ارسال شده است." + #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" +msgstr "فیلترها" + +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:78 +msgid "request form" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:157 +msgid "main content" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 -msgid "None" +#: templates/rest_framework/base.html:173 +msgid "request info" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 -msgid "No items to select." +#: templates/rest_framework/base.html:177 +msgid "response info" msgstr "" -#: validators.py:43 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "None" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "آیتمی برای انتخاب وجود ندارد" + +#: validators.py:39 msgid "This field must be unique." -msgstr "" +msgstr "این فیلد باید یکتا باشد" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." +msgstr "فیلدهای {field_names} باید یک مجموعه یکتا باشند." + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." msgstr "" -#: validators.py:245 +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." -msgstr "" +msgstr "این فیلد باید برای تاریخ \"{date_field}\" یکتا باشد." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." -msgstr "" +msgstr "این فیلد باید برای ماه \"{date_field}\" یکتا باشد." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." -msgstr "" +msgstr "این فیلد باید برای سال \"{date_field}\" یکتا باشد." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." -msgstr "" +msgstr "ورژن نامعتبر در هدر \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." -msgstr "" +msgstr "ورژن نامعتبر در مسیر URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "ورژن نامعتبر در مسیر URL. با هیچ نام گذاری ورژنی تطابق ندارد." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." -msgstr "" +msgstr "نسخه نامعتبر در نام هاست" -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." -msgstr "" - -#: views.py:88 -msgid "Permission denied." -msgstr "" +msgstr "ورژن نامعتبر در پارامتر کوئری." diff --git a/rest_framework/locale/fa_IR/LC_MESSAGES/django.mo b/rest_framework/locale/fa_IR/LC_MESSAGES/django.mo index 1f72e1090f6e667d41ee4f925ea7f9a0f51b6f6d..52d3f3bf84e43fd3972255b9ae363c56677e4d00 100644 GIT binary patch literal 11989 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, 2020 +# Aryan Baghi , 2020 +# Omid Zarin , 2019 +# Xavier Ordoquy , 2020 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2016-07-12 15:14+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:59+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Persian (Iran) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/fa_IR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fa_IR\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." -msgstr "" +msgstr "هدر اولیه نامعتبر است. اطلاعات هویتی ارائه نشده است." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." -msgstr "" +msgstr "هدر اولیه نامعتبر است. رشته ی اطلاعات هویتی نباید شامل فاصله باشد." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." -msgstr "" +msgstr "هدر اولیه نامعتبر است. اطلاعات هویتی با متد base64 به درستی رمزنگاری نشده است." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." -msgstr "" +msgstr "نام کاربری/رمز‌عبور نامعتبر است." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." -msgstr "" +msgstr "کاربر غیر فعال یا حذف شده است." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." -msgstr "" +msgstr "توکن هدر نامعتبر است. اطلاعات هویتی ارائه نشده است. " -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." -msgstr "" +msgstr "توکن هدر نامعتبر است. توکن نباید شامل فضای خالی باشد." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." -msgstr "" +msgstr "توکن هدر نامعتبر است. توکن شامل کاراکتر‌های نامعتبر است." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." -msgstr "" +msgstr "توکن هدر نامعتبر است. " #: authtoken/apps.py:7 msgid "Auth Token" -msgstr "" +msgstr "توکن اعتبار‌سنجی" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" -msgstr "" +msgstr "کلید" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "کاربر" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" -msgstr "" +msgstr "ایجاد‌شد" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" -msgstr "" +msgstr "توکن" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" -msgstr "" +msgstr "توکن‌ها" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "نام‌کاربری" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" - -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "" +msgstr "رمز‌عبور" -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." -msgstr "" +msgstr "با اطلاعات ارسال شده نمیتوان وارد شد." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." -msgstr "" +msgstr "باید شامل نام‌کاربری و رمز‌عبود باشد." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." +msgstr "خطای سمت سرور رخ داده است." + +#: exceptions.py:142 +msgid "Invalid input." msgstr "" -#: exceptions.py:84 +#: exceptions.py:161 msgid "Malformed request." -msgstr "" +msgstr "درخواست ناقص." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." -msgstr "" +msgstr "اطلاعات احراز هویت صحیح نیست." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." -msgstr "" +msgstr "اطلاعات برای اعتبارسنجی ارسال نشده است." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." -msgstr "" +msgstr "شما اجازه انجام این دستور را ندارید." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." -msgstr "" +msgstr "یافت نشد." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." -msgstr "" +msgstr "متد {method} مجاز نیست." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." -msgstr "" +msgstr "نوع محتوای درخواستی در هدر قابل قبول نیست." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." -msgstr "" +msgstr "نوع رسانه {media_type} در درخواست پشتیبانی نمیشود." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." +msgstr "تعداد درخواست‌های شما محدود شده است." + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." msgstr "" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 -msgid "This field is required." +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." msgstr "" -#: fields.py:270 +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "این مقدار لازم است." + +#: fields.py:317 msgid "This field may not be null." +msgstr "این مقدار نباید توهی باشد." + +#: fields.py:701 +msgid "Must be a valid boolean." msgstr "" -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." +#: fields.py:766 +msgid "Not a valid string." msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." -msgstr "" +msgstr "این مقدار نباید خالی باشد." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." -msgstr "" +msgstr "مطمعن شوید طول این مقدار بیشتر از {max_length} نیست." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." -msgstr "" +msgstr "مطمعن شوید طول این مقدار حداقل {min_length} است." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." -msgstr "" +msgstr "پست الکترونیکی صحبح وارد کنید." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." -msgstr "" +msgstr "مقدار وارد شده با الگو مطابقت ندارد." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." +msgstr "یک \"slug\" معتبر شامل حروف، اعداد، آندرلاین یا خط فاصله وارد کنید" + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." msgstr "" -#: fields.py:747 +#: fields.py:854 msgid "Enter a valid URL." -msgstr "" +msgstr "یک URL معتبر وارد کنید" -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." +#: fields.py:867 +msgid "Must be a valid UUID." msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "" +msgstr "یک آدرس IPv4 یا IPv6 معتبر وارد کنید." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." -msgstr "" +msgstr "یک مقدار عددی معتبر لازم است." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." -msgstr "" +msgstr "این مقدار باید کوچکتر یا مساوی با {max_value} باشد." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." -msgstr "" +msgstr "این مقدار باید بزرگتر یا مساوی با {min_value} باشد." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." -msgstr "" +msgstr "رشته بسیار طولانی است." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." -msgstr "" +msgstr "یک عدد معتبر نیاز است." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." -msgstr "" +msgstr "بیشتر از {max_digits} رقم نباید وجود داشته باشد." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." -msgstr "" +msgstr "بیشتر از {max_decimal_places} ممیز اعشار نباید وجود داشته باشد" -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." -msgstr "" +msgstr "بیشتر از {max_whole_digits} رقم نباید قبل از ممیز اعشار باشد." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "فرمت Datetime اشتباه است. از یکی از این فرمت ها استفاده کنید: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." +msgstr "باید datetime باشد اما date دریافت شد." + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." msgstr "" -#: fields.py:1103 -msgid "Date has wrong format. Use one of these formats instead: {format}." +#: fields.py:1151 +msgid "Datetime value out of range." msgstr "" -#: fields.py:1104 +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "فرمت تاریخ اشتباه است. از یکی از این فرمت ها استفاده کنید: {format}." + +#: fields.py:1237 msgid "Expected a date but got a datetime." -msgstr "" +msgstr "باید date باشد اما datetime دریافت شد." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "فرمت Time اشتباه است. از یکی از این فرمت ها استفاده کنید: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "فرمت Duration اشتباه است. از یکی از این فرمت ها استفاده کنید: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." -msgstr "" +msgstr "\"{input}\" یک انتخاب معتبر نیست." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." -msgstr "" +msgstr "بیشتر از {count} آیتم..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." -msgstr "" +msgstr "باید یک لیست از مقادیر ارسال شود اما یک «{input_type}» دریافت شد." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." -msgstr "" +msgstr "این بخش نمی‌تواند خالی باشد." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." -msgstr "" +msgstr "\"{input}\" یک مسیر انتخاب معتبر نیست." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." -msgstr "" +msgstr "فایلی ارسال نشده است." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." -msgstr "" +msgstr "دیتای ارسال شده فایل نیست. encoding type فرم را چک کنید." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." -msgstr "" +msgstr "اسم فایل مشخص نیست." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." -msgstr "" +msgstr "فایل ارسال شده خالی است." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." -msgstr "" +msgstr "نام این فایل باید حداکثر {max_length} کاراکتر باشد ({length} کاراکتر دارد)." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." -msgstr "" +msgstr "یک عکس معتبر آپلود کنید. فایلی که ارسال کردید عکس یا عکس خراب شده نیست" -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." +msgstr "این لیست نمی تواند خالی باشد" + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." msgstr "" -#: fields.py:1502 +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "باید دیکشنری از آیتم ها ارسال می شد، اما \"{input_type}\" ارسال شده است." + +#: fields.py:1683 +msgid "This dictionary may not be empty." msgstr "" -#: fields.py:1549 +#: fields.py:1755 msgid "Value must be valid JSON." +msgstr "مقدار باید JSON معتبر باشد." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "جستجو" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "ترتیب" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" -msgstr "" +msgstr "صعودی" -#: filters.py:337 +#: filters.py:288 msgid "descending" +msgstr "نزولی" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." msgstr "" -#: pagination.py:193 +#: pagination.py:189 msgid "Invalid page." +msgstr "صفحه نامعتبر" + +#: pagination.py:374 +msgid "The initial index from which to return the results." msgstr "" -#: pagination.py:427 -msgid "Invalid cursor" +#: pagination.py:581 +msgid "The pagination cursor value." msgstr "" -#: relations.py:207 +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "مکان نمای نامعتبر" + +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "" +msgstr "pk نامعتبر \"{pk_value}\" - این Object وجود ندارد" -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." -msgstr "" +msgstr "تایپ نامعتبر. باید pk ارسال می شد اما {data_type} ارسال شده است." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." -msgstr "" +msgstr "هایپرلینک نامعتبر - URL مطابق وجود ندارد" -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." -msgstr "" +msgstr "هایپرلینک نامعتبر - خطا در تطابق URL" -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." -msgstr "" +msgstr "هایپرلینک نامعبتر - Object وجود ندارد." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." -msgstr "" +msgstr "داده نامعتبر. باید رشته ی URL باشد، اما {data_type} دریافت شد." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." -msgstr "" +msgstr "Object با {slug_name}={value} وجود ندارد." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." +msgstr "مقدار نامعتبر." + +#: schemas/utils.py:32 +msgid "unique integer value" msgstr "" -#: serializers.py:326 -msgid "Invalid data. Expected a dictionary, but got {datatype}." +#: schemas/utils.py:34 +msgid "UUID string" msgstr "" +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "داده نامعتبر. باید دیکشنری ارسال می شد، اما {datatype} ارسال شده است." + #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" +msgstr "فیلترها" + +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:78 +msgid "request form" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:157 +msgid "main content" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 -msgid "None" +#: templates/rest_framework/base.html:173 +msgid "request info" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 -msgid "No items to select." +#: templates/rest_framework/base.html:177 +msgid "response info" msgstr "" -#: validators.py:43 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "None" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "آیتمی برای انتخاب وجود ندارد" + +#: validators.py:39 msgid "This field must be unique." -msgstr "" +msgstr "این فیلد باید یکتا باشد" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." +msgstr "فیلدهای {field_names} باید یک مجموعه یکتا باشند." + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." msgstr "" -#: validators.py:245 +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." -msgstr "" +msgstr "این فیلد باید برای تاریخ \"{date_field}\" یکتا باشد." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." -msgstr "" +msgstr "این فیلد باید برای ماه \"{date_field}\" یکتا باشد." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." -msgstr "" +msgstr "این فیلد باید برای سال \"{date_field}\" یکتا باشد." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." -msgstr "" +msgstr "ورژن نامعتبر در هدر \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." -msgstr "" +msgstr "ورژن نامعتبر در مسیر URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "ورژن نامعتبر در مسیر URL. با هیچ نام گذاری ورژنی تطابق ندارد." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." -msgstr "" +msgstr "نسخه نامعتبر در نام هاست" -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." -msgstr "" - -#: views.py:88 -msgid "Permission denied." -msgstr "" +msgstr "ورژن نامعتبر در پارامتر کوئری." diff --git a/rest_framework/locale/fi/LC_MESSAGES/django.mo b/rest_framework/locale/fi/LC_MESSAGES/django.mo index 67dd26ef2cea382cfe820e3eb19867b8af42aeec..50a6d0f8a9fb01938b54ccd72ef0d55931faf36a 100644 GIT binary patch delta 2472 zcmZ|QZERCj9LMofwy_5h24jPJ1TJAVC*GfH$NuCYX>YDlteR!6g{UB8emtNi;@Z7~;eC*WM-g!jt{`Ip^H= z+;jiuoVz1kqn(MdX_?0irI#pGWEk_RoX7{omt%~DbFm87;7lAueg80);Azam&(M$8 za6aD0Z1m+CvjPLS8i&#*%tb0~ba?o6E4JfCd;=fD+t`T34;j;e8?XUSp|0P;wK#o} zF^^ynr{U|U2_AR*KXUyJHQrxXZxY5VeKS3Ovl7>14IV@O z%;$XQdpFRBlcpF`f%W((ZbF7I2QYwdVGVwY?cCq|N@W=~7_oHr@#7g`gl`&%( zGX*NK5$llOC(Iy~4Rm~n8u(9SW1Dtnp@Di(KRkdM@Ev51=3{KZQPk2Ddehe%QK{}l z{>)C-!>EazM?ZdpRXYEFQ_;YcT-7dZN8TwWh}r`KI0yH-?USfXokjMmNn$2mK~}?j zjasUoP!lX?aFS)Vp_b$o!} zQ<$A5v=uesov5YRg9~s3*WtIQ2dg5jKCH!rQrkm?Yo-siDc;6x{KEAbdT3`5Yl&&Z zcxj`elc1uvoK8>>yP1q$)aY%8tq^La%I1=`JF)94g9c7om;KHZac-I_6qVl_!W+Vv_D(LFmk?um#LhsapDt=2lbx zV6>L2iE_77yIA|-8Mi$bJKg#+h(3tj)n;I~27#qdOvd;`^+oy^(aSb+;Xk4BNq)?U8}zcQ zJSgjpSSv@aNs#Ru$gC~SoW gFONjRVS6aXd{0$haE5(5vLkkXC?1PBW4@Yy0XW$#F8}}l delta 2713 zcmZYATWl0n9LMpqz0d-dVu4Z$ZKti!Vriv9p|;Y}a5lPTf4K3q~J!Q zAcB~0_+SWLzQ5T4A3W)QKIhENocozx zUv_w9{QJC&V}`Vim_)Rv8*>o%4&y>Pm2FHZzKs?R;bgpp#W-TPF$Fjk^?oZ(#LYMY z2e2CV<23vjv+yRi8WT6akXgu$X*?~XxCqbVTud8j%xav6v++1K;#b&)qaHG*0oS2^ zKZfmi1VDWWZp`asRN)z{!#l{IsT`H+=`7TUn@|n)U_GA1X8aoG zVL`4jHMjw}We%a9JC2oj6*ZIh@KG!tO*^!2mXMi_Yfu%uj7#t=&cVRLsS2C1kn&Q$ z?4U;SENWyYkSR6i{PI2j_X!L?o%?f8Gtt6DOV@>QRTw2xfk!YO&!B}@a18#A8JI?a zRWVuk80I0{z_j|dA&;6aR6W~K+vqtQk1zS)KBPbf1dawaiUWwXF z5uAmGP-}eAFW*HCpmtp9xpuTD??83%AbK-`nUwFLI+RB5s&IHd^IuD*jvH%nJ8Gn# z;RL+o`vYpEcl~lY!+D%?5wd^Ha@5SM!vg_hY}A_n#?jE;$)_?c)stx97Njrc`8b(U zGH;?rdd~mhI+jtsjk9nR!_){CU?;ZW(|8iKSN_EL*h(eZj0bQvUd9G2V>(sOM%4YI zsDa0ClhITa6&Vx64%F`a0BPHNgW4N^U=vPa=cu9eI0H|i-oJ*qm{F3Nsj;Ym6`_`( z8C&oLd=fuLj&IxqtkkaWKsKz|i|W}CRKuU*Blt6F3UjDc4b@^JwxBw&4_D%QsPnJs z;{cfsR6Uze@9js`_XZZxzPUiAnHx7yYc+vZ^guIe%6pKX%ue4|kw5bxmukF*dM}Na z^j;+wmfv(B-za0F_Qo?f1^4>pcQBjw&HH53)3eAyFkfH>eupfdxry3*f1%cJ7_F(p zbC6j#`%p`D0@cwosLgi+TQH4QwFlcCxKa1Lb=1Vea=r;0>$zr}buo~6y0BQs; zqV~pXs3|{>%kfv#NE>NYGqVWQfn8iQRBU&buFSIPV(3lPDoH2o4Oh!~Cxxqr?U`3%p!v zT{n>5LM$Sx2=!b^-*%l{{f}5kXvUQ06J5ksLSMnfgwnDU-qL0JdB*Fdjaq+Ap^|p} zJTI5}NN9@J6KTXYLQABxt2I|jC$!PD7uFI=qX|BW-cjyB&8`xM!aH4Ffs0LK5b@1i z_)qDrcLgpb8i{UVCb5dprqTN8c+DY{nuv8??!U`iviU^0e`^yCBiOfIYURr3#!K}n zI=%mR+Js6^5S@f~eoz|-XSeMp?iO9h?6v!1(Znak^?vT3;zg-ka#=}LAhRp7%MK@&mQ?w< zzl*CAKU$>^N{a(s$*$5ZX}M}My2rBVg6r<7Nqkg3E4!WU*ff~@sr;3+yakTcA9386 wwae+-8H~BHVB*k}(*>>*avltIXmF1^bS4@d8mvi7ujtJP#d?E@cPncC1#S{*3;+NC diff --git a/rest_framework/locale/fi/LC_MESSAGES/django.po b/rest_framework/locale/fi/LC_MESSAGES/django.po index 0791a30050..97e5c13942 100644 --- a/rest_framework/locale/fi/LC_MESSAGES/django.po +++ b/rest_framework/locale/fi/LC_MESSAGES/django.po @@ -5,13 +5,14 @@ # Translators: # Aarni Koskela, 2015 # Aarni Koskela, 2015-2016 +# Kimmo Huoman , 2020 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Aarni Koskela\n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Finnish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/fi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,423 +20,556 @@ msgstr "" "Language: fi\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." -msgstr "Epäkelpo perusotsake. Ei annettuja tunnuksia." +msgstr "Epäkelpo \"basic\" -otsake. Ei annettuja tunnuksia." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." -msgstr "Epäkelpo perusotsake. Tunnusmerkkijono ei saa sisältää välilyöntejä." +msgstr "Epäkelpo \"basic\" -otsake. Tunnusmerkkijono ei saa sisältää välilyöntejä." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." -msgstr "Epäkelpo perusotsake. Tunnukset eivät ole base64-koodattu." +msgstr "Epäkelpo \"basic\" -otsake. Tunnukset eivät ole base64-koodattu." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Epäkelpo käyttäjänimi tai salasana." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Käyttäjä ei-aktiivinen tai poistettu." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." -msgstr "Epäkelpo Token-otsake. Ei annettuja tunnuksia." +msgstr "Epäkelpo \"token\" -otsake. Ei annettuja tunnuksia." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." -msgstr "Epäkelpo Token-otsake. Tunnusmerkkijono ei saa sisältää välilyöntejä." +msgstr "Epäkelpo \"token\" -otsake. Tunnusmerkkijono ei saa sisältää välilyöntejä." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." -msgstr "Epäkelpo Token-otsake. Tunnusmerkkijono ei saa sisältää epäkelpoja merkkejä." +msgstr "Epäkelpo \"token\" -otsake. Tunnusmerkkijono ei saa sisältää epäkelpoja merkkejä." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." -msgstr "Epäkelpo Token." +msgstr "Epäkelpo token." #: authtoken/apps.py:7 msgid "Auth Token" msgstr "Autentikaatiotunniste" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Avain" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Käyttäjä" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Luotu" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Tunniste" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tunnisteet" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Käyttäjänimi" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Salasana" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Käyttäjätili ei ole käytössä." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." -msgstr "Ei voitu kirjautua annetuilla tunnuksilla." +msgstr "Kirjautuminen epäonnistui annetuilla tunnuksilla." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Pitää sisältää \"username\" ja \"password\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Sattui palvelinvirhe." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Pyyntö on virheellisen muotoinen." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Väärät autentikaatiotunnukset." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Autentikaatiotunnuksia ei annettu." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." -msgstr "Sinulla ei ole lupaa suorittaa tätä toimintoa." +msgstr "Sinulla ei ole oikeutta suorittaa tätä toimintoa." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Ei löydy." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Metodi \"{method}\" ei ole sallittu." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Ei voitu vastata pyynnön Accept-otsakkeen mukaisesti." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Pyynnön mediatyyppiä \"{media_type}\" ei tueta." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Pyyntö hidastettu." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Tämä kenttä vaaditaan." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Tämän kentän arvo ei voi olla \"null\"." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" ei ole kelvollinen totuusarvo." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Tämä kenttä ei voi olla tyhjä." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Arvo saa olla enintään {max_length} merkkiä pitkä." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Arvo tulee olla vähintään {min_length} merkkiä pitkä." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Syötä kelvollinen sähköpostiosoite." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Arvo ei täsmää vaadittuun kuvioon." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Tässä voidaan käyttää vain kirjaimia (a-z), numeroita (0-9) sekä ala- ja tavuviivoja (_ -)." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Syötä oikea URL-osoite." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "{value} ei ole kelvollinen UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Syötä kelvollinen IPv4- tai IPv6-osoite." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Syötä kelvollinen kokonaisluku." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." -msgstr "Tämän arvon on oltava enintään {max_value}." +msgstr "Tämän arvon on oltava pienempi tai yhtä suuri kuin {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." -msgstr "Tämän luvun on oltava vähintään {min_value}." +msgstr "Tämän luvun on oltava suurempi tai yhtä suuri kuin {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Liian suuri merkkijonoarvo." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Kelvollinen luku vaaditaan." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Tässä luvussa voi olla yhteensä enintään {max_digits} numeroa." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." -msgstr "Tässä luvussa saa olla enintään {max_decimal_places} desimaalia." +msgstr "Tässä luvussa voi olla enintään {max_decimal_places} desimaalia." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." -msgstr "Tässä luvussa saa olla enintään {max_whole_digits} numeroa ennen desimaalipilkkua." +msgstr "Tässä luvussa voi olla enintään {max_whole_digits} numeroa ennen desimaalipilkkua." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Virheellinen päivämäärän/ajan muotoilu. Käytä jotain näistä muodoista: {format}" -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Odotettiin päivämäärää ja aikaa, saatiin vain päivämäärä." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Virheellinen päivämäärän muotoilu. Käytä jotain näistä muodoista: {format}" -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Odotettiin päivämäärää, saatiin päivämäärä ja aika." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Virheellinen kellonajan muotoilu. Käytä jotain näistä muodoista: {format}" -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Virheellinen keston muotoilu. Käytä jotain näistä muodoista: {format}" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" ei ole kelvollinen valinta." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Enemmän kuin {count} kappaletta..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Odotettiin listaa, saatiin tyyppi {input_type}." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Valinta ei saa olla tyhjä." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" ei ole kelvollinen polku." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Yhtään tiedostoa ei ole lähetetty." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Tiedostoa ei lähetetty. Tarkista lomakkeen koodaus (encoding)." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Tiedostonimeä ei voitu päätellä." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Lähetetty tiedosto on tyhjä." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Varmista että tiedostonimi on enintään {max_length} merkkiä pitkä (nyt {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Kuva ei kelpaa. Lähettämäsi tiedosto ei ole kuva, tai tiedosto on vioittunut." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Lista ei saa olla tyhjä." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Odotettiin sanakirjaa, saatiin tyyppi {input_type}." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Arvon pitää olla kelvollista JSONia." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Lähetä" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Haku" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Järjestys" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "nouseva" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "laskeva" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Epäkelpo sivu." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Epäkelpo kursori" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Epäkelpo pääavain {pk_value} - objektia ei ole olemassa." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Väärä tyyppi. Odotettiin pääavainarvoa, saatiin {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Epäkelpo linkki - URL ei täsmää." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Epäkelpo linkki - epäkelpo URL-osuma." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Epäkelpo linkki - objektia ei ole." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Epäkelpo tyyppi. Odotettiin URL-merkkijonoa, saatiin {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objektia ({slug_name}={value}) ei ole." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Epäkelpo arvo." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Odotettiin sanakirjaa, saatiin tyyppi {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Suotimet" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Kenttäsuotimet" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Järjestys" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Haku" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Ei mitään" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Ei valittavia kohteita." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Arvon tulee olla uniikki." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Kenttien {field_names} tulee muodostaa uniikki joukko." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Kentän tulee olla uniikki päivämäärän {date_field} suhteen." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Kentän tulee olla uniikki kuukauden {date_field} suhteen." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Kentän tulee olla uniikki vuoden {date_field} suhteen." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Epäkelpo versio Accept-otsakkeessa." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Epäkelpo versio URL-polussa." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "URL-polun versio ei täsmää mihinkään versionimiavaruuteen." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Epäkelpo versio palvelinosoitteessa." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Epäkelpo versio kyselyparametrissa." - -#: views.py:88 -msgid "Permission denied." -msgstr "Pääsy evätty." diff --git a/rest_framework/locale/fr/LC_MESSAGES/django.mo b/rest_framework/locale/fr/LC_MESSAGES/django.mo index b462e08d7185771785d15b3f7b20ba5579011b26..a1c0b3c2d4a0e9d28c07dac5652968dee90fcff6 100644 GIT binary patch delta 2624 zcmZ|Qe@vBC9LMp)PcadH03!11;VN)ZKtMqS&0mS)k3hj~&+|Mi=jx1eU+0|XdCqgr z`JVIeZR^>V$amutPZ-i>a;|cMF$ZNbKO}#OF&36$5iZAx*o(UVIn2Sc_yESR94})T z-oX**A8E`Yti%TFbB&lG3QK8-oA*TDbV>}OF^3^jEunyqIPu*Q}GgN(_KYvw&WtWJry;uY*cK^kiJbV z*5d}$0AEI;WqwAYY(^Bjd!-O_c)xj&!aUsIc@iI_epNSMCeu^LRmd1jD{4S{ktH7Miic>P?A>=FVq`CZ-DgO7Sg;d(S25mkf;A~EXLo21E zFB~i9oQ6cyX3{eAi7>0kN@K{)I0+w@u*iPN@lfJ)_&qs;H2fALjQOclXVg}K&D1tt1R+x(niv;8BY2* z&G#ztxQIN$9!SZvucuT*OGm~fMQ5kwCfQrlYolke^%N3T*G{2v(T;ZnfL8 zi=(Hrr^dy{2JJ5G*~cTslz32euExCvb>Qzst8M!bt#F_li6a34zh72JzY zQC2>*J1bJTh&?nsLD`*~*ddHXjiAgRjIvVKZ6BfR`plfjCf$SVS9K2K@G>sNYgmbs zxCS#drFP+N)aCqtO-0VDGdHq%d?-142W69di;}CSC`;;eMozT`_){g*FnQFlouREdEhd};2+5TRDU9WqyEM$%;BRT zIjuz|rv_05dK?+6x{k8h?jheI^#Em4KEYVb&ZnLCt3_1U$f^uwgv}_q?nZfGAIg#* z#u_|{GT`r#G%Jl!OR82PX;ob)?E#dfKWTd#<%9H({d*N(rI5U!g9?*U2T(?M7G;x7 zpo}zaabzYrDDC+ux%J~dJdcT($i`tmss$)3unc8l9Vq=9C<8iyGx63E)?b$94h;_6 zMpP5B+|uDxkrN?*ATO1Y5W%`iZlzQZq~0nkxynEpzg1*ktS4kLk}6(j4Rnpe#}z)FTsfnCxYuh0$8IJUM$dEi_4$U)puE~+1l+xY^3de`Uy{lyi%YADOP6b9 z<<%8cV-qPgj;J~cn%#q?h4 z+L~NX>T-ue3xAN-&h=s1=5Td-6ScR~H*&p`eva!~zzxV=MTBiZ>;baz23 z<$6~ua%iV=v!V<$oSU06%Q-rl)~{2phxcG0i%{*x%b-ai~*HC^GaY2wCy}HM6Pc1{B@JM}{_#t$@Uy}%{sq2Cq&ENn diff --git a/rest_framework/locale/fr/LC_MESSAGES/django.po b/rest_framework/locale/fr/LC_MESSAGES/django.po index 25b39e453f..c2e08c80b0 100644 --- a/rest_framework/locale/fr/LC_MESSAGES/django.po +++ b/rest_framework/locale/fr/LC_MESSAGES/django.po @@ -3,16 +3,18 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Erwann Mest , 2019 # Etienne Desgagné , 2015 # Martin Maillard , 2015 # Martin Maillard , 2015 +# Stéphane Raimbault , 2019 # Xavier Ordoquy , 2015-2016 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" "Last-Translator: Xavier Ordoquy \n" "Language-Team: French (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/fr/)\n" "MIME-Version: 1.0\n" @@ -21,40 +23,40 @@ msgstr "" "Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "En-tête « basic » non valide. Informations d'identification non fournies." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "En-tête « basic » non valide. Les informations d'identification ne doivent pas contenir d'espaces." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "En-tête « basic » non valide. Encodage base64 des informations d'identification incorrect." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Nom d'utilisateur et/ou mot de passe non valide(s)." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Utilisateur inactif ou supprimé." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "En-tête « token » non valide. Informations d'identification non fournies." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "En-tête « token » non valide. Un token ne doit pas contenir d'espaces." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "En-tête « token » non valide. Un token ne doit pas contenir de caractères invalides." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Token non valide." @@ -62,382 +64,515 @@ msgstr "Token non valide." msgid "Auth Token" msgstr "Jeton d'authentification" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Clef" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Utilisateur" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Création" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Jeton" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Jetons" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Nom de l'utilisateur" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Mot de passe" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Ce compte est désactivé." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Impossible de se connecter avec les informations d'identification fournies." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." -msgstr "\"username\" et \"password\" doivent être inclus." +msgstr "« username » et « password » doivent être inclus." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Une erreur du serveur est survenue." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Requête malformée" -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Informations d'authentification incorrectes." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Informations d'authentification non fournies." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Vous n'avez pas la permission d'effectuer cette action." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Pas trouvé." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." -msgstr "Méthode \"{method}\" non autorisée." +msgstr "Méthode « {method} » non autorisée." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "L'en-tête « Accept » n'a pas pu être satisfaite." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." -msgstr "Type de média \"{media_type}\" non supporté." +msgstr "Type de média « {media_type} » non supporté." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Requête ralentie." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Ce champ est obligatoire." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Ce champ ne peut être nul." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" n'est pas un booléen valide." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Ce champ ne peut être vide." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." -msgstr "Assurez-vous que ce champ comporte au plus {max_length} caractères." +msgstr "Assurez-vous que ce champ comporte au plus {max_length} caractères." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." -msgstr "Assurez-vous que ce champ comporte au moins {min_length} caractères." +msgstr "Assurez-vous que ce champ comporte au moins {min_length} caractères." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." -msgstr "Saisissez une adresse email valable." +msgstr "Saisissez une adresse e-mail valide." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Cette valeur ne satisfait pas le motif imposé." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Ce champ ne doit contenir que des lettres, des nombres, des tirets bas _ et des traits d'union." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Saisissez une URL valide." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" n'est pas un UUID valide." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Saisissez une adresse IPv4 ou IPv6 valide." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Un nombre entier valide est requis." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." -msgstr "Assurez-vous que cette valeur est inférieure ou égale à {max_value}." +msgstr "Assurez-vous que cette valeur est inférieure ou égale à {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." -msgstr "Assurez-vous que cette valeur est supérieure ou égale à {min_value}." +msgstr "Assurez-vous que cette valeur est supérieure ou égale à {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Chaîne de caractères trop longue." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Un nombre valide est requis." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." -msgstr "Assurez-vous qu'il n'y a pas plus de {max_digits} chiffres au total." +msgstr "Assurez-vous qu'il n'y a pas plus de {max_digits} chiffres au total." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." -msgstr "Assurez-vous qu'il n'y a pas plus de {max_decimal_places} chiffres après la virgule." +msgstr "Assurez-vous qu'il n'y a pas plus de {max_decimal_places} chiffres après la virgule." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Assurez-vous qu'il n'y a pas plus de {max_whole_digits} chiffres avant la virgule." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." -msgstr "La date + heure n'a pas le bon format. Utilisez un des formats suivants : {format}." +msgstr "La date + heure n'a pas le bon format. Utilisez un des formats suivants : {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Attendait une date + heure mais a reçu une date." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." -msgstr "La date n'a pas le bon format. Utilisez un des formats suivants : {format}." +msgstr "La date n'a pas le bon format. Utilisez un des formats suivants : {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Attendait une date mais a reçu une date + heure." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." -msgstr "L'heure n'a pas le bon format. Utilisez un des formats suivants : {format}." +msgstr "L'heure n'a pas le bon format. Utilisez un des formats suivants : {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." -msgstr "La durée n'a pas le bon format. Utilisez l'un des formats suivants: {format}." +msgstr "La durée n'a pas le bon format. Utilisez l'un des formats suivants : {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." -msgstr "\"{input}\" n'est pas un choix valide." +msgstr "« {input} » n'est pas un choix valide." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Plus de {count} éléments..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." -msgstr "Attendait une liste d'éléments mais a reçu \"{input_type}\"." +msgstr "Attendait une liste d'éléments mais a reçu « {input_type} »." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Cette sélection ne peut être vide." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." -msgstr "\"{input}\" n'est pas un choix de chemin valide." +msgstr "« {input} » n'est pas un choix de chemin valide." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Aucun fichier n'a été soumis." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "La donnée soumise n'est pas un fichier. Vérifiez le type d'encodage du formulaire." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Le nom de fichier n'a pu être déterminé." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Le fichier soumis est vide." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." -msgstr "Assurez-vous que le nom de fichier comporte au plus {max_length} caractères (il en comporte {length})." +msgstr "Assurez-vous que le nom de fichier comporte au plus {max_length} caractères (il en comporte {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Transférez une image valide. Le fichier que vous avez transféré n'est pas une image, ou il est corrompu." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Cette liste ne peut pas être vide." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." -msgstr "Attendait un dictionnaire d'éléments mais a reçu \"{input_type}\"." +msgstr "Attendait un dictionnaire d'éléments mais a reçu « {input_type} »." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "La valeur doit être un JSON valide." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Envoyer" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Recherche" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Ordre" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "croissant" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "décroissant" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." -msgstr "Page invalide." +msgstr "Page non valide." + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" -#: pagination.py:427 +#: pagination.py:583 msgid "Invalid cursor" msgstr "Curseur non valide" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "Clé primaire \"{pk_value}\" non valide - l'objet n'existe pas." +msgstr "Clé primaire « {pk_value} » non valide - l'objet n'existe pas." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Type incorrect. Attendait une clé primaire, a reçu {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." -msgstr "Lien non valide : pas d'URL correspondante." +msgstr "Lien non valide : pas d'URL correspondante." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." -msgstr "Lien non valide : URL correspondante incorrecte." +msgstr "Lien non valide : URL correspondante incorrecte." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." -msgstr "Lien non valide : l'objet n'existe pas." +msgstr "Lien non valide : l'objet n'existe pas." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Type incorrect. Attendait une URL, a reçu {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "L'object avec {slug_name}={value} n'existe pas." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Valeur non valide." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Donnée non valide. Attendait un dictionnaire, a reçu {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtres" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Filtres de champ" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Ordre" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Recherche" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Aucune" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Aucun élément à sélectionner." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Ce champ doit être unique." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Les champs {field_names} doivent former un ensemble unique." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." -msgstr "Ce champ doit être unique pour la date \"{date_field}\"." +msgstr "Ce champ doit être unique pour la date « {date_field} »." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." -msgstr "Ce champ doit être unique pour le mois \"{date_field}\"." +msgstr "Ce champ doit être unique pour le mois « {date_field} »." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." -msgstr "Ce champ doit être unique pour l'année \"{date_field}\"." +msgstr "Ce champ doit être unique pour l'année « {date_field} »." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Version non valide dans l'en-tête « Accept »." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Version non valide dans l'URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Version invalide dans l'URL. Ne correspond à aucune version de l'espace de nommage." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Version non valide dans le nom d'hôte." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Version non valide dans le paramètre de requête." - -#: views.py:88 -msgid "Permission denied." -msgstr "Permission refusée." diff --git a/rest_framework/locale/hu/LC_MESSAGES/django.mo b/rest_framework/locale/hu/LC_MESSAGES/django.mo index 8fadddcea143d0981a071d72c1801565403d1c8e..61f285299f185130df373672340c8f4146af0556 100644 GIT binary patch delta 3436 zcmb8vdu&tJ9l-Gu5|R)SMnWirgm4IySHUDe(h_KahQ}aTfRKdJ7rod=@vZIaaPPGt zL6xPhYu9z{pl3*E6Y5fJ(=x`iDjVvm4r%qmv_DuiRg+5^JN{(vyMwv+8Y<2!%45_PspU$2UaaJcvZh)ojcf5vJb;UF6y^PA zu^GROHTVW@!0WgkZ(0F*4L%BYQk~1$X zApTu6zRQimUZY$$H5O-l2<62DYJ3J|Lf=7&zzv*@%?pbYS&nycy#_mQ5Rc*0C=2^7 zHsOaT3;Op$;x9Le)koWf!{#+-AAbL zU)Y697=%1khw*dxNXbh%8UykoK8by7)G)q@a=nV>AHhi+#9Qd&0CALLoW|WaXQ@&T zV?RoSF5xnK2W8K1qWpdZvldUIOe}Z3)TrQX3CT7js%kgN9v;J;ID!560UpB+T};~N zP~QIq?!os^a$qG#U@Z=zWP1+fz3<~z%;UY>R<~#z=f<|SVkEwf^5RFh2Fpo(qOaED z2JAuU@FdFbzJYRV)k>wdz$%oScnCRI%E4855($F3fZccn*U9<+3k}9nE%d@%)DDym z9OQ(l5!{BSQF7ylScO-S-B8z1BKHBx0DnYQrsgo}QQU{5fqDsLL9e3x?tNUz`05r7 z8K8}g5+B8T@N39Ny~USwa1&*KMtYIwo3IZPI2&KZD!huD@fylXKfy<_ll_oCqOW2n zzKl8k1Jn%~eAFNKl5B2VS6t~nlx#kXWmry?0UM|@WgiVrQ=v%bbEtzcF3hO%vve3V=v#L|)($z&vpbr1D0mBUiV0SR|6Rf-%p zDRO$M3$5ZwnOACW$Mv%Rx0hX9NOnt+5VAAs0qT8JNlGb_-9%J9Lam@mh$U*0q;j^U zNa77php6OdA^iKXi7Mr@)Sc8?8DEl)2p0;uSU6^KQCYZC{F42abG?_ktaz(%8YGb( zDqYJK9xSyb0r_hvlmjINIXOL4$s3SC4Ppz_bl}l}3 zWwqU0*;Fy=gmK$jdAw2&rW3}q->Q7Ls>e4@Xksr`Jy*Ru<2#|7_NMx(F3(!9|4?>| zPWu|Wv=fW@Ca}Ms^K?zjjmk@oKW6=!)izqwRkhbmhQ<%3F4PQ{RYo&@koN6|bAMd! zj2LTcFEqGb)=9cC9gRD_6O~u(-)gT%h6ehyOevywr%j-}bf{BK7>#Sk8`D|dVi@f? zDHDu3QDZOF^_2ITG5e>wEsN8>(P7;2^qFWnQ_fgnh9`_RsnKvOq6Xt`P#Qrd`oAyOv-2~xg*dCicVH{(f3jT-1;*E|M;P3! zk@&`#BO4GYJdxF^VJDd}I+bBmMk|Kyz>$Of_T&1_Mkk0GFD6TfnVF_t(J;JcQ~QPv zKAUyN*6uBxcePV$`kWx#Fz7p8kaWVd->n~TvM%EjhV;qInBIPx5J+oh()H3=*O|K7 zP+b7lf z&E1s=<7^YDsO;J1<@WW)#q08uq4rFwIFJcL%_JT-LH=@RJU62HcPsM z-8HV=*z~#3@k8guh5M$?FFak|SV-NhGcs08-e~@E%Gmm*7F*Z!cIWO-^H;OHlu78g zNsh@#rR5wC$41Q)ZOnjsDKp~480&XUTdR+ZPru?%pG(-5C9m0cmK?Mjn;*0-dDyR^=>E?v7QKY4pV6P$tsr@+|5OBdT;wp8=Z6&q=7 zvHw`Qk-Df|_Y(m(nKZ$8!o=K!HY3ciuo=c5ck@%)%TH!Qy5$@MCS^NXm)JX7duI1$ zlF`79v~F$AUpaANy-vAa_~{vuwW@ z|2p?IZ*iKOh#D?smE*aNPMBm;)>s@ov@b3HV%=>=$0Y2kwm0kcnq-_r^!R0d%FbVL zclUqYk&r0zfteARgsg2Q?hGskXoK7%ik@rio)w)7z5JD|No1$bsc4YDk{}Oh{s(b( Bu$lk> delta 2116 zcmajfTWnNC7{Kw_(n3L6*jjp}c3Ku$E?rs{Xn_KyEtJF>6D%bvVwUZJ9(KEjbWaPc zF^fKU0ZGKMDj{kb5|c)WnvD7=ZOf;e-x zzkuuU29{&AOsP8TM(IC}GO;AeL|^hf?T>$f6}(^lPUAidaMvQ#ScO|r;}giF)nQzY zNB#fLVKx14P$qT_Wr^$O6kZrd`Twxre;sA3KE%~{3A3{FrE?1dT__P8z~%Td${v1- zOR*%VR2#P8BN)e8{Mh$%Yz*TG@isx zJdY3JU%q|wlzN8#1d=qWhUJmIgAe1MIDlIScN9-z50;auy|@i`;%nH5*HKowvX=Fi z8#@U@JdUgJD)MgCz;eivuD~wLpuF%L?!q~&r>sy4H{%B=5#Pkc7-r)mxDD6f(t$@l62fKo zR2bLc8f0#240)u=9;dOM#(O9;`VCnfwOJP?vq0AX!QddS5=-KS>@b zvHX2=V$LzMf9{0Y8jP4sFlsIaW2U=ejX73PlY6(~vw%5WnKI#d=Oa26k2_;2SKc>d zr(?U5)=)%kPg|qfPQ~J`J#J}dR1aB6%e97#u3BaGRc$YCYj0`oXlae=wv}sFb(k-! zs`bFIGZIVdp5akD?b?>^be)kdYTO^l6<3!A1~+B&-568Wh_?0~()p>;@%&Wkkd@X+ zXRn=z*)rtjrxFqMe>X6(k%)0>vZk)N$<)=>ghs5)=g0Tur(7-1Pg=vw+;w%rPG_9t z-uz@TWk<};+Bb6jbqA-J>kU7c;>LE<+xSt5cc=Ng@gcv}uOIbWHO+12SN*lQ)YNxp zWP{&2(|pEnHHGGz=NBwD)uCP!3w4;Yp%={X!v6YAIzc@7skBZwqsjcF>tx1b%*=Ho txoZo9MP^0#S#u%$6&LQYlEbldCdD-KIqlj>mn+9*<0lulo6058e*kRCQ0o8y diff --git a/rest_framework/locale/hu/LC_MESSAGES/django.po b/rest_framework/locale/hu/LC_MESSAGES/django.po index 9002f8e614..a1d75b9f06 100644 --- a/rest_framework/locale/hu/LC_MESSAGES/django.po +++ b/rest_framework/locale/hu/LC_MESSAGES/django.po @@ -3,14 +3,14 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: -# Zoltan Szalai , 2015 +# Zoltan Szalai , 2015,2018-2019 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Hungarian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/hu/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -18,423 +18,556 @@ msgstr "" "Language: hu\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." -msgstr "Érvénytelen basic fejlécmező. Nem voltak megadva azonosítók." +msgstr "Érvénytelen basic fejléc. Nem voltak megadva azonosítók." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." -msgstr "Érvénytelen basic fejlécmező. Az azonosító karakterlánc nem tartalmazhat szóközöket." +msgstr "Érvénytelen basic fejléc. Az azonosító karakterlánc nem tartalmazhat szóközöket." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." -msgstr "Érvénytelen basic fejlécmező. Az azonosítók base64 kódolása nem megfelelő." +msgstr "Érvénytelen basic fejléc. Az azonosítók base64 kódolása nem megfelelő." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Érvénytelen felhasználónév/jelszó." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "A felhasználó nincs aktiválva vagy törölve lett." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." -msgstr "Érvénytelen token fejlécmező. Nem voltak megadva azonosítók." +msgstr "Érvénytelen token fejléc. Nem voltak megadva azonosítók." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." -msgstr "Érvénytelen token fejlécmező. A token karakterlánc nem tartalmazhat szóközöket." +msgstr "Érvénytelen token fejléc. A token karakterlánc nem tartalmazhat szóközöket." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." -msgstr "" +msgstr "Érvénytelen token fejléc. A token karakterlánc nem tartalmazhat érvénytelen karaktereket." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Érvénytelen token." #: authtoken/apps.py:7 msgid "Auth Token" -msgstr "" +msgstr "Auth token" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" -msgstr "" +msgstr "Kulcs" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "Felhasználó" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" -msgstr "" +msgstr "Létrehozva" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" -msgstr "" +msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" -msgstr "" +msgstr "Tokenek" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "Felhasználónév" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" +msgstr "Jelszó" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "A felhasználó tiltva van." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "A megadott azonosítókkal nem lehet bejelentkezni." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Tartalmaznia kell a \"felhasználónevet\" és a \"jelszót\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Szerver oldali hiba történt." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Hibás kérés." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Hibás azonosítók." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Nem voltak megadva azonosítók." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Nincs jogosultsága a művelet végrehajtásához." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Nem található." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "A \"{method}\" metódus nem megengedett." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." -msgstr "A kérés Accept fejlécmezőjét nem lehetett kiszolgálni." +msgstr "A kérés Accept fejlécét nem lehetett teljesíteni." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Nem támogatott média típus \"{media_type}\" a kérésben." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "A kérés korlátozva lett." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Ennek a mezőnek a megadása kötelező." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Ez a mező nem lehet null értékű." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "Az \"{input}\" nem egy érvényes logikai érték." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." msgstr "Ez a mező nem lehet üres." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Bizonyosodjon meg arról, hogy ez a mező legfeljebb {max_length} karakterből áll." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Bizonyosodjon meg arról, hogy ez a mező legalább {min_length} karakterből áll." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Adjon meg egy érvényes e-mail címet!" -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Ez az érték nem illeszkedik a szükséges mintázatra." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Az URL barát cím csak betűket, számokat, aláhúzásokat és kötőjeleket tartalmazhat." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Adjon meg egy érvényes URL-t!" -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." +#: fields.py:867 +msgid "Must be a valid UUID." msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "" +msgstr "Adjon meg egy érvényes IPv4 vagy IPv6 címet!" -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Egy érvényes egész szám megadása szükséges." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Bizonyosodjon meg arról, hogy ez az érték legfeljebb {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Bizonyosodjon meg arról, hogy ez az érték legalább {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "A karakterlánc túl hosszú." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Egy érvényes szám megadása szükséges." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Bizonyosodjon meg arról, hogy a számjegyek száma összesen legfeljebb {max_digits}." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Bizonyosodjon meg arról, hogy a tizedes tört törtrészében levő számjegyek száma összesen legfeljebb {max_decimal_places}." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Bizonyosodjon meg arról, hogy a tizedes tört egész részében levő számjegyek száma összesen legfeljebb {max_whole_digits}." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "A dátum formátuma hibás. Használja ezek valamelyikét helyette: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Időt is tartalmazó dátum helyett egy időt nem tartalmazó dátum lett elküldve." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "A dátum formátuma hibás. Használja ezek valamelyikét helyette: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Időt nem tartalmazó dátum helyett egy időt is tartalmazó dátum lett elküldve." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Az idő formátuma hibás. Használja ezek valamelyikét helyette: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "Az időtartam formátuma hibás. Használja ezek valamelyikét helyette: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." -msgstr "Az \"{input}\" nem egy érvényes elem." +msgstr "Érvénytelen választási lehetőség: \"{input}\"" -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." -msgstr "" +msgstr "Több, mint {count} elem ..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Elemek listája helyett \"{input_type}\" lett elküldve." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." -msgstr "" +msgstr "Választania kell egy elemet." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." -msgstr "" +msgstr "Érvénytelen választási lehetőség: \"{input}\"" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Semmilyen fájl sem került feltöltésre." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Az elküldött adat nem egy fájl volt. Ellenőrizze a kódolás típusát az űrlapon!" -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "A fájlnév nem megállapítható." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "A küldött fájl üres." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Bizonyosodjon meg arról, hogy a fájlnév legfeljebb {max_length} karakterből áll (jelenlegi hossza: {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Töltsön fel egy érvényes képfájlt! A feltöltött fájl nem kép volt, vagy megsérült." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." +msgstr "Nem lehet üres a lista." + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." msgstr "" -#: fields.py:1502 +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "Dictionary elemek helyett \"{input_type}\" lett megadva." + +#: fields.py:1683 +msgid "This dictionary may not be empty." msgstr "" -#: fields.py:1549 +#: fields.py:1755 msgid "Value must be valid JSON." +msgstr "Az értéknek érvényes JSON-nek lennie." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Keresés" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Rendezés" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" -msgstr "" +msgstr "növekvő" -#: filters.py:337 +#: filters.py:288 msgid "descending" +msgstr "csökkenő" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." msgstr "" -#: pagination.py:193 +#: pagination.py:189 msgid "Invalid page." +msgstr "Érvénytelen oldal." + +#: pagination.py:374 +msgid "The initial index from which to return the results." msgstr "" -#: pagination.py:427 -msgid "Invalid cursor" +#: pagination.py:581 +msgid "The pagination cursor value." msgstr "" -#: relations.py:207 +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "Érvénytelen kurzor" + +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Érvénytelen pk \"{pk_value}\" - az objektum nem létezik." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Helytelen típus. pk érték helyett {data_type} lett elküldve." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Érvénytelen link - Nem illeszkedő URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Érvénytelen link. - Eltérő URL illeszkedés." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Érvénytelen link - Az objektum nem létezik." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Helytelen típus. URL karakterlánc helyett {data_type} lett elküldve." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Nem létezik olyan objektum, amelynél {slug_name}={value}." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Érvénytelen érték." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Érvénytelen adat. Egy dictionary helyett {datatype} lett elküldve." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" +msgstr "Szűrők" + +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:78 +msgid "request form" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:157 +msgid "main content" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 -msgid "None" +#: templates/rest_framework/base.html:173 +msgid "request info" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 -msgid "No items to select." +#: templates/rest_framework/base.html:177 +msgid "response info" msgstr "" -#: validators.py:43 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "Semmi" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "Nincsenek kiválasztható elemek." + +#: validators.py:39 msgid "This field must be unique." msgstr "Ennek a mezőnek egyedinek kell lennie." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "A {field_names} mezőnevek nem tartalmazhatnak duplikátumot." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "A mezőnek egyedinek kell lennie a \"{date_field}\" dátumra." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "A mezőnek egyedinek kell lennie a \"{date_field}\" hónapra." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "A mezőnek egyedinek kell lennie a \"{date_field}\" évre." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." -msgstr "Érvénytelen verzió az \"Accept\" fejlécmezőben." +msgstr "Érvénytelen verzió az \"Accept\" fejlécben." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Érvénytelen verzió az URL elérési útban." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "Érvénytelen verzió az URL elérési útban. Nem illeszkedik egyetlen verzió névtérre sem." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Érvénytelen verzió a hosztnévben." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Érvénytelen verzió a lekérdezési paraméterben." - -#: views.py:88 -msgid "Permission denied." -msgstr "" diff --git a/rest_framework/locale/hy/LC_MESSAGES/django.mo b/rest_framework/locale/hy/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..c5f3ebcfaa1efa88d9c0bbd02b03d659b1ae662c GIT binary patch literal 12885 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 Z>1&, 2019 +msgid "" +msgstr "" +"Project-Id-Version: Django REST framework\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" +"Language-Team: Armenian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/hy/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: hy\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: authentication.py:70 +msgid "Invalid basic header. No credentials provided." +msgstr "Անվավեր վերնագիր: Նույնականացման տվյալները տրամադրված չեն:" + +#: authentication.py:73 +msgid "Invalid basic header. Credentials string should not contain spaces." +msgstr "Անվավեր վերնագիր: Նույնականացման տողը չպետք է պարունակի բացատներ:" + +#: authentication.py:83 +msgid "Invalid basic header. Credentials not correctly base64 encoded." +msgstr "Անվավեր վերնագիր: Նույնականացման տվյալները սխալ են ձեւակերպված base64-ում:" + +#: authentication.py:101 +msgid "Invalid username/password." +msgstr "Անվավեր օգտանուն / գաղտնաբառ:" + +#: authentication.py:104 authentication.py:206 +msgid "User inactive or deleted." +msgstr "Օգտատերը ջնջված է կամ ոչ ակտիվ:" + +#: authentication.py:184 +msgid "Invalid token header. No credentials provided." +msgstr "Անվավեր տոկենի վերնագիր: Նույնականացման տվյալները տրամադրված չեն:" + +#: authentication.py:187 +msgid "Invalid token header. Token string should not contain spaces." +msgstr "Անվավեր տոկենի վերնագիր: Տոկենի տողը չպետք է պարունակի բացատներ:" + +#: authentication.py:193 +msgid "" +"Invalid token header. Token string should not contain invalid characters." +msgstr "Անվավեր տոկենի վերնագիր: Տոկենի տողը չպետք է պարունակի անթույլատրելի նիշեր:" + +#: authentication.py:203 +msgid "Invalid token." +msgstr "Անվավեր տոկեն։" + +#: authtoken/apps.py:7 +msgid "Auth Token" +msgstr "Վավերացման Տոկեն։" + +#: authtoken/models.py:13 +msgid "Key" +msgstr "Բանալի" + +#: authtoken/models.py:16 +msgid "User" +msgstr "Օգտատեր" + +#: authtoken/models.py:18 +msgid "Created" +msgstr "Ստեղծված է" + +#: authtoken/models.py:27 authtoken/serializers.py:19 +msgid "Token" +msgstr "Տոկեն" + +#: authtoken/models.py:28 +msgid "Tokens" +msgstr "Տոկեններ" + +#: authtoken/serializers.py:9 +msgid "Username" +msgstr "Օգտանուն" + +#: authtoken/serializers.py:13 +msgid "Password" +msgstr "Գաղտնաբառ" + +#: authtoken/serializers.py:35 +msgid "Unable to log in with provided credentials." +msgstr "Անհնար է մուտք գործել տրամադրված նույնականացման տվյալներով:" + +#: authtoken/serializers.py:38 +msgid "Must include \"username\" and \"password\"." +msgstr "Պետք է ներառի «օգտանուն» եւ «գաղտնաբառ»:" + +#: exceptions.py:102 +msgid "A server error occurred." +msgstr "Տեղի ունեցել սերվերի սխալ:" + +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 +msgid "Malformed request." +msgstr "Սխալ հարցում:" + +#: exceptions.py:167 +msgid "Incorrect authentication credentials." +msgstr "Սխալ նույնականացման տվյալներ։" + +#: exceptions.py:173 +msgid "Authentication credentials were not provided." +msgstr "Նույնականացման տվյալները տրամադրված չեն:" + +#: exceptions.py:179 +msgid "You do not have permission to perform this action." +msgstr "Այս գործողությունը կատարելու թույլտվություն չունեք:" + +#: exceptions.py:185 +msgid "Not found." +msgstr "Չի գտնվել։" + +#: exceptions.py:191 +#, python-brace-format +msgid "Method \"{method}\" not allowed." +msgstr "\"{method}\" մեթոդը թույլատրված չէ:" + +#: exceptions.py:202 +msgid "Could not satisfy the request Accept header." +msgstr "Չհաջողվեց բավարարել հարցման Ընդունել վերնագիրը:" + +#: exceptions.py:212 +#, python-brace-format +msgid "Unsupported media type \"{media_type}\" in request." +msgstr "Անհամապատասխան մեդիա տիպ \"{media_type}\":" + +#: exceptions.py:223 +msgid "Request was throttled." +msgstr "Հարցումն ընդհատվել է:" + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "Այս դաշտը պարտադիր է:" + +#: fields.py:317 +msgid "This field may not be null." +msgstr "Այս դաշտը չի կարող զրոյական լինել:" + +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 +msgid "This field may not be blank." +msgstr "Այս դաշտը չի կարող դատարկ լինել:" + +#: fields.py:768 fields.py:1881 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} characters." +msgstr "Համոզվեք, որ այս դաշտը լինի ոչ ավել, քան {max_length} նիշ:" + +#: fields.py:769 +#, python-brace-format +msgid "Ensure this field has at least {min_length} characters." +msgstr "Համոզվեք, որ այս դաշտը ունի առնվազն {min_length} նիշ:" + +#: fields.py:816 +msgid "Enter a valid email address." +msgstr "Մուտքագրեք վավեր էլ.փոստի հասցե:" + +#: fields.py:827 +msgid "This value does not match the required pattern." +msgstr "Այս արժեքը չի համապատասխանում պահանջվող օրինակին:" + +#: fields.py:838 +msgid "" +"Enter a valid \"slug\" consisting of letters, numbers, underscores or " +"hyphens." +msgstr "Մուտքագրեք վավեր \"slug\", որը բաղկացած է տառերից, թվերից, ընդգծումից կամ դեֆիսից:" + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 +msgid "Enter a valid URL." +msgstr "Մուտքագրեք վավեր հղում:" + +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" + +#: fields.py:903 +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "Մուտքագրեք վավեր IPv4 կամ IPv6 հասցե:" + +#: fields.py:931 +msgid "A valid integer is required." +msgstr "Պահանջվում է լիարժեք ամբողջական թիվ:" + +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format +msgid "Ensure this value is less than or equal to {max_value}." +msgstr "Համոզվեք, որ արժեքը փոքր է կամ հավասար {max_value} -ին։" + +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format +msgid "Ensure this value is greater than or equal to {min_value}." +msgstr "Համոզվեք, որ արժեքը մեծ է կամ հավասար {min_value} -ին։" + +#: fields.py:934 fields.py:971 fields.py:1010 +msgid "String value too large." +msgstr "Տողը ունի չափազանց մեծ արժեք:" + +#: fields.py:968 fields.py:1004 +msgid "A valid number is required." +msgstr "Պահանջվում է վավեր համար:" + +#: fields.py:1007 +#, python-brace-format +msgid "Ensure that there are no more than {max_digits} digits in total." +msgstr "Համոզվեք, որ {max_digits} -ից ավել թվանշան չկա:" + +#: fields.py:1008 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_decimal_places} decimal places." +msgstr "Համոզվեք, որ {max_decimal_places} -ից ավել տասնորդական նշան չկա:" + +#: fields.py:1009 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_whole_digits} digits before the " +"decimal point." +msgstr "Համոզվեք, որ տասնորդական կետից առաջ չկա {max_whole_digits} -ից ավել թվանշան:" + +#: fields.py:1148 +#, python-brace-format +msgid "Datetime has wrong format. Use one of these formats instead: {format}." +msgstr "Datetime -ը սխալ ձեւաչափ ունի: Փոխարենը օգտագործեք այս ձեւաչափերից մեկը. {format} ։" + +#: fields.py:1149 +msgid "Expected a datetime but got a date." +msgstr "Ակնկալվում է օրվա ժամը, սակայն ամսաթիվ է ստացել:" + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "Ամսաթիվն ունի սխալ ձեւաչափ: Փոխարենը օգտագործեք այս ձեւաչափերից մեկը. {format} ։" + +#: fields.py:1237 +msgid "Expected a date but got a datetime." +msgstr "Ակնկալվում է ամսաթիվ, սակայն ստացել է օրվա ժամը:" + +#: fields.py:1303 +#, python-brace-format +msgid "Time has wrong format. Use one of these formats instead: {format}." +msgstr "Ժամանակը սխալ ձեւաչափ ունի: Փոխարենը օգտագործեք այս ձեւաչափերից մեկը. {format} ։" + +#: fields.py:1365 +#, python-brace-format +msgid "Duration has wrong format. Use one of these formats instead: {format}." +msgstr "Տեւողությունը սխալ ձեւաչափ ունի: Փոխարենը օգտագործեք այս ձեւաչափերից մեկը. {format} ։" + +#: fields.py:1399 fields.py:1456 +#, python-brace-format +msgid "\"{input}\" is not a valid choice." +msgstr "\"{input}\" -ը վավեր ընտրություն չէ:" + +#: fields.py:1402 +#, python-brace-format +msgid "More than {count} items..." +msgstr "Ավելի քան {count} առարկա..." + +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format +msgid "Expected a list of items but got type \"{input_type}\"." +msgstr "Ակնկալվում է տարրերի ցանկ, բայց ստացել է \"{input_type}\" -ի տիպ:" + +#: fields.py:1458 +msgid "This selection may not be empty." +msgstr "Այս ընտրությունը չի կարող դատարկ լինել:" + +#: fields.py:1495 +#, python-brace-format +msgid "\"{input}\" is not a valid path choice." +msgstr "\"{input}\" -ը վավեր ճանապարհի ընտրություն չէ:" + +#: fields.py:1514 +msgid "No file was submitted." +msgstr "Ոչ մի ֆայլ չի ուղարկվել:" + +#: fields.py:1515 +msgid "" +"The submitted data was not a file. Check the encoding type on the form." +msgstr "Ուղարկված տվյալը ֆայլ չէ: Ստուգեք կոդավորման տիպը:" + +#: fields.py:1516 +msgid "No filename could be determined." +msgstr "Ֆայլի անունը հնարավոր չէ որոշվել:" + +#: fields.py:1517 +msgid "The submitted file is empty." +msgstr "Ուղարկված ֆայլը դատարկ է:" + +#: fields.py:1518 +#, python-brace-format +msgid "" +"Ensure this filename has at most {max_length} characters (it has {length})." +msgstr "Համոզվեք, որ այս ֆայլի անունը ունի առավելագույնը {max_length} նիշ, (այն ունի {length})։" + +#: fields.py:1566 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "Վերբեռնեք վավեր նկար: Ձեր բեռնած ֆայլը կամ նկար չէ կամ վնասված նկար է:" + +#: fields.py:1604 relations.py:486 serializers.py:571 +msgid "This list may not be empty." +msgstr "Այս ցանկը չի կարող դատարկ լինել:" + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format +msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "Ակնկալվում է տարրերի բառարան, բայց ստացել է \"{input_type}\" տիպ։" + +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 +msgid "Value must be valid JSON." +msgstr "Արժեքը պետք է լինի JSON:" + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Որոնում" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Պատվիրել" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 +msgid "ascending" +msgstr "աճման կարգով" + +#: filters.py:288 +msgid "descending" +msgstr "նվազման կարգով" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 +msgid "Invalid page." +msgstr "Անվավեր էջ:" + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "Սխալ կուրսոր" + +#: relations.py:246 +#, python-brace-format +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "Անվավեր pk \"{pk_value}\" օբյեկտը գոյություն չունի:" + +#: relations.py:247 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "Անհայտ տիպ: Ակնկալվում է pk արժեք, ստացված է {data_type}։" + +#: relations.py:280 +msgid "Invalid hyperlink - No URL match." +msgstr "Անվավեր հղում - Ոչ մի հղման համընկնում:" + +#: relations.py:281 +msgid "Invalid hyperlink - Incorrect URL match." +msgstr "Անվավեր հղում - սխալ հղման համընկնում:" + +#: relations.py:282 +msgid "Invalid hyperlink - Object does not exist." +msgstr "Անվավեր հղում - տվյալ օբյեկտը գոյություն չունի:" + +#: relations.py:283 +#, python-brace-format +msgid "Incorrect type. Expected URL string, received {data_type}." +msgstr "Անվավեր տիպ: Սպասվում է հղման տողը, ստացել է {data_type}։" + +#: relations.py:448 +#, python-brace-format +msgid "Object with {slug_name}={value} does not exist." +msgstr "{slug_name}={value} տվյալ պարունակությամբ օբյեկտ գոյություն չունի:" + +#: relations.py:449 +msgid "Invalid value." +msgstr "Անվավեր արժեք:" + +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "Անվավեր տվյալներ: Սպասվում է բառարան, բայց ստացվել է {datatype}։" + +#: templates/rest_framework/admin.html:116 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 +msgid "Filters" +msgstr "Ֆիլտրեր" + +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" + +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" + +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "Ոչ մեկը" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "Ոչ մի տարր ընտրված չէ։" + +#: validators.py:39 +msgid "This field must be unique." +msgstr "Այս դաշտը պետք է լինի եզակի:" + +#: validators.py:89 +#, python-brace-format +msgid "The fields {field_names} must make a unique set." +msgstr "{field_names} դաշտերը պետք է կազմեն եզակի հավաքածու:" + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" date." +msgstr "Այս դաշտը պետք է եզակի լինի \"{date_field}\" ամսաթվի համար:" + +#: validators.py:258 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" month." +msgstr "Այս դաշտը պետք է եզակի լինի \"{date_field}\" ամսվա համար:" + +#: validators.py:271 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" year." +msgstr "Այս դաշտը պետք է եզակի լինի \"{date_field}\" տարվա համար:" + +#: versioning.py:40 +msgid "Invalid version in \"Accept\" header." +msgstr "\"Accept\" վերնագրի անվավեր տարբերակ:" + +#: versioning.py:71 +msgid "Invalid version in URL path." +msgstr "Անվավեր տարբերակ հղման ճանապարհի մեջ:" + +#: versioning.py:116 +msgid "Invalid version in URL path. Does not match any version namespace." +msgstr "Անվավեր տարբերակ հղման ճանապարհի մեջ: Չի համապատասխանում որեւէ անվանման տարբերակի:" + +#: versioning.py:148 +msgid "Invalid version in hostname." +msgstr "Անվավեր տարբերակ անվանման մեջ:" + +#: versioning.py:170 +msgid "Invalid version in query parameter." +msgstr "Անվավեր տարբերակ `հարցման պարամետրում:" diff --git a/rest_framework/locale/id/LC_MESSAGES/django.mo b/rest_framework/locale/id/LC_MESSAGES/django.mo index 471d5a830e78ff06fb04917d914030cf36639ad5..c67e2a1da9e52599dd32d35227af0187b2ec1b12 100644 GIT binary patch literal 5188 zcmbuCO^h5z6~~(-1ZN=u41}+Q6q5jUyfeFNN5&ar$LqvS{JpZ*7I0bBOxMhm`_tXk z`@zcMVid)lr?T7f``uD>@puo4lPk;v>sn;C==NP{Rw!s_VJoruU zEci?CJor|FC*B(buQL8Rcn17Ecndu6Xb^l6Tmw&nKLjsK_n1PGn>)@xr-+^BM{|P<`KK58`zXEv`90BG13!w0D3H&%X zYR2CHKh5~3paS0j#m~QjxDq@K8JHYA3myTV2baKGAQS}O20saY50vlytQr3bJj(cw z;E&<{zu;lUKl(t;_isVr_ix~b!MVqS;IrUU;EUjCQ0&+Qg`eMm%iw`0Y923wqW=fr zY48tVTjoDl^LrfZGrj|!0RIe%-X}gpOu^ScdH)me^D+-EfNz76C-WF3JlzJ5gWm$5 z1K$9J?>E7x!AA+!$H3=6TnxJ4XTWnHCIulVddlYgcR=Cs=b-TZ7w`=DA5i!{_K_N2 z1ZCYz;7ecx3ctSq4}rf1p(OY_D7^g(6gwaPXb?cR_ety(8{~S3Tgd|#OT7;wFBc*{ z_e8(=GO%LODaw1vrP*#? z{=Y0MbQ5hpn`Hj0FuGS#9kOH^=504~hj-dR_E$!-epndtu#l*wiY>w>&@n{T?)YUS z+9V`MOhjW-mix(J&{R%zk9TFOjH_Ww^|Q3JrL!bGNgf*~G|j8DN_zgGO4%P}pD2`K z(P%4Y3wj=Bm0eg_e?g|f<4T7itM{zAdHu53J}YJtZR45so4FxOAvT7#FMC`UTVj@V zCMi{~a%xCjaa*}jO|@B*!QECTxR7FFfw5_0S=qmYDR*_Ms1J)Wz|b8 z6eE6>j|?*C?F1{0&fq!$m*ON#-KfZ%i)HC5=|7RIl@WTA` zQ_rYgtkY-5Yb~8}X)nr5xI@eYP5w?bWQS=2ot?ubXu%l5IJgy6WiS z(Z%)>f5&+2E*?9w$TfGFV%J_RbXvyRWkpwgS+84m`L!aLPP*zs8gj~&ma21T#CqlfGJ)#+|ykdcJ*P zx=w5vn4*1-jUH02y6QyFx``)UWy86Zn8Pz}pC=w=SEachmZz3Za-7M`sl}6X!L(0t zHq;U2hjOE1o$xzwx=^`|Ed`jJV2ay1v}(vKrNK7+3wzgMs_G$5*|e#pxEB=#$`nZ z(z!P-H)%Dpoj{!Dg~J5wRwcHNI_lLb)hMoq{JN@XWyEM3Rc^ME*`)$6sYnqb51=fB z{}Kk@g*huamDHoK4kI}kFgX@yhAPSu3V@GdkM3c_`PH){;#k;yyBjIBv%2mpS~oPi zMSM)}`^_DV3?~R)&twWwm40hygA&o7L>!EJR?ixkP_po@@od~>Ohw18QO(FfQ;=!* zwX@OCQTqu>evP{oHT}jeXX>=CNhhTxLHKr)u)YNw_a6HSM&yLVb+6=1*K(=hMl}r! zJ9A4mtYkfug_cXOctTVYKHuh-t2+u^2W@hZ?{gyTQ>pth+YWHQQsV37-Xr6*1M(=KY_EeL4lLgmk zX`-=}c|qFIO+{vFXY12c5oxhRJH<_6NHrM_sTp-}BB@s;6plCWu0_J|xZ`PRVQ6hh zHCmBcI>|;;j5p}0iYxyw(|G@eQv)tdzBXG1eZ^zrv%PJP6{7uYo#jzh@=qJX-37TZ zV&?tqhT2DLcG?S%>1#=EL^J38k$jpdI%7Re*?jkm$)5=VckM4%kHa{CMF>#BCG7zXw$0H;Tok)M?SatM}#vOg1uw^1E8}V;s5{u delta 122 zcmX@2ah2KPo)F7a1|VPrVi_P-0b*t#)&XJ=umIxOKuJp=4N?OGlmBpq@fn)w8kp-E v8Yvi>S{a&5PT&?-2+7FLO)OS$&M3+(F3C()uqnyUbpYaIsKDmw+)RuBT7Vdw diff --git a/rest_framework/locale/id/LC_MESSAGES/django.po b/rest_framework/locale/id/LC_MESSAGES/django.po index c84add0a4c..b3d3c2e894 100644 --- a/rest_framework/locale/id/LC_MESSAGES/django.po +++ b/rest_framework/locale/id/LC_MESSAGES/django.po @@ -3,13 +3,17 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Aldiantoro Nugroho , 2017 +# aslam hadi , 2017 +# Joseph Aditya P G, 2019 +# Xavier Ordoquy , 2020 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2016-07-12 15:14+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 20:03+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Indonesian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/id/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -17,423 +21,556 @@ msgstr "" "Language: id\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "" -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "" -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "" -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." -msgstr "" +msgstr "Nama pengguna atau kata sandi salah." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." -msgstr "" +msgstr "Pengguna tidak akfif atau telah dihapus." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "" -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "" -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "" -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "" #: authtoken/apps.py:7 msgid "Auth Token" -msgstr "" +msgstr "Token Autentikasi" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "Pengguna" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" -msgstr "" +msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" -msgstr "" +msgstr "Token" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "Nama pengguna" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" - -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "" +msgstr "Kata sandi" -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." -msgstr "" +msgstr "Tidak dapat masuk dengan data pengguna ini." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." -msgstr "" +msgstr "Nama pengguna dan password harus diisi." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." +msgstr "Terjadi galat di server." + +#: exceptions.py:142 +msgid "Invalid input." msgstr "" -#: exceptions.py:84 +#: exceptions.py:161 msgid "Malformed request." msgstr "" -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." -msgstr "" +msgstr "Data autentikasi salah." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." -msgstr "" +msgstr "Data autentikasi tidak diberikan." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." -msgstr "" +msgstr "Anda tidak memiliki izin untuk melakukan tindakan ini." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." -msgstr "" +msgstr "Data tidak ditemukan." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "" -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." -msgstr "" +msgstr "Permintaan dengan header Accept ini tidak dapat dipenuhi." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." -msgstr "" +msgstr "Jenis media \"{media_type}\" dalam permintaan ini tidak didukung." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." +msgstr "Permintaan ini telah dibatasi." + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." msgstr "" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 -msgid "This field is required." +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." msgstr "" -#: fields.py:270 +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "Bidang ini harus diisi." + +#: fields.py:317 msgid "This field may not be null." +msgstr "Bidang ini tidak boleh diisi dengan \"null\"." + +#: fields.py:701 +msgid "Must be a valid boolean." msgstr "" -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." +#: fields.py:766 +msgid "Not a valid string." msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." -msgstr "" +msgstr "Bidang ini tidak boleh kosong." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." -msgstr "" +msgstr "Isi bidang ini tidak boleh melebihi {max_length} karakter." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." -msgstr "" +msgstr "Isi bidang ini minimal {min_length} karakter." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." -msgstr "" +msgstr "Masukkan alamat email dengan format yang benar." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "" -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." +msgstr "Karakter \"slug\" hanya dapat terdiri dari huruf, angka, underscore dan tanda hubung." + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." msgstr "" -#: fields.py:747 +#: fields.py:854 msgid "Enter a valid URL." -msgstr "" +msgstr "Masukkan URL dengan format yang benar." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." +#: fields.py:867 +msgid "Must be a valid UUID." msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "" +msgstr "Masukkan alamat IPv4 atau IPv6 dengan format yang benar." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." -msgstr "" +msgstr "Nilai bidang ini harus berupa bilangan bulat." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." -msgstr "" +msgstr "Isi bidang ini harus kurang atau sama dengan {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." -msgstr "" +msgstr "Isi bidang ini harus lebih atau sama dengan {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "" -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "" -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." -msgstr "" +msgstr "Panjang angka tidak boleh lebih dari {max_digits}." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." -msgstr "" +msgstr "Panjang angka di belakang koma tidak boleh lebih dari {max_decimal_places}." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." -msgstr "" +msgstr "Panjang angka bulat tidak boleh lebih dari {max_whole_digits}." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "Format tanggal dan waktu salah. Gunakan salah satu format berikut: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "" -#: fields.py:1103 -msgid "Date has wrong format. Use one of these formats instead: {format}." +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." msgstr "" -#: fields.py:1104 +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "Format tanggal salah. Gunakan salah satu format berikut: {format}." + +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "" -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "Format waktu salah. Gunakan salah satu format berikut: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "Format durasi salah. Gunakan salah satu format berikut: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." -msgstr "" +msgstr "\"{input}\" tidak ada dalam daftar pilihan." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "" -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." -msgstr "" +msgstr "Pilihan tidak boleh kosong." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." -msgstr "" +msgstr "Pilih berkas terlebih dahulu." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "" -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "" -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." -msgstr "" +msgstr "Berkas kosong." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." -msgstr "" +msgstr "Panjang nama berkas tidak boleh lebih dari {max_length}. Panjang nama berkas ini {length} karakter." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "" -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "Isi bidang ini harus berupa dictionary, bukan \"{input_type}\"." + +#: fields.py:1683 +msgid "This dictionary may not be empty." msgstr "" -#: fields.py:1549 +#: fields.py:1755 msgid "Value must be valid JSON." +msgstr "Isi bidang ini harus berupa JSON." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" msgstr "" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "" +msgstr "Objek dengan primary key \"{pk_value}\" tidak ditemukan." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "" -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "" -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "" -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "" -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "" -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "" -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "" -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "" #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "" -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "" -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "" -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "" -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "" -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "" -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "" -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "" -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "" - -#: views.py:88 -msgid "Permission denied." -msgstr "" diff --git a/rest_framework/locale/it/LC_MESSAGES/django.mo b/rest_framework/locale/it/LC_MESSAGES/django.mo index 1d4fd34c38965b195e1c86708ecd37ea7e246b8c..6c84273a9228807bff9e2698eaf72d15892722cc 100644 GIT binary patch delta 2662 zcmYk+X>6259LMqLfvvQKQd&wu;Nh?>S1E-8y`ZJstu1Y7X%&UX?qj=BcFXRzoVp1f z1R^MTg80S|5s!c->WVK^Vzeac3$GXxhz3uLFEk)vLV`rUzh}3BNoPMZ&-3(oX8!Y^ z`Evc0mgvQt)T4&7g?f)_iZM^fyZE6LryFD8OuQG@;dtyvJ%0cv;;T3kKf$^91(6&f{InH_)OPMko0f1-Z^HM6%-Gx`n{ z%BzWf`Iz|qR!rggASwd8aWWo4z3&Yy!cTD=UdE_J<1U^ai$$1ex z6@kroJjs|xQA;qA)SrPR$mC2LDrX)+h58xP|DDDq_#SGYmr;=_<6~>E0w2JAQ5x%L zTuj`U$wJl951}4-7i%$_Fs#HD^x-j7&isp2SV>%4a1dwVXQ+^0Lk&1%63K?833p>F z{pdLwjWqHZzDDT7nYb0#<59d1f5!#5a7uh_dr={N3l)i9P&4}nYjCL*kHB8klAS?4 zcL~ez2A1=EGn>6~KNoD&3_e6Qgc&u>7~W+jqyBLzYG5s>EZ&0~@a07R25MVbtdn*@ zGqTL44K4H|!88X^5qibD9yR7`8gseuH|m2^h*ugeL(T}Z8g+jIa@LrAsN^|}O15LD zBpgD0@N=xdAMs%s7Z=1()A)Z8K@deb3f5Rk9 zp(`fq>s17x8P=~CP0ZA4l%Ek^E-yRiQ3^^iT^NY zFVQqG4a%abC}&nul`~pbO<*InfvTc`tF%$my_PX6kW<<#)%?!U`G04rqtlhRplm17 zrjxpqnoCvD2}eN8gH(l6OQ($Hi12m_cnRE!3cHr>A*!~Y@<3$`wU(Nx?@y&FBUO|b zOLe-~#w_7r@(v2{ ziV{g#I$U%HEK78faXB@O>fTJ7;9gHFaqH4M(ya}_txmwyxlLIG?wjd#Y4st;jyS&9 zU+K$JV>MY5l2hy+C)PN6M^Z_9Yopa~M|w-F6+tI#1%eT4n;q%ywd}wyYtRXW{lS10 zu(vtke!JUok7Vb$L)j%M4bHCEmF&!yL5e)g`ck_w01j z9(F>OKVWx9{DY1a3|T&>&tW(v-W|Q$J)GB+Ylpj?fX^T3F+OLw>3*NrF{-R|?tK0h zSo13?7nZxr#^qa`_Mo4aHivw{?E|~4>YWTht=c|+AUNo^V}s)glH3pS7r8m(J4efasA^T%kZKRG>x|9bB`3PjLj?fBQ-XA^4{cZyN6*CgMc+?_xXKx?4)%rsk*P| zc95F5*BuOn{Nes!z~|5=I!-t|a7U0VpMN-Vj@vw~%L~?P(`s%9>)P~nvHBT*CZ+3B z{R2bqxK)ML?M_eOp{#fq*CvL2t?=wDh0(M}oIu1;G$TYaXay~&&%aIG;2q(-TC^!Y O9!@=Ne10#UUi*I;8g-EX delta 2508 zcmZwHTWnNC7{Kw_x}`0*+9K^%DIHpRlXlsbOSu$iX(`Yma%+J$QMT+m?P#4Ff=4SAj^^#qO8419_$m@`qS9$bS>IEG8{9CqQfNlGojM^S!%71!Zc zSb=4el`6zGlnM2qj3;~#pgiv*Tp&yRjm8uP>iNf_)KZicwxPVp#6|cPuD}b}hNV-L z3S$)cQ!nsOt{cM|JdYB&K)zBBVg*KUJ%%xbYbDh0(O8DRBloC<_bPQiu0rYWKv{u} zvLe^_h(CTBWyM!fQW7l4++Ts!^p|51?nI6Ia2mdjsUVH_X%JoY0nWt}NSf3yC=dP< z>juHA@D5)GpndnKB>%RB*LFkPt5jcpllA|aSyoC~hWjd3R5tPurjSKJ_?8W?9nFZ`d z_O%*EZdIpHo_iUYP)gmRA-lA;JmW@`2J)w- zutDU-jkp9i`R+%#|8xKMtg6fc%TYF8CobguYJ^4yzKe3hb(9OjgrysMk!PqQNV?To zzkdzq(=VxE=i}`n>69X2mjy|>R#8_|=78)^O63T`PB+dnR{lI+E8KoMglX^-eS981Y2fno^pTekDH^aMu)s(@zyl z3KSUeAv2LMZA%XtmT3%z-RBCMCTvdj51CH-MB(-qGG*empv3s z=(b&PGvSzqZg%XU7V3aMkPa4?1VWim*n%B5ZimDUyLq9`(B0Kr%=@!LI%#Qd;hmwV zdo(nfUR=_UU8IL)b}>B=9U8V(2O)@?s?9wUnvs5H<|A3@8~S*_&97)lZ?0$#, 2015 +# Marco Ventura, 2019 # Mattia Procopio , 2015 +# Riccardo Magliocchetti , 2019 # Sergio Morstabilini , 2015 # Xavier Ordoquy , 2015 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Italian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/it/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -21,423 +23,556 @@ msgstr "" "Language: it\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Header di base invalido. Credenziali non fornite." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Header di base invalido. Le credenziali non dovrebbero contenere spazi." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Credenziali non correttamente codificate in base64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Nome utente/password non validi" -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Utente inattivo o eliminato." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Header del token non valido. Credenziali non fornite." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Header del token non valido. Il contenuto del token non dovrebbe contenere spazi." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Header del token invalido. La stringa del token non dovrebbe contenere caratteri illegali." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Token invalido." #: authtoken/apps.py:7 msgid "Auth Token" -msgstr "" +msgstr "Auth Token" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" -msgstr "" +msgstr "Key" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "User" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" -msgstr "" +msgstr "Creato" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" -msgstr "" +msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" -msgstr "" +msgstr "I Token" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "Username" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" +msgstr "Password" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "L'account dell'utente è disabilitato" - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Impossibile eseguire il login con le credenziali immesse." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Deve includere \"nome utente\" e \"password\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Errore del server." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Richiesta malformata." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Credenziali di autenticazione incorrette." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Non sono state immesse le credenziali di autenticazione." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Non hai l'autorizzazione per eseguire questa azione." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Non trovato." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Metodo \"{method}\" non consentito" -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Impossibile soddisfare l'header \"Accept\" presente nella richiesta." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Tipo di media \"{media_type}\"non supportato." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "La richiesta è stata limitata (throttled)." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Campo obbligatorio." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Il campo non può essere nullo." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" non è un valido valore booleano." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." msgstr "Questo campo non può essere omesso." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Assicurati che questo campo non abbia più di {max_length} caratteri." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Assicurati che questo campo abbia almeno {min_length} caratteri." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Inserisci un indirizzo email valido." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Questo valore non corrisponde alla sequenza richiesta." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Immetti uno \"slug\" valido che consista di lettere, numeri, underscore o trattini." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Inserisci un URL valido" -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" non è un UUID valido." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Inserisci un indirizzo IPv4 o IPv6 valido." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "È richiesto un numero intero valido." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Assicurati che il valore sia minore o uguale a {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Assicurati che il valore sia maggiore o uguale a {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Stringa troppo lunga." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "È richiesto un numero valido." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Assicurati che non ci siano più di {max_digits} cifre in totale." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Assicurati che non ci siano più di {max_decimal_places} cifre decimali." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Assicurati che non ci siano più di {max_whole_digits} cifre prima del separatore decimale." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "L'oggetto di tipo datetime è in un formato errato. Usa uno dei seguenti formati: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Atteso un oggetto di tipo datetime ma l'oggetto ricevuto è di tipo date." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "La data è in un formato errato. Usa uno dei seguenti formati: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Atteso un oggetto di tipo date ma l'oggetto ricevuto è di tipo datetime." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "L'orario ha un formato errato. Usa uno dei seguenti formati: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "La durata è in un formato errato. Usa uno dei seguenti formati: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" non è una scelta valida." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Più di {count} oggetti..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Attesa una lista di oggetti ma l'oggetto ricevuto è di tipo \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." -msgstr "Questa selezione potrebbe non essere vuota." +msgstr "Questa selezione non può essere vuota." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" non è un percorso valido." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Non è stato inviato alcun file." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "I dati inviati non corrispondono ad un file. Si prega di controllare il tipo di codifica nel form." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Il nome del file non può essere determinato." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Il file inviato è vuoto." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Assicurati che il nome del file abbia, al più, {max_length} caratteri (attualmente ne ha {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Invia un'immagine valida. Il file che hai inviato non era un'immagine o era corrotto." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." -msgstr "Questa lista potrebbe non essere vuota." +msgstr "Questa lista non può essere vuota." + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" -#: fields.py:1502 +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Era atteso un dizionario di oggetti ma il dato ricevuto è di tipo \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Il valore deve essere un JSON valido." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Invia" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Cerca" -#: filters.py:336 -msgid "ascending" +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:337 +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Ordinamento" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 +msgid "ascending" +msgstr "ascendente" + +#: filters.py:288 msgid "descending" +msgstr "discendente" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." msgstr "" -#: pagination.py:193 +#: pagination.py:189 msgid "Invalid page." +msgstr "Pagina non valida." + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." msgstr "" -#: pagination.py:427 +#: pagination.py:583 msgid "Invalid cursor" msgstr "Cursore non valido" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Pk \"{pk_value}\" non valido - l'oggetto non esiste." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Tipo non corretto. Era atteso un valore pk, ma è stato ricevuto {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Collegamento non valido - Nessuna corrispondenza di URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Collegamento non valido - Corrispondenza di URL non corretta." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Collegamento non valido - L'oggetto non esiste." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Tipo non corretto. Era attesa una stringa URL, ma è stato ricevuto {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "L'oggetto con {slug_name}={value} non esiste." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Valore non valido." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Dati non validi. Era atteso un dizionario, ma si è ricevuto {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtri" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Filtri per il campo" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Ordinamento" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Cerca" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Nessuno" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Nessun elemento da selezionare." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Questo campo deve essere unico." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "I campi {field_names} devono costituire un insieme unico." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Questo campo deve essere unico per la data \"{date_field}\"." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Questo campo deve essere unico per il mese \"{date_field}\"." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Questo campo deve essere unico per l'anno \"{date_field}\"." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Versione non valida nell'header \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Versione non valida nella sequenza URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "Versione non valida nell'URL path. Non corrisponde con nessun namespace di versione." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Versione non valida nel nome dell'host." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Versione non valida nel parametro della query." - -#: views.py:88 -msgid "Permission denied." -msgstr "Permesso negato." diff --git a/rest_framework/locale/ja/LC_MESSAGES/django.mo b/rest_framework/locale/ja/LC_MESSAGES/django.mo index 5b9dbd8da62bf95a71c3f4a6e24ce0ddcb47d3c2..e949a57f792568f3e4200b448b49a1f59b8b6bab 100644 GIT binary patch delta 2239 zcmYk-TWnNC9LMq5cH3?*T*~&gP-Kf}Tj}-!Y%kktE3{y{RSLGWwMx4I#cJ(J3l$+| zwJ5|(3A%yk111o_`-54Hi3w5B#Q0!hf+REnQXWW5#GuBQs4@8coik0Gbk1jH&Yn4Q z`OoRq;hRH=t4qAc3~7X1s+?}jL7B;gRN*rwh?RH;c3}xVjr#q|xD3x?4xYylevei7 z4`yLSwlV9m7B}LgYr=d^VG|7=o^HcV+=j1U175>s4CERU#XgMSN!0zz*o_5w#@voE zT!Js52Kc6J|I|8*>hE6+n}jh<3)~ULQ8Ro7^@0=Fh?lSx^A{Sk0k>c^9z*`jH(d0) z3s`}9i;OA5Fy4jzNEc=rYw;*n<5$?p`^~Qu?#9Ssx5It7l=@*?KZTmv$EX=yL@nj- zwjR!R?{CF)+NV$}upe*38B~8CVmW?`MR*evK?<4t+K=Vv#b#vHO&iu=6xl{|!1^Mp zgBetqoW^4O0BiAU)Rz8%8gL7p>;53Bz8e)YNBpdRJ%#saaP}Hi4-~i~?m_+VNetp) z)PUYat-vMBz-5K*K+146^$M)RUL3?jsEN&C0I#DabgPi{SA&n~tihE?Jeoe#3Jl|0 z9%G)sdg@+6U$M}P+S>yd!n4*Nkx830I@SF`+=6Y$E}J)SJD#$75^RhbM(|<$0;5>U zw0rS!d;rg3KjyK1L%0*e_@(tv+)uq~xiRhd4r%~5umy_=1WlmV`YNiw#CZx2QCPZy z&T%j5!D-xqAK@dIA2enZ<2Zt|sDVZZb-FNza1hU++HYY8MpwG^*O57xE7*m9AuE$G z?SyumhU54cX0CEOdcbL|5qregIvyrjz=q(Ox%S`!i-}h9<}Y?q4x3z)Bxvfdp?I>D^Y?S*nl(m94ba~ zE8Y7|$RlO|D|o+op27gWXH8@MRUbl){1emzKcimgC&aa)X=#)KXmrmzmjP)m3cwF1|yKcR7LfG<1#UB$ z_dBVHt5!xwe1tqq*2G8&v)C;-(aZ<8sF%P28mfMl9w3D=gRoegmr+cX=ij@=-god*f3YY@2l9rAwbxwsBBV>I$ipckq zl@uRa$$ezv+S#T~vv@wYZu*A!V7UR_Jd>5fhA3H46I#wW*O zd*Tz3;6Q9@^wEjn=82v0-Fu%6w(N_$rM9ur@%YqeER|i7?Me0o##6rq(y~&E%69mY RM=JYMH!3|D$?}@d{sZ9P-bMfb delta 2607 zcmZYAX>1f_9LMpecMFuGlwP!SED9Cd(gHm!&_X#&Yq?P<*S6ilgk{&Ttq@Q*v>wy~ zRyHL{AaWR^@Pfn|5Mo3zL1QHGh*F8m`Cs!TKG9WhSzZ%4oNX43m-?_Ux#CH0}jDm zSd6b=5q^kC_&e4a6EZi+%%`G=)-s3-@I21K*rCR(!Z}!phj2Q6hYdJZlE?@NKNcpKuOl zjWDJJ*CJJ>7uD_%7UK7)nY@paaNJ1xp?_0PrWBt>Jzx*k<5`@EF{7dnti>G4O>Wsn zjbsOEWQUO{HRs&&efRflh95`$Ow>%w=AxxrfgwFGNM<7L#|%7!7GB2DcpKv}mIA9{ zl5jGnA=|*zxi%n8%?i|WHleoBir~#CXiMDG*i}GgF3-+KhBbY$>KI(;HdAlbtC4>1dBU4VrYTSex>1UXY zmt22Cjr5*dj$=4eDCZ&j*DOKJ%o;oqV~iKIbSXTf0vDk6%8RJ`K0)n)ze8k-$>cH& zxd=6dJ5UXJaVefb_KitqI@V*B>vq>GxR&~IrcI~f0ItAm*o0H_*k9O%&G-wJW2l0i zpo|Z9;&GgddHK;7Xu%nj_n}7em1~R@eP9)?p}r4!2lFR#@=Ow2T?0tRR;fgY5c-1YBf7IO*xQO~*)C^t002Xpq+wo1*^Zr3? zws8}TVfoEsRN3noGUgx|-S`Dg#Y&DsEpEX?JcSy`2dEjiiW+G;hq?w!QA-y<-G2mW zV!lP)e-{}vpUCLsi;4MbUxS7}5<#$*AKmQEPn{*JA}wABO#|$5FqZM*V&Z_hAEH ztrYwjpTr32d3l^c-QR@Muo*S8eW)4uW-{xq%#T#Wpmu0A!RNpXB_1UGgHZ>(my?FL z3Qf6^HZfbsNqWf!LbITxSy)6Q5cvcX<$O10)4#KMC>u_MGrh_)Fy}aJ#03O1=ZpxP zZ@V^9Dlwm6e>rI`R~@-lLPvNZp|m)Pvv-o+JS*s=bz1)=Zb2WSId0xUCdE0cvDi*% zkE|lJ)=F^%-x+60bz+o85_}YmhiD^`2qkU6QBE#;@j^Ti+Q3C?^c11JppV6LqJ_|! ztt2Xm3B)pDCZSYA=-fY?W>Lr>IH^w39?|l0GM!Y%HP_ASKOyLV;xmEq|cH@>tJ#e0H1F5(xOb9)C%=FnM8K!7eI0z5j2u za%KJ80e^B_!cecT&1$!O9o}uh0e@^^vc@Kj=9W{D?w&|@f28Y#@?94OI`VzVi8tPR z|K?kL;iVHZ!&OD2!gnUlOT6)V-{7m~2Pz6<6LRRKi^lKLSEPGSWcS`bUOL8`4DLM` Mt}o6HR~CQtFR>O}8UO$Q diff --git a/rest_framework/locale/ja/LC_MESSAGES/django.po b/rest_framework/locale/ja/LC_MESSAGES/django.po index a5e72d9a13..6b8d71429b 100644 --- a/rest_framework/locale/ja/LC_MESSAGES/django.po +++ b/rest_framework/locale/ja/LC_MESSAGES/django.po @@ -9,9 +9,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Kouichi Nishizawa \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Japanese (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ja/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,40 +19,40 @@ msgstr "" "Language: ja\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "不正な基本ヘッダです。認証情報が含まれていません。" -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "不正な基本ヘッダです。認証情報文字列に空白を含めてはいけません。" -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "不正な基本ヘッダです。認証情報がBASE64で正しくエンコードされていません。" -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "ユーザ名かパスワードが違います。" -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "ユーザが無効か削除されています。" -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "不正なトークンヘッダです。認証情報が含まれていません。" -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "不正なトークンヘッダです。トークン文字列に空白を含めてはいけません。" -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "不正なトークンヘッダです。トークン文字列に不正な文字を含めてはいけません。" -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "不正なトークンです。" @@ -60,382 +60,515 @@ msgstr "不正なトークンです。" msgid "Auth Token" msgstr "認証トークン" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "キー" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "ユーザ" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "作成された" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "トークン" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "トークン" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "ユーザ名" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "パスワード" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "ユーザアカウントが無効化されています。" - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "提供された認証情報でログインできません。" -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "\"username\"と\"password\"を含まなければなりません。" -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "サーバエラーが発生しました。" -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "不正な形式のリクエストです。" -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "認証情報が正しくありません。" -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "認証情報が含まれていません。" -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "このアクションを実行する権限がありません。" -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "見つかりませんでした。" -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "メソッド \"{method}\" は許されていません。" -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "リクエストのAcceptヘッダを満たすことができませんでした。" -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "リクエストのメディアタイプ \"{media_type}\" はサポートされていません。" -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "リクエストの処理は絞られました。" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "この項目は必須です。" -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "この項目はnullにできません。" -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" は有効なブーリアンではありません。" +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "この項目は空にできません。" -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "この項目が{max_length}文字より長くならないようにしてください。" -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "この項目は少なくとも{min_length}文字以上にしてください。" -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "有効なメールアドレスを入力してください。" -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "この値は所要のパターンにマッチしません。" -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "文字、数字、アンダースコア、またはハイフンから成る有効な \"slug\" を入力してください。" -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "有効なURLを入力してください。" -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" は有効なUUIDではありません。" +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "有効なIPv4またはIPv6アドレスを入力してください。" -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "有効な整数を入力してください。" -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "この値は{max_value}以下にしてください。" -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "この値は{min_value}以上にしてください。" -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "文字列が長過ぎます。" -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "有効な数値を入力してください。" -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "合計で最大{max_digits}桁以下になるようにしてください。" -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "小数点以下の桁数を{max_decimal_places}を超えないようにしてください。" -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "整数部の桁数を{max_whole_digits}を超えないようにしてください。" -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "日時の形式が違います。以下のどれかの形式にしてください: {format}。" -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "日付ではなく日時を入力してください。" -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "日付の形式が違います。以下のどれかの形式にしてください: {format}。" -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "日時ではなく日付を入力してください。" -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "時刻の形式が違います。以下のどれかの形式にしてください: {format}。" -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "機関の形式が違います。以下のどれかの形式にしてください: {format}。" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\"は有効な選択肢ではありません。" -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr " {count} 個より多い..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "\"{input_type}\" 型のデータではなく項目のリストを入力してください。" -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "空でない項目を選択してください。" -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\"は有効なパスの選択肢ではありません。" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "ファイルが添付されていません。" -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "添付されたデータはファイルではありません。フォームのエンコーディングタイプを確認してください。" -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "ファイル名が取得できませんでした。" -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "添付ファイルの中身が空でした。" -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "ファイル名は最大{max_length}文字にしてください({length}文字でした)。" -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "有効な画像をアップロードしてください。アップロードされたファイルは画像でないか壊れた画像です。" -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "リストは空ではいけません。" -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "\"{input_type}\" 型のデータではなく項目の辞書を入力してください。" -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "値は有効なJSONでなければなりません。" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "提出" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "検索" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "順序" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "昇順" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "降順" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "不正なページです。" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "カーソルが不正です。" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "主キー \"{pk_value}\" は不正です - データが存在しません。" -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "不正な型です。{data_type} 型ではなく主キーの値を入力してください。" -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "ハイパーリンクが不正です - URLにマッチしません。" -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "ハイパーリンクが不正です - 不正なURLにマッチします。" -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "ハイパーリンクが不正です - リンク先が存在しません。" -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "不正なデータ型です。{data_type} 型ではなくURL文字列を入力してください。" -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "{slug_name}={value} のデータが存在しません。" -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "不正な値です。" -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "不正なデータです。{datatype} 型ではなく辞書を入力してください。" #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "フィルタ" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "フィールドフィルタ" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "順序" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "検索" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "なし" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "選択する項目がありません。" -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "この項目は一意でなければなりません。" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "項目 {field_names} は一意な組でなければなりません。" -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "この項目は \"{date_field}\" の日に対して一意でなければなりません。" -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "この項目は \"{date_field}\" の月に対して一意でなければなりません。" -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "この項目は \"{date_field}\" の年に対して一意でなければなりません。" -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "\"Accept\" 内のバージョンが不正です。" -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "URLパス内のバージョンが不正です。" -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "不正なバージョンのURLのパスです。どのバージョンの名前空間にも一致しません。" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "ホスト名内のバージョンが不正です。" -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "クエリパラメータ内のバージョンが不正です。" - -#: views.py:88 -msgid "Permission denied." -msgstr "権限がありません。" diff --git a/rest_framework/locale/ko_KR/LC_MESSAGES/django.mo b/rest_framework/locale/ko_KR/LC_MESSAGES/django.mo index 570ff6f538532729f73753ecc2bf9135aa3de88f..c3aeb27a96e5c3b3ef9e61ac79e9ad229463ec7c 100644 GIT binary patch delta 2349 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 GpZf<{iC`1} diff --git a/rest_framework/locale/ko_KR/LC_MESSAGES/django.po b/rest_framework/locale/ko_KR/LC_MESSAGES/django.po index 43dbe0cddb..722dc2c82e 100644 --- a/rest_framework/locale/ko_KR/LC_MESSAGES/django.po +++ b/rest_framework/locale/ko_KR/LC_MESSAGES/django.po @@ -4,15 +4,16 @@ # # Translators: # GarakdongBigBoy , 2017 +# Hochul Kwak , 2018 # Joon Hwan 김준환 , 2017 # SUN CHOI , 2015 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-09-28 09:41+0000\n" -"Last-Translator: GarakdongBigBoy \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Korean (Korea) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ko_KR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -20,423 +21,556 @@ msgstr "" "Language: ko_KR\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials)가 제공되지 않았습니다." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials) 문자열은 빈칸(spaces)을 포함하지 않아야 합니다." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "기본 헤더(basic header)가 유효하지 않습니다. 인증데이터(credentials)가 base64로 적절히 부호화(encode)되지 않았습니다." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "아이디/비밀번호가 유효하지 않습니다." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "계정이 중지되었거나 삭제되었습니다." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "토큰 헤더가 유효하지 않습니다. 인증데이터(credentials)가 제공되지 않았습니다." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 빈칸(spaces)을 포함하지 않아야 합니다." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "토큰 헤더가 유효하지 않습니다. 토큰 문자열은 유효하지 않은 문자를 포함하지 않아야 합니다." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "토큰이 유효하지 않습니다." #: authtoken/apps.py:7 msgid "Auth Token" -msgstr "" +msgstr "인증 토큰" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" -msgstr "" +msgstr "키" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "유저" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" -msgstr "" +msgstr "생성됨" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" -msgstr "" +msgstr "토큰" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "유저이름" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" +msgstr "비밀번호" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "사용자 계정을 사용할 수 없습니다." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "제공된 인증데이터(credentials)로는 로그인할 수 없습니다." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "\"아이디\"와 \"비밀번호\"를 포함해야 합니다." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "서버 장애가 발생했습니다." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "잘못된 요청입니다." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "자격 인증데이터(authentication credentials)가 정확하지 않습니다." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "자격 인증데이터(authentication credentials)가 제공되지 않았습니다." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "이 작업을 수행할 권한(permission)이 없습니다." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "찾을 수 없습니다." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "메소드(Method) \"{method}\"는 허용되지 않습니다." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Accept header 요청을 만족할 수 없습니다." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "요청된 \"{media_type}\"가 지원되지 않는 미디어 형태입니다." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "요청이 지연(throttled)되었습니다." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "이 필드는 필수 항목입니다." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "이 필드는 null일 수 없습니다." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\"이 유효하지 않은 부울(boolean)입니다." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "이 필드는 blank일 수 없습니다." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "이 필드의 글자 수가 {max_length} 이하인지 확인하십시오." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "이 필드의 글자 수가 적어도 {min_length} 이상인지 확인하십시오." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "유효한 이메일 주소를 입력하십시오." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "형식에 맞지 않는 값입니다." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "문자, 숫자, 밑줄( _ ) 또는 하이픈( - )으로 이루어진 유효한 \"slug\"를 입력하십시오." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "유효한 URL을 입력하십시오." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\"가 유효하지 않은 UUID 입니다." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "유효한 IPv4 또는 IPv6 주소를 입력하십시오." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "유효한 정수(integer)를 넣어주세요." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "이 값이 {max_value}보다 작거나 같은지 확인하십시오." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "이 값이 {min_value}보다 크거나 같은지 확인하십시오." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "문자열 값이 너무 큽니다." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "유효한 숫자를 넣어주세요." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "전체 숫자(digits)가 {max_digits} 이하인지 확인하십시오." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "소수점 자릿수가 {max_decimal_places} 이하인지 확인하십시오." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "소수점 자리 앞에 숫자(digits)가 {max_whole_digits} 이하인지 확인하십시오." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datetime의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "예상된 datatime 대신 date를 받았습니다." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Date의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "예상된 date 대신 datetime을 받았습니다." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Time의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Duration의 포멧이 잘못되었습니다. 이 형식들 중 한가지를 사용하세요: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\"이 유효하지 않은 선택(choice)입니다." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "" -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "아이템 리스트가 예상되었으나 \"{input_type}\"를 받았습니다." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "이 선택 항목은 비워 둘 수 없습니다." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\"이 유효하지 않은 경로 선택입니다." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "파일이 제출되지 않았습니다." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "제출된 데이터는 파일이 아닙니다. 제출된 서식의 인코딩 형식을 확인하세요." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "파일명을 알 수 없습니다." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "제출한 파일이 비어있습니다." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "이 파일명의 글자수가 최대 {max_length}를 넘지 않는지 확인하십시오. (이것은 {length}가 있습니다)." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "유효한 이미지 파일을 업로드 하십시오. 업로드 하신 파일은 이미지 파일이 아니거나 손상된 이미지 파일입니다." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "이 리스트는 비워 둘 수 없습니다." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "아이템 딕셔너리가 예상되었으나 \"{input_type}\" 타입을 받았습니다." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Value 는 유효한 JSON형식이어야 합니다." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "검색" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "순서" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" msgstr "오름차순" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "내림차순" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "페이지가 유효하지 않습니다." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "커서(cursor)가 유효하지 않습니다." -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "유효하지 않은 pk \"{pk_value}\" - 객체가 존재하지 않습니다." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "잘못된 형식입니다. pk 값 대신 {data_type}를 받았습니다." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "유효하지 않은 하이퍼링크 - 일치하는 URL이 없습니다." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "유효하지 않은 하이퍼링크 - URL이 일치하지 않습니다." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "유효하지 않은 하이퍼링크 - 객체가 존재하지 않습니다." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "잘못된 형식입니다. URL 문자열을 예상했으나 {data_type}을 받았습니다." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "{slug_name}={value} 객체가 존재하지 않습니다." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "값이 유효하지 않습니다." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "유효하지 않은 데이터. 딕셔너리(dictionary)대신 {datatype}를 받았습니다." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" +msgstr "필터" + +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:78 +msgid "request form" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "검색" +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "선택할 아이템이 없습니다." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "이 필드는 반드시 고유(unique)해야 합니다." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "필드 {field_names} 는 반드시 고유(unique)해야 합니다." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "이 필드는 고유(unique)한 \"{date_field}\" 날짜를 갖습니다." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "이 필드는 고유(unique)한 \"{date_field}\" 월을 갖습니다. " -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "이 필드는 고유(unique)한 \"{date_field}\" 년을 갖습니다. " -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "\"Accept\" 헤더(header)의 버전이 유효하지 않습니다." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "URL path의 버전이 유효하지 않습니다." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "URL 경로에 유효하지 않은 버전이 있습니다. 버전 네임 스페이스와 일치하지 않습니다." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "hostname내 버전이 유효하지 않습니다." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "쿼리 파라메터내 버전이 유효하지 않습니다." - -#: views.py:88 -msgid "Permission denied." -msgstr "사용 권한이 거부되었습니다." diff --git a/rest_framework/locale/lt/LC_MESSAGES/django.mo b/rest_framework/locale/lt/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..b43a6a8a4dfe2dd8777393c186c605423e76089c GIT binary patch literal 5056 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, 2019 +msgid "" +msgstr "" +"Project-Id-Version: Django REST framework\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" +"Language-Team: Lithuanian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/lt/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: lt\n" +"Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);\n" + +#: authentication.py:70 +msgid "Invalid basic header. No credentials provided." +msgstr "" + +#: authentication.py:73 +msgid "Invalid basic header. Credentials string should not contain spaces." +msgstr "" + +#: authentication.py:83 +msgid "Invalid basic header. Credentials not correctly base64 encoded." +msgstr "" + +#: authentication.py:101 +msgid "Invalid username/password." +msgstr "" + +#: authentication.py:104 authentication.py:206 +msgid "User inactive or deleted." +msgstr "Vartotojas neaktyvus arba pašalintas." + +#: authentication.py:184 +msgid "Invalid token header. No credentials provided." +msgstr "" + +#: authentication.py:187 +msgid "Invalid token header. Token string should not contain spaces." +msgstr "" + +#: authentication.py:193 +msgid "" +"Invalid token header. Token string should not contain invalid characters." +msgstr "" + +#: authentication.py:203 +msgid "Invalid token." +msgstr "" + +#: authtoken/apps.py:7 +msgid "Auth Token" +msgstr "" + +#: authtoken/models.py:13 +msgid "Key" +msgstr "Raktas" + +#: authtoken/models.py:16 +msgid "User" +msgstr "Vartotojas" + +#: authtoken/models.py:18 +msgid "Created" +msgstr "Sukurta" + +#: authtoken/models.py:27 authtoken/serializers.py:19 +msgid "Token" +msgstr "" + +#: authtoken/models.py:28 +msgid "Tokens" +msgstr "" + +#: authtoken/serializers.py:9 +msgid "Username" +msgstr "Vartotojo vardas" + +#: authtoken/serializers.py:13 +msgid "Password" +msgstr "Slaptažodis" + +#: authtoken/serializers.py:35 +msgid "Unable to log in with provided credentials." +msgstr "" + +#: authtoken/serializers.py:38 +msgid "Must include \"username\" and \"password\"." +msgstr "" + +#: exceptions.py:102 +msgid "A server error occurred." +msgstr "Įvyko serverio klaida." + +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 +msgid "Malformed request." +msgstr "Netinkamai suformuota užklausa." + +#: exceptions.py:167 +msgid "Incorrect authentication credentials." +msgstr "Neteisingi autentifikacijos duomenys." + +#: exceptions.py:173 +msgid "Authentication credentials were not provided." +msgstr "Autentifikacijos duomenys nesuteikti." + +#: exceptions.py:179 +msgid "You do not have permission to perform this action." +msgstr "" + +#: exceptions.py:185 +msgid "Not found." +msgstr "Nerasta." + +#: exceptions.py:191 +#, python-brace-format +msgid "Method \"{method}\" not allowed." +msgstr "Metodas \"{method}\" yra neleidžiamas." + +#: exceptions.py:202 +msgid "Could not satisfy the request Accept header." +msgstr "Nepavyko patenkinti užklausos Accept antraštės." + +#: exceptions.py:212 +#, python-brace-format +msgid "Unsupported media type \"{media_type}\" in request." +msgstr "Nepalaikomas duomenų formatas \"{media_type}\" užklausoje." + +#: exceptions.py:223 +msgid "Request was throttled." +msgstr "Užklausa pristabdyta." + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "Šis laukas yra privalomas." + +#: fields.py:317 +msgid "This field may not be null." +msgstr "Šis laukas negali būti nustatytas kaip null." + +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 +msgid "This field may not be blank." +msgstr "Šis laukas negali būti tuščias." + +#: fields.py:768 fields.py:1881 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} characters." +msgstr "Patikrinkite, ar šis laukas turi ne daugiau nei {max_length} simbolių." + +#: fields.py:769 +#, python-brace-format +msgid "Ensure this field has at least {min_length} characters." +msgstr "Patikrinkite, ar šis laukas turi ne mažiau nei {min_length} simbolių." + +#: fields.py:816 +msgid "Enter a valid email address." +msgstr "Įveskite tinkamą el. pašto adresą." + +#: fields.py:827 +msgid "This value does not match the required pattern." +msgstr "Ši vertė neatitinka nustatyto šablono." + +#: fields.py:838 +msgid "" +"Enter a valid \"slug\" consisting of letters, numbers, underscores or " +"hyphens." +msgstr "Įveskite tinkamą \"slug\" tekstą, turintį tik raidžių, skaičių, pabraukimų arba brūkšnelių." + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 +msgid "Enter a valid URL." +msgstr "Įveskite tinkamą URL adresą." + +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" + +#: fields.py:903 +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "Įveskite tinkamą IPv4 arba IPv6 adresą." + +#: fields.py:931 +msgid "A valid integer is required." +msgstr "Reikiama tinkama integer tipo reikšmė." + +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format +msgid "Ensure this value is less than or equal to {max_value}." +msgstr "Patikrinkite, ar ši reikšmė yra mažesnė arba lygi {max_value}." + +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format +msgid "Ensure this value is greater than or equal to {min_value}." +msgstr "Patikrinkite, ar ši reikšmė yra didesnė arba lygi {min_value}." + +#: fields.py:934 fields.py:971 fields.py:1010 +msgid "String value too large." +msgstr "String tipo reikšmė per ilga." + +#: fields.py:968 fields.py:1004 +msgid "A valid number is required." +msgstr "Reikiamas tinkamas skaičius." + +#: fields.py:1007 +#, python-brace-format +msgid "Ensure that there are no more than {max_digits} digits in total." +msgstr "Patikrinkite, ar nėra daugiau nei {max_digits} skaitmenų." + +#: fields.py:1008 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_decimal_places} decimal places." +msgstr "Patikrinkite, ar nėra daugiau nei {max_decimal_places} skaitmenų po kablelio." + +#: fields.py:1009 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_whole_digits} digits before the " +"decimal point." +msgstr "Patikrinkite, ar nėra daugiau nei {max_whole_digits} skaitmenų priešais kablelį." + +#: fields.py:1148 +#, python-brace-format +msgid "Datetime has wrong format. Use one of these formats instead: {format}." +msgstr "Datetime tipo reikšmė yra netinkamo formato. Naudokite vieną iš šių formatų: {format}." + +#: fields.py:1149 +msgid "Expected a datetime but got a date." +msgstr "Tikėtasi datetime, gauta date tipo reikšmė." + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "Date tipo reikšmė yra netinkamo formato. Naudokite vieną iš šių formatų: {format}." + +#: fields.py:1237 +msgid "Expected a date but got a datetime." +msgstr "Tikėtasi date, gauta datetime tipo reikšmė." + +#: fields.py:1303 +#, python-brace-format +msgid "Time has wrong format. Use one of these formats instead: {format}." +msgstr "Time tipo reikšmė yra netinkamo formato. Naudokite vieną iš šių formatų: {format}." + +#: fields.py:1365 +#, python-brace-format +msgid "Duration has wrong format. Use one of these formats instead: {format}." +msgstr "Duration tipo reikšmė yra netinkamo formato. Naudokite vieną iš šių formatų: {format}." + +#: fields.py:1399 fields.py:1456 +#, python-brace-format +msgid "\"{input}\" is not a valid choice." +msgstr "\"{input}\" nėra tinkamas pasirinkimas." + +#: fields.py:1402 +#, python-brace-format +msgid "More than {count} items..." +msgstr "" + +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format +msgid "Expected a list of items but got type \"{input_type}\"." +msgstr "" + +#: fields.py:1458 +msgid "This selection may not be empty." +msgstr "Šis pasirinkimas negali būti tuščias." + +#: fields.py:1495 +#, python-brace-format +msgid "\"{input}\" is not a valid path choice." +msgstr "" + +#: fields.py:1514 +msgid "No file was submitted." +msgstr "" + +#: fields.py:1515 +msgid "" +"The submitted data was not a file. Check the encoding type on the form." +msgstr "" + +#: fields.py:1516 +msgid "No filename could be determined." +msgstr "" + +#: fields.py:1517 +msgid "The submitted file is empty." +msgstr "" + +#: fields.py:1518 +#, python-brace-format +msgid "" +"Ensure this filename has at most {max_length} characters (it has {length})." +msgstr "" + +#: fields.py:1566 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: fields.py:1604 relations.py:486 serializers.py:571 +msgid "This list may not be empty." +msgstr "" + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format +msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "" + +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 +msgid "Value must be valid JSON." +msgstr "" + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 +msgid "ascending" +msgstr "" + +#: filters.py:288 +msgid "descending" +msgstr "" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 +msgid "Invalid page." +msgstr "Netinkamas puslapis." + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "" + +#: relations.py:246 +#, python-brace-format +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "" + +#: relations.py:247 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "" + +#: relations.py:280 +msgid "Invalid hyperlink - No URL match." +msgstr "" + +#: relations.py:281 +msgid "Invalid hyperlink - Incorrect URL match." +msgstr "" + +#: relations.py:282 +msgid "Invalid hyperlink - Object does not exist." +msgstr "" + +#: relations.py:283 +#, python-brace-format +msgid "Incorrect type. Expected URL string, received {data_type}." +msgstr "" + +#: relations.py:448 +#, python-brace-format +msgid "Object with {slug_name}={value} does not exist." +msgstr "" + +#: relations.py:449 +msgid "Invalid value." +msgstr "" + +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "" + +#: templates/rest_framework/admin.html:116 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 +msgid "Filters" +msgstr "" + +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" + +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" + +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "" + +#: validators.py:39 +msgid "This field must be unique." +msgstr "" + +#: validators.py:89 +#, python-brace-format +msgid "The fields {field_names} must make a unique set." +msgstr "" + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" date." +msgstr "" + +#: validators.py:258 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" month." +msgstr "" + +#: validators.py:271 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" year." +msgstr "" + +#: versioning.py:40 +msgid "Invalid version in \"Accept\" header." +msgstr "" + +#: versioning.py:71 +msgid "Invalid version in URL path." +msgstr "" + +#: versioning.py:116 +msgid "Invalid version in URL path. Does not match any version namespace." +msgstr "" + +#: versioning.py:148 +msgid "Invalid version in hostname." +msgstr "" + +#: versioning.py:170 +msgid "Invalid version in query parameter." +msgstr "" diff --git a/rest_framework/locale/lv/LC_MESSAGES/django.mo b/rest_framework/locale/lv/LC_MESSAGES/django.mo index b4accc34ef160c63fdff67361e4f0154487a6ba6..7ee7172407f2e4b860d57c55ce1d73a04f42f0a7 100644 GIT binary patch delta 2243 zcmYk-S!`5Q9LMp0ZKoTxKnpEn3%x~aJJKDdc7wK5C|#g*14=;{mzc7Rw6zpX9HJn> zST#;ad?9YdAmEmXA^Knu?J1pncMBzmA&O`=}YsqL%W8-wrSK zKJUk5?oXmt;Bj1m)2RL~Vkut1Jp2`74wY1%&c#wp!6szYtre@V8`(yC!uL3;gK1Qn z%wRrVz-s&ywWYtK2HZ^N`g|DGK8(tl=W|*AIx6S6;qEo69bDp#cq{6`{pjFH)PT;R zR$vYTSg_O^NHMOWU4}I{h{N~{YGOZN5U-&obZaT=uN&!1rxsTt`DjC^71)E9lg##E z5$y_6UpcZF^}TTn;WRGCOQ@O7qW1U~RKJBhtbTj3ABS-lUW`%cqmsb}ZN?E)F1&?Z z_%mu{YFUPLxDU7KbL>Q|=O*aF2QZFz;h)H2S^=X{4pgC5ZVPIQp2RH}dzVTNm48rs z+0D~;V?QeEk765sh(mZ22e6Oj(D%+Fd1N&Tzo;-Z-puk>aXM#gG8aRW}Fw&Y{f-v5N!+eI817RiFh*es0d zZwSlr2s%3dGgP!>pCgmA>lnZcZt{+>g{YYnBJUdOK&{jOx&uMI;igbObPTm+uVM?% zp`KgDC`kvaL$!OcfZw-+RP^8}Y{M^bEiPamG~!Nla6jsc&!P74JSum-L(RB?!>uH% zL+x!VY9KG5a^?yusaKSF{f026J?W;xAZ^6=7^cxai|X)gOu}SBd(N@`uXIr3G`U4@ zIXyo}G!fbkl?Fm9tHJCg_7HUhC1$+D+!E%hMA}7YwDW~QyJvX~YV;1FvXj_AXazVV zZVqh6ZbC&{rJ`g_cWY+%VYXjy#&Yfd|4SQ$CcCWmQ7q+i7fqoF`)!g(KcxBR8l?MYdu#Tn~g+~d&m1X zs|+9U+ac6RQc?c!3UEu8uc##23FQ$7#lp(Sd}6)?sOY%Om#tJ<{Kjp#o=A(INnakn znqHX*Wh4a>_1T4i_#?S>iJ9Eee_J2efBYr?ClDVaTy@rjDTvB;tE z1Cfd7SfjHeGCBJ2nA1DaFnTcYM}Bise0Q)aaV|I) XNK6;yq{iPVX;1uB@=|jALg>W57r@%^ delta 2558 zcmZYAU2Icj9LMoz>)v31SYR8Ijl)13Fa`t0aIlRH*ch9*!2o#?Y}d1Tq_o)60SRg~ zh)O^t*2RQiQ1L>;jY$avyb|l%1sA?0MiU|tF(k%B??f*A{!S0k#3%iq&-0v~KJVx0 zrNIw}($|AUV}>+B%pm#$#vH?EO1O~TE;D8pzJnHif)C)y)mT_=%vM~3EqEGN;@8-RlO`C`iaSu> zk6}Mv!`WCp(U>5%qdGWL=xUx*Rp)c2q+VT!Ck?1HZ>L zSaqKG+xNPpFaJ@yh{*vxIUDvVYA0YG!ueg#u%2R0GviG84N|duRkTpcBZ)yr1SekIYr9 z!QYVIn(+)vd!Y{1&>*Uz7jYAw#9ep;2eF-6AHh?oJ#!s5;0&g*1*6!GXK|~(NA0n6 zGp(*y2E+IT&cOxj5LU$upf*c{i>CAlYVESP5pSYix1|h|H_kkdn&MY+F1~>s_z`yE z-?#hc|$ff_(RK8j=5jlUq< z!!+=U@DtOC8qhFmfCsUL_RSeG>$q_RwMP0@Yd06Q=9^G!wiD^RIf$z8O>Dr+XyGm7 zV@j!nNihvrjD4t?8AJ_aH3qLwO$nwf8X|H4wrwey+(ab)H)F9o=bXeG2xnxcD2 zqtkPmZY4E#FD>;m&B&5_X%*K#g8q5wF|HekVnQ=YbKbepsrA?Eqof&F=jHPMSi8tS zLG%#X51JVzEfwd8_aD)XtcI7?avdUui6F6_P|~JUT27RCx%?}y?Bld=2K<{kh}QUd z3l|YPi9%w8(3)zxHN{E+qLR=WZzq%{6WaA5B0^}Im9!jsyY3~MOc9aZ!$rGOhX>2= zt?x<<6Uzv_t6FRJkazHC11Yr=JG|V#m(^q|h}r(F-B?0s?UlL+&K)o5pOLiLJLNd^ zy!5blCx0I3MlYcaq@+{OOLd2x`2Nwh!{r{)7Z0TK_jh6+A%k5ai8gXJNJLzWQ6K56&2khiN$90`V+=|$7$Br~+P6S&@ zHjjq)IjQW`V0~a}Xm~g=8c(U|h~tLBF+Fs1+K-c(S{5!|v2by-)wHCwX=&zKMPD=k%2I+Ae*Mrm|<} dWCGJd*4{)sjwE)z|;%uxMn{{V#IEJFYQ diff --git a/rest_framework/locale/lv/LC_MESSAGES/django.po b/rest_framework/locale/lv/LC_MESSAGES/django.po index 2bc978866a..876f6f3677 100644 --- a/rest_framework/locale/lv/LC_MESSAGES/django.po +++ b/rest_framework/locale/lv/LC_MESSAGES/django.po @@ -8,9 +8,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-05 12:13+0000\n" -"Last-Translator: peterisb \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Latvian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/lv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -18,40 +18,40 @@ msgstr "" "Language: lv\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Nederīgs pieprasījuma sākums. Akreditācijas parametri nav nodrošināti." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Nederīgs pieprasījuma sākums. Akreditācijas parametriem jābūt bez atstarpēm." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Nederīgs pieprasījuma sākums. Akreditācijas parametri nav korekti base64 kodēti." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Nederīgs lietotājvārds/parole." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Lietotājs neaktīvs vai dzēsts." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Nederīgs pilnvaras sākums. Akreditācijas parametri nav nodrošināti." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Nederīgs pilnvaras sākums. Pilnvaras parametros nevar būt tukšumi." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Nederīgs pilnvaras sākums. Pilnvaras parametros nevar būt nederīgas zīmes." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Nederīga pilnavara." @@ -59,382 +59,515 @@ msgstr "Nederīga pilnavara." msgid "Auth Token" msgstr "Autorizācijas pilnvara" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Atslēga" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Lietotājs" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Izveidots" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Pilnvara" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Pilnvaras" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Lietotājvārds" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Parole" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Lietotāja konts ir atslēgts." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Neiespējami pieteikties sistēmā ar nodrošinātajiem akreditācijas datiem." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Jābūt iekļautam \"username\" un \"password\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Notikusi servera kļūda." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Nenoformēts pieprasījums." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Nekorekti autentifikācijas parametri." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Netika nodrošināti autorizācijas parametri." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Tev nav tiesību veikt šo darbību." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Nav atrasts." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Metode \"{method}\" nav atļauta." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Nevarēja apmierināt pieprasījuma Accept header." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Pieprasījumā neatbalstīts datu tips \"{media_type}\" ." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Pieprasījums tika apturēts." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Šis lauks ir obligāts." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Šis lauks nevar būt null." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" ir nederīga loģiskā vērtība." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Šis lauks nevar būt tukšs." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Pārliecinies, ka laukā nav vairāk par {max_length} zīmēm." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Pārliecinies, ka laukā ir vismaz {min_length} zīmes." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Ievadi derīgu e-pasta adresi." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Šī vērtība neatbilst prasītajam pierakstam." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Ievadi derīgu \"slug\" vērtību, kura sastāv no burtiem, skaitļiem, apakš-svītras vai defises." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Ievadi derīgu URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" ir nedrīgs UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Ievadi derīgu IPv4 vai IPv6 adresi." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Prasīta ir derīga skaitliska vērtība." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Pārliecinies, ka šī vērtība ir mazāka vai vienāda ar {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Pārliecinies, ka šī vērtība ir lielāka vai vienāda ar {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Teksta vērtība pārāk liela." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Derīgs skaitlis ir prasīts." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Pārliecinies, ka nav vairāk par {max_digits} zīmēm kopā." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Pārliecinies, ka nav vairāk par {max_decimal_places} decimālajām zīmēm." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Pārliecinies, ka nav vairāk par {max_whole_digits} zīmēm pirms komata." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datuma un laika formāts ir nepareizs. Lieto vienu no norādītajiem formātiem: \"{format}.\"" -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Tika gaidīts datums un laiks, saņemts datums.." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Datumam ir nepareizs formāts. Lieto vienu no norādītajiem formātiem: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Tika gaidīts datums, saņemts datums un laiks." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Laikam ir nepareizs formāts. Lieto vienu no norādītajiem formātiem: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Ilgumam ir nepreizs formāts. Lieto vienu no norādītajiem formātiem: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" ir nederīga izvēle." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Vairāk par {count} ierakstiem..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Tika gaidīts saraksts ar ierakstiem, bet tika saņemts \"{input_type}\" tips." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Šī daļa nevar būt tukša." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" ir nederīga ceļa izvēle." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Neviens fails netika pievienots." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Pievienotie dati nebija fails. Pārbaudi kodējuma tipu formā." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Faila nosaukums nevar tikt noteikts." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Pievienotais fails ir tukšs." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Pārliecinies, ka faila nosaukumā ir vismaz {max_length} zīmes (tajā ir {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Augšupielādē derīgu attēlu. Pievienotā datne nebija attēls vai bojāts attēls." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Šis saraksts nevar būt tukšs." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Tika gaidīta vārdnīca ar ierakstiem, bet tika saņemts \"{input_type}\" tips." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Vērtībai ir jābūt derīgam JSON." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Iesniegt" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Meklēt" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Kārtošana" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "augoši" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "dilstoši" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Nederīga lapa." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Nederīgs kursors" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Nederīga pk \"{pk_value}\" - objekts neeksistē." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Nepareizs tips. Tika gaidīta pk vērtība, saņemts {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Nederīga hipersaite - Nav URL sakritība." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Nederīga hipersaite - Nederīga URL sakritība." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Nederīga hipersaite - Objekts neeksistē." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Nepareizs tips. Tika gaidīts URL teksts, saņemts {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objekts ar {slug_name}={value} neeksistē." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Nedrīga vērtība." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Nederīgi dati. Tika gaidīta vārdnīca, saņemts {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtri" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Lauka filtri" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Kārtošana" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Meklēt" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Nekas" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Nav ierakstu, ko izvēlēties." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Šim laukam ir jābūt unikālam." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Laukiem {field_names} jāveido unikālas kombinācijas." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Šim laukam ir jābūt unikālam priekš \"{date_field}\" datuma." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Šim laukam ir jābūt unikālam priekš \"{date_field}\" mēneša." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Šim laukam ir jābūt unikālam priekš \"{date_field}\" gada." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Nederīga versija \"Accept\" galvenē." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Nederīga versija URL ceļā." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Nederīga versija URL ceļā. Nav atbilstības esošo versiju telpā." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Nederīga versija servera nosaukumā." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Nederīga versija pieprasījuma parametros." - -#: views.py:88 -msgid "Permission denied." -msgstr "Pieeja liegta." diff --git a/rest_framework/locale/mk/LC_MESSAGES/django.mo b/rest_framework/locale/mk/LC_MESSAGES/django.mo index 752263456cfbeed993de97e6ba73c4e06629f66f..4c13e5a3df71ee2357544a6a7386ed8cdce59f37 100644 GIT binary patch delta 2126 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 diff --git a/rest_framework/locale/mk/LC_MESSAGES/django.po b/rest_framework/locale/mk/LC_MESSAGES/django.po index 0e59663d04..03f313523a 100644 --- a/rest_framework/locale/mk/LC_MESSAGES/django.po +++ b/rest_framework/locale/mk/LC_MESSAGES/django.po @@ -8,9 +8,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Filip Dimitrovski \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Macedonian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/mk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -18,40 +18,40 @@ msgstr "" "Language: mk\n" "Plural-Forms: nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Невалиден основен header. Не се внесени податоци за автентикација." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Невалиден основен header. Автентикационата низа не треба да содржи празни места." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Невалиден основен header. Податоците за автентикација не се енкодирани со base64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Невалидно корисничко име/лозинка." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Корисникот е деактивиран или избришан." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Невалиден токен header. Не се внесени податоци за најава." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Невалиден токен во header. Токенот не треба да содржи празни места." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "" -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Невалиден токен." @@ -59,382 +59,515 @@ msgstr "Невалиден токен." msgid "Auth Token" msgstr "Автентикациски токен" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Корисник" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Токен" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Токени" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Корисничко име" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Лозинка" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Сметката на корисникот е деактивирана." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Не може да се најавите со податоците за најава." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Мора да се внесе „username“ и „password“." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Настана серверска грешка." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Неправилен request." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Неточни податоци за најава." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Не се внесени податоци за најава." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Немате дозвола да го сторите ова." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Не е пронајдено ништо." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Методата \"{method}\" не е дозволена." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Не може да се исполни барањето на Accept header-от." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Media типот „{media_type}“ не е поддржан." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Request-от е забранет заради ограничувања." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Ова поле е задолжително." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Ова поле не смее да биде недефинирано." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" не е валиден boolean." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Ова поле не смее да биде празно." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Ова поле не смее да има повеќе од {max_length} знаци." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Ова поле мора да има барем {min_length} знаци." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Внесете валидна email адреса." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Ова поле не е по правилната шема/барање." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Внесете валидно име што содржи букви, бројки, долни црти или црти." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Внесете валиден URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" не е валиден UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Внеси валидна IPv4 или IPv6 адреса." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Задолжителен е валиден цел број." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Вредноста треба да биде помала или еднаква на {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Вредноста треба да биде поголема или еднаква на {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Вредноста е преголема." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Задолжителен е валиден број." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Не смее да има повеќе од {max_digits} цифри вкупно." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Не смее да има повеќе од {max_decimal_places} децимални места." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Не смее да има повеќе од {max_whole_digits} цифри пред децималната точка." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Датата и времето се со погрешен формат. Користете го овој формат: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Очекувано беше дата и време, а внесено беше само дата." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Датата е со погрешен формат. Користете го овој формат: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Очекувана беше дата, а внесени беа и дата и време." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Времето е со погрешен формат. Користете го овој формат: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "„{input}“ не е валиден избор." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Повеќе од {count} ставки..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Очекувана беше листа од ставки, а внесено беше „{input_type}“." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "" -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Ниеден фајл не е качен (upload-иран)." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Испратените податоци не се фајл. Проверете го encoding-от на формата." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Не може да се открие име на фајлот." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Качениот (upload-иран) фајл е празен." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Името на фајлот треба да има највеќе {max_length} знаци (а има {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Качете (upload-ирајте) валидна слика. Фајлот што го качивте не е валидна слика или е расипан." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Оваа листа не смее да биде празна." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Очекуван беше dictionary од ставки, a внесен беше тип \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Вредноста мора да биде валиден JSON." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Испрати" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Пребарај" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Подредување" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "растечки" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "опаѓачки" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Невалидна вредност за страна." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Невалиден покажувач (cursor)" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Невалиден pk „{pk_value}“ - објектот не постои." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Неточен тип. Очекувано беше pk, а внесено {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Невалиден хиперлинк - не е внесен URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Невалиден хиперлинк - внесен е неправилен URL." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Невалиден хиперлинк - Објектот не постои." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Неточен тип. Очекувано беше URL, a внесено {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Објектот со {slug_name}={value} не постои." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Невалидна вредност." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Невалидни податоци. Очекуван беше dictionary, а внесен {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Филтри" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Филтри на полиња" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Подредување" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Пребарај" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Ништо" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Нема ставки за избирање." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Ова поле мора да биде уникатно." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Полињата {field_names} заедно мора да формираат уникатен збир." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Ова поле мора да биде уникатно за „{date_field}“ датата." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Ова поле мора да биде уникатно за „{date_field}“ месецот." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Ова поле мора да биде уникатно за „{date_field}“ годината." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Невалидна верзија во „Accept“ header-от." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Невалидна верзија во URL патеката." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Верзијата во URL патеката не е валидна. Не се согласува со ниеден version namespace (именски простор за верзии)." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Невалидна верзија во hostname-от." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Невалидна верзија во query параметарот." - -#: views.py:88 -msgid "Permission denied." -msgstr "Барањето не е дозволено." diff --git a/rest_framework/locale/nb/LC_MESSAGES/django.mo b/rest_framework/locale/nb/LC_MESSAGES/django.mo index fc57ebb9ff00848ccf5b869e51ecb703ea8c06a4..bd983b292c4cb1c0223b45b9624554ca496ec38d 100644 GIT binary patch delta 2239 zcmYk+drZ}39LMo59uA>*0b|Hujl!lbAG?) z`99z0Tx|VnXXN`OiDO1PK&({w%#O(<-e~zLW)2qM8r*@)@eu0!CvXK$;5_^ci}4~B z;h&g{`E$)SV<|S`kZZ&~qO*+)3H-Vqo3ImKz`Jn}QZNc4Gf@8?brg_u% zE@3{V-eH!FK`g{BwOF;tz2OnOlkstH{5EQ3AEH)t8I{Uk zy>W1{`*|DsxITi)z!SI{PonNOi2?i)Gw^4OICPTub{YmS5v!4`TRq-|&B!*|lb+9_ zZg3J+CgZpa-^Eh=9JQstp(b3z&GmUVYJ3<~GcTu+{|Y*1xe(uL)HpNUopCGbi;tj# z$59hHgUY}a^y7-9?nJV29pii~!*=Y(=TQs$5i@ZXwV;2Nl7C%DVL9cPgVdvSpfb>d z?KsXOPKT5+5pKxu|h3^0K46CF3}%=q8az*fg>R zyN&^z!z$Bt{tM}lb*n?_&$>}X^(bn_L#P!!joQOW)C0~T*|zhTh~J?e_yej)Z=o_3 z;Oy#qhfvRX33=HBZ`nNGzNQnzpHQbDhh=WSeW)3~f)2iiOy0gh?fp&Mf`x0`3HPHW zHjD-M6si^`@P536+TwC5SoaNKMAh0whi$V5P!E0vQ}CSU1x#S0eG6YA31*Wlc?!)%~@&14Iu|LC_+$$nC^4z#o#`M|67Eg!W2jSrgM_9YSp%(Ln4b zI27@}?k;R5)aJX`9@Jvgwi2o(H5F@-_W%EF6N7H=f~u5-+g@TLkxr2+N;_oHAt7f$S)TagYC!wmn-TZWP3U0SnI(6P)4sIrrqo-0< zMX#ll#0usn_+ym|SNfw5rB%en(*nNeY{un=^3vimUhAE*P}PRux>DM_mhjMUarm77<4L&$<__4>Gnj-^lul`_P|G-FJIQGY~+=OUjW<~6+%u0W3 VW_5u-nw1xdh4Z%iqSJvB{{o+}-6a43 delta 2574 zcmZYATWl0n9LMp~Zf_L9S_+g)=~x8IrIiAubxW@lXra_XTP_8~DLa%&hgs9zHY&(I zX(SM%7GgpWHG(h32UA5cQ6nYB2k=3IAzlb3MB;;&7}Fr>gWuomAP=7Ozn^nXXV09= ze`deha-}PEy)f&fq3kE-6CDv_j^g1QJ}7VI8S@~%g%*zEBD{$UFn@|M#kd6Z{T7^u zdodpmVFkW`%kcxu#hl*p44zJ!U+R9MY)^*&pTqJa^w*-^Jw7}oPob% z7G~1mR7@^Df`v#Ln3mAZ$fc$SHO>GkjgH`Kd_HWyj#}{98Jxe)`YIh-K}k{i!YHb} z4V6rBT!Y6@XM8bi-$5;)YG(Sn4zy?=Kuz!{20Mb;v?oy$%4BxSaLO$9zlusV9lLM< zwbCze4t^PW1GUmSVLQTdR?;p(^4D~scBT){W*Fn3&ODz%bmpsYG4_V-!^lwPOp1y{ zY|3ALih&_#5vr`?H7cmg%?tEer$fn|6LwNv@5P7A5W z2K12yq|AF%l=TzHlVKKF#;nC^)ENz-cH#_bfN|u}Hh<$P%wT`@{c7BRG1N}Hi+T)y zL;lQU*j~V_G+qVH=l*6Jm1a8PL5DH#qrUJp=Hj2oahlu698Cew9GhTPpq`peBoEC9 za%$!nlF#NGD(NnuCj2>SL6=cSc^h-NzsY1e8XzB)?WLH7b*LLRVIJ;8Wwnhu`&Urc ze~-HH1Tv;6;BBP^tj22Gjx~53SK}qr0{+32MP)XrsV~%`lA;gS;R(#fYp9*Lj!W>>6MdJDD@DvzZJUSL|Rigr=uah-oxP))yxwCySu zp|jpiWD@%co%MD?Td5Kulpl@6E<&Y%P%ha-jL0QabiDi={8t=mSww0tAIeU>6gVE! zNz@VjgwCp$Q1Wo(W*bpYs5B6LLG9j06IH#{7KUBQ4dn%S8I%@2dEEp>?;J{Z@a!sk zRg`?)M28xbc0%t26}?1)Qg+DopBhd+S7x~h%a125+ZwSwH)i$6m{9`#F#&=G`q0N89L}D5^mhLVvg@RvC7fog*7?d z!~F-{vsH@ f<@)~EL|J>JXfW<4;}+w_9D6Y7a#t>^UHtODNQf-M diff --git a/rest_framework/locale/nb/LC_MESSAGES/django.po b/rest_framework/locale/nb/LC_MESSAGES/django.po index f572f90beb..9dbaf45dee 100644 --- a/rest_framework/locale/nb/LC_MESSAGES/django.po +++ b/rest_framework/locale/nb/LC_MESSAGES/django.po @@ -10,9 +10,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-11-28 15:25+0000\n" -"Last-Translator: Håken Lid \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Norwegian Bokmål (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/nb/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -20,40 +20,40 @@ msgstr "" "Language: nb\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Ugyldig basic header. Ingen legitimasjon gitt." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Ugyldig basic header. Legitimasjonsstreng bør ikke inneholde mellomrom." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Ugyldig basic header. Legitimasjonen ikke riktig Base64 kodet." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Ugyldig brukernavn eller passord." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Bruker inaktiv eller slettet." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Ugyldig token header. Ingen legitimasjon gitt." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Ugyldig token header. Token streng skal ikke inneholde mellomrom." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Ugyldig token header. Tokenstrengen skal ikke inneholde ugyldige tegn." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Ugyldig token." @@ -61,382 +61,515 @@ msgstr "Ugyldig token." msgid "Auth Token" msgstr "Auth Token" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Nøkkel" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Bruker" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Opprettet" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokener" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Brukernavn" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Passord" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Brukerkonto er deaktivert." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Kan ikke logge inn med gitt legitimasjon." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Må inneholde \"username\" og \"password\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "En serverfeil skjedde." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Misformet forespørsel." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Ugyldig autentiseringsinformasjon." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Manglende autentiseringsinformasjon." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Du har ikke tilgang til å utføre denne handlingen." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Ikke funnet." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Metoden \"{method}\" ikke gyldig." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Kunne ikke tilfredsstille request Accept header." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Ugyldig media type \"{media_type}\" i request." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Forespørselen ble strupet." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Dette feltet er påkrevd." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Dette feltet må ikke være tomt." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" er ikke en gyldig bolsk verdi." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Dette feltet må ikke være blankt." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Forsikre deg om at dette feltet ikke har mer enn {max_length} tegn." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Forsikre deg at dette feltet har minst {min_length} tegn." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Oppgi en gyldig epost-adresse." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Denne verdien samsvarer ikke med de påkrevde mønsteret." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Skriv inn en gyldig \"slug\" som består av bokstaver, tall, understrek eller bindestrek." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Skriv inn en gyldig URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" er ikke en gyldig UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Skriv inn en gyldig IPv4 eller IPv6-adresse." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "En gyldig heltall er nødvendig." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Sikre denne verdien er mindre enn eller lik {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Sikre denne verdien er større enn eller lik {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Strengverdien for stor." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Et gyldig nummer er nødvendig." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Pass på at det ikke er flere enn {max_digits} siffer totalt." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Pass på at det ikke er flere enn {max_decimal_places} desimaler." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Pass på at det ikke er flere enn {max_whole_digits} siffer før komma." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datetime har feil format. Bruk et av disse formatene i stedet: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Forventet en datetime, men fikk en date." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Dato har feil format. Bruk et av disse formatene i stedet: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Forventet en date, men fikk en datetime." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Tid har feil format. Bruk et av disse formatene i stedet: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Varighet har feil format. Bruk et av disse formatene i stedet: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" er ikke et gyldig valg." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Mer enn {count} elementer ..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Forventet en liste over elementer, men fikk type \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Dette valget kan ikke være tomt." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" er ikke en gyldig bane valg." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Ingen fil ble sendt." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "De innsendte data var ikke en fil. Kontroller kodingstypen på skjemaet." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Kunne ikke finne filnavn." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Den innsendte filen er tom." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Sikre dette filnavnet har på det meste {max_length} tegn (det har {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Last opp et gyldig bilde. Filen du lastet opp var enten ikke et bilde eller en ødelagt bilde." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Denne listen kan ikke være tom." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Forventet en dictionary av flere ting, men fikk typen \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Verdien må være gyldig JSON." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Send inn" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Søk" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Sortering" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "stigende" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "synkende" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Ugyldig side" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Ugyldig markør" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Ugyldig pk \"{pk_value}\" - objektet eksisterer ikke." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Feil type. Forventet pk verdi, fikk {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Ugyldig hyperkobling - No URL match." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Ugyldig hyperkobling - Incorrect URL match." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Ugyldig hyperkobling - Objektet eksisterer ikke." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Feil type. Forventet URL streng, fikk {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objekt med {slug_name}={value} finnes ikke." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Ugyldig verdi." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Ugyldige data. Forventet en dictionary, men fikk {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtre" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Feltfiltre" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Sortering" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Søk" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Ingen" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Ingenting å velge." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Dette feltet må være unikt." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Feltene {field_names} må gjøre et unikt sett." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Dette feltet må være unikt for \"{date_field}\" dato." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Dette feltet må være unikt for \"{date_field}\" måned." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Dette feltet må være unikt for \"{date_field}\" år." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Ugyldig versjon på \"Accept\" header." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Ugyldig versjon i URL-banen." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Ugyldig versjon i URL. Passer ikke med noen eksisterende versjon." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Ugyldig versjon i vertsnavn." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Ugyldig versjon i søkeparameter." - -#: views.py:88 -msgid "Permission denied." -msgstr "Tillatelse avslått." diff --git a/rest_framework/locale/ne_NP/LC_MESSAGES/django.mo b/rest_framework/locale/ne_NP/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..a9071d09e83417e0d10cf349f5e2f9f8d9f9a2d4 GIT binary patch literal 15636 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 literal 0 HcmV?d00001 diff --git a/rest_framework/locale/ne_NP/LC_MESSAGES/django.po b/rest_framework/locale/ne_NP/LC_MESSAGES/django.po new file mode 100644 index 0000000000..74b56e47ed --- /dev/null +++ b/rest_framework/locale/ne_NP/LC_MESSAGES/django.po @@ -0,0 +1,574 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Shrawan Poudel , 2018 +# Xavier Ordoquy , 2020 +msgid "" +msgstr "" +"Project-Id-Version: Django REST framework\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:59+0000\n" +"Last-Translator: Xavier Ordoquy \n" +"Language-Team: Nepali (Nepal) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ne_NP/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ne_NP\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: authentication.py:70 +msgid "Invalid basic header. No credentials provided." +msgstr "अमान्य हेडरहरू, क्रेडेन्सियलहरू प्रदान गरिएको छैन |" + +#: authentication.py:73 +msgid "Invalid basic header. Credentials string should not contain spaces." +msgstr "अमान्य हेडरहरू, क्रेडेन्सियलहरूमा स्पेस हुनु हुँदैन |" + +#: authentication.py:83 +msgid "Invalid basic header. Credentials not correctly base64 encoded." +msgstr "अमान्य हेडरहरू, क्रेडेन्सियलहरूमा सही तरिकाले base64 एन्कोड गरिएको छैन |" + +#: authentication.py:101 +msgid "Invalid username/password." +msgstr "अमान्य प्रयोगकर्तानाम/पासवर्ड |" + +#: authentication.py:104 authentication.py:206 +msgid "User inactive or deleted." +msgstr "प्रयोगकर्ता निष्क्रिय वा मेटायिएको छ |" + +#: authentication.py:184 +msgid "Invalid token header. No credentials provided." +msgstr "अमान्य टोकन हेडर । कुनै क्रेडेन्सियल प्रदान गरिएको छैन ।" + +#: authentication.py:187 +msgid "Invalid token header. Token string should not contain spaces." +msgstr "अमान्य टोकन हेडर | टोकनमा स्पेस हुनु हुँदैन |" + +#: authentication.py:193 +msgid "" +"Invalid token header. Token string should not contain invalid characters." +msgstr "अमान्य टोकन हेडर | टोकनमा अवैध अक्षरहरू हुनु हुँदैन |" + +#: authentication.py:203 +msgid "Invalid token." +msgstr "अमान्य टोकन |" + +#: authtoken/apps.py:7 +msgid "Auth Token" +msgstr "प्रयोगकर्ता प्रमाणिकरण टोकन " + +#: authtoken/models.py:13 +msgid "Key" +msgstr "मूल" + +#: authtoken/models.py:16 +msgid "User" +msgstr "प्रयोगकर्ता" + +#: authtoken/models.py:18 +msgid "Created" +msgstr "सिर्जना गरियो" + +#: authtoken/models.py:27 authtoken/serializers.py:19 +msgid "Token" +msgstr "टोकन" + +#: authtoken/models.py:28 +msgid "Tokens" +msgstr "टोकेनहरु" + +#: authtoken/serializers.py:9 +msgid "Username" +msgstr "प्रयोगकर्ताको नाम" + +#: authtoken/serializers.py:13 +msgid "Password" +msgstr "पासवर्ड" + +#: authtoken/serializers.py:35 +msgid "Unable to log in with provided credentials." +msgstr "प्रदान गरिएको क्रेडेन्सियलसँग लग इन गर्न सकिएन |" + +#: authtoken/serializers.py:38 +msgid "Must include \"username\" and \"password\"." +msgstr "\"प्रयोगकर्ताको नाम\" र \"पासवर्ड\" सामेल हुनु पर्छ |" + +#: exceptions.py:102 +msgid "A server error occurred." +msgstr "सर्भर त्रुटि देखापर्यो |" + +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 +msgid "Malformed request." +msgstr "विकृत अनुरोध |" + +#: exceptions.py:167 +msgid "Incorrect authentication credentials." +msgstr "गलत प्रमाणीकरण क्रेडेन्सियलहरू |" + +#: exceptions.py:173 +msgid "Authentication credentials were not provided." +msgstr "प्रमाणीकरण क्रेडेन्सियलहरू पयिएन | " + +#: exceptions.py:179 +msgid "You do not have permission to perform this action." +msgstr "तपाइँसँग यो कार्य गर्न अनुमति छैन |" + +#: exceptions.py:185 +msgid "Not found." +msgstr "फेला परेन |" + +#: exceptions.py:191 +#, python-brace-format +msgid "Method \"{method}\" not allowed." +msgstr "\"{method}\" गर्ने अनुमती छैन |" + +#: exceptions.py:202 +msgid "Could not satisfy the request Accept header." +msgstr "Accept Header अनुरोधलाई सन्तुष्ट गर्ने सकिएन |" + +#: exceptions.py:212 +#, python-brace-format +msgid "Unsupported media type \"{media_type}\" in request." +msgstr "असमर्थित मिडिया टाईप \"{media_type}\" अनुरोधको | " + +#: exceptions.py:223 +msgid "Request was throttled." +msgstr "अनुरोध प्रतिबन्दित गरियो |" + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "यो फिल्ड आवश्यक छ |" + +#: fields.py:317 +msgid "This field may not be null." +msgstr "यो फिल्ड खाली हुनु हुँदैन |" + +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 +msgid "This field may not be blank." +msgstr "यो फिल्ड खाली हुन सक्दैन |" + +#: fields.py:768 fields.py:1881 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} characters." +msgstr "यो फिल्डसँग {max_length} अक्षरहरू भन्दा बढी छ छैन सुनिश्चित गर्नुहोस् |" + +#: fields.py:769 +#, python-brace-format +msgid "Ensure this field has at least {min_length} characters." +msgstr "यो फिल्डमा कम से कम {min_length} अक्षरहरू छ छैन सुनिश्चित गर्नुहोस् |" + +#: fields.py:816 +msgid "Enter a valid email address." +msgstr "मान्य इमेल ठेगाना प्रविष्ट गर्नुहोस् |" + +#: fields.py:827 +msgid "This value does not match the required pattern." +msgstr "यो परिमाण आवश्यक ढाँचा मेल खाँदैन ।" + +#: fields.py:838 +msgid "" +"Enter a valid \"slug\" consisting of letters, numbers, underscores or " +"hyphens." +msgstr "अक्षरहरू, अङ्कहरू, अन्डरसेर्सहरू वा हाइफनहरू समावेश भएका एक मान्य \"slug\" प्रविष्टि गर्नुहोस् ।" + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 +msgid "Enter a valid URL." +msgstr "मान्य यूआरएल प्रविष्ट गर्नुहोस् ।" + +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" + +#: fields.py:903 +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "मान्य IPv4 वा IPv6 ठेगाना प्रविष्टि गर्नुहोस् ।" + +#: fields.py:931 +msgid "A valid integer is required." +msgstr "एक मान्य पूर्णांक आवश्यक छ ।" + +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format +msgid "Ensure this value is less than or equal to {max_value}." +msgstr "यो परिमाण {max_value} को भन्दा कम वा बराबर छ सुनिश्चित गर्नुहोस् ।" + +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format +msgid "Ensure this value is greater than or equal to {min_value}." +msgstr "यो परिमाण {min_value} भन्दा बढी वा बराबर छ सुनिश्चित गर्नुहोस् ।" + +#: fields.py:934 fields.py:971 fields.py:1010 +msgid "String value too large." +msgstr "स्ट्रिङ परिमाण धेरै ठूलो छ ।" + +#: fields.py:968 fields.py:1004 +msgid "A valid number is required." +msgstr "एउटा मान्य अङ्कहरू आवश्यक छ ।" + +#: fields.py:1007 +#, python-brace-format +msgid "Ensure that there are no more than {max_digits} digits in total." +msgstr "सुनिश्चित गर्नुहोस् कि {max_digits} अङ्कहरू भन्दा अधिक नहोस् ।" + +#: fields.py:1008 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_decimal_places} decimal places." +msgstr "सुनिश्चित गर्नुहोस् कि {max_decimal_places} दशमलव स्थानहरू भन्दा अधिक नहोस् ।" + +#: fields.py:1009 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_whole_digits} digits before the " +"decimal point." +msgstr "सुनिश्चित गर्नुहोस् कि दशमलव बिन्दुभन्दा पहिले {max_whole_digits} अङ्कहरू भन्दा अधिक नहोस् ।" + +#: fields.py:1148 +#, python-brace-format +msgid "Datetime has wrong format. Use one of these formats instead: {format}." +msgstr "डेटटाइममा गलत ढाँचा छ । यसको सट्टामा यी मध्ये एक ढाँचा प्रयोग गर्नुहोस्: {format} ।" + +#: fields.py:1149 +msgid "Expected a datetime but got a date." +msgstr "डेटटाइम अपेक्षित भए तर मिति प्राप्त भयो ।" + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "डेटमा गलत ढाँचा छ । यसको सट्टामा यी मध्ये एक ढाँचा प्रयोग गर्नुहोस्: {format} ।" + +#: fields.py:1237 +msgid "Expected a date but got a datetime." +msgstr "डेट अपेक्षित भए तर डेटाटाइम प्राप्त भयो ।" + +#: fields.py:1303 +#, python-brace-format +msgid "Time has wrong format. Use one of these formats instead: {format}." +msgstr "समयमा गलत ढाँचा छ । यसको सट्टामा यी मध्ये एक ढाँचा प्रयोग गर्नुहोस्: {format} ।" + +#: fields.py:1365 +#, python-brace-format +msgid "Duration has wrong format. Use one of these formats instead: {format}." +msgstr "अवधिमा गलत ढाँचा छ । यसको सट्टामा यी मध्ये एक ढाँचा प्रयोग गर्नुहोस्: {format} ।" + +#: fields.py:1399 fields.py:1456 +#, python-brace-format +msgid "\"{input}\" is not a valid choice." +msgstr "\"{input}\" मान्य छनौट छैन ।" + +#: fields.py:1402 +#, python-brace-format +msgid "More than {count} items..." +msgstr "{count} वस्तुहरू भन्दा बढी ..." + +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format +msgid "Expected a list of items but got type \"{input_type}\"." +msgstr "वस्तुहरूको सूची अपेक्षित तर \"{input_type}\" टाइप प्राप्त भयो ।" + +#: fields.py:1458 +msgid "This selection may not be empty." +msgstr "यो चयन रिक्त हुन सक्दैन ।" + +#: fields.py:1495 +#, python-brace-format +msgid "\"{input}\" is not a valid path choice." +msgstr "\"{input}\" मान्य मार्गको विकल्प होइन ।" + +#: fields.py:1514 +msgid "No file was submitted." +msgstr "कुनै फाइल पेश गरिएको छैन ।" + +#: fields.py:1515 +msgid "" +"The submitted data was not a file. Check the encoding type on the form." +msgstr "पेश गरिएको डेटा फाईल थिएन । एन्कोडिङ प्रकार फारममा जाँच्नुहोस् ।" + +#: fields.py:1516 +msgid "No filename could be determined." +msgstr "कुनै फाइल नाम निर्धारण गर्न सकिएन ।" + +#: fields.py:1517 +msgid "The submitted file is empty." +msgstr "पेश गरिएको फाइल खाली छ ।" + +#: fields.py:1518 +#, python-brace-format +msgid "" +"Ensure this filename has at most {max_length} characters (it has {length})." +msgstr "यो फाइल नाममा अधिक {max_length} अक्षरहरू छ छैन (यसमा {length} छ) सुनिश्चित गर्नुहोस् ।" + +#: fields.py:1566 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "मान्य चित्र अपलोड गर्नुहोस् । तपाईंले अपलोड गर्नुभएको फाइल होइन वा चित्र वा भ्रष्ट चित्र हो ।" + +#: fields.py:1604 relations.py:486 serializers.py:571 +msgid "This list may not be empty." +msgstr "यो सूची खाली हुन सक्दैन ।" + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format +msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "वस्तुहरूको शब्दकोशको अपेक्षित तर \"{input_type}\" टाइप प्राप्त भयो ।" + +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 +msgid "Value must be valid JSON." +msgstr "परिमाण मान्य JSON हुनु पर्छ ।" + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "खोजी गर्नुहोस्" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "क्रम" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 +msgid "ascending" +msgstr "आरोहण" + +#: filters.py:288 +msgid "descending" +msgstr "अवरोहण" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 +msgid "Invalid page." +msgstr "अमान्य पृष्ठ ।" + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "अमान्य कर्सर" + +#: relations.py:246 +#, python-brace-format +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "अमान्य pk \"{pk_value}\" - वस्तुको अस्तित्व छैन ।" + +#: relations.py:247 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "गलत प्रकार। अपेक्षित pk परिमाण, {data_type} प्राप्त भयो ।" + +#: relations.py:280 +msgid "Invalid hyperlink - No URL match." +msgstr "अमान्य हाइपरलिंक - कुनै यूआरएलको मेल छैन ।" + +#: relations.py:281 +msgid "Invalid hyperlink - Incorrect URL match." +msgstr "अमान्य हाइपरलिंक - गलत यूआरएलको मेल छ ।" + +#: relations.py:282 +msgid "Invalid hyperlink - Object does not exist." +msgstr "अमान्य हाइपरलिंक - वस्तुको अस्तित्व छैन ।" + +#: relations.py:283 +#, python-brace-format +msgid "Incorrect type. Expected URL string, received {data_type}." +msgstr "गलत प्रकार। अपेक्षित यूआरएल स्ट्रिङ, {data_type} प्राप्त भयो ।" + +#: relations.py:448 +#, python-brace-format +msgid "Object with {slug_name}={value} does not exist." +msgstr "वस्तु {slug_name} = {value} सँग अस्तित्व छैन ।" + +#: relations.py:449 +msgid "Invalid value." +msgstr "अमान्य परिमाण ।" + +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "अमान्य डेटा । एक शब्दकोश अपेक्षित, तर {datatype} प्राप्त भयो ।" + +#: templates/rest_framework/admin.html:116 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 +msgid "Filters" +msgstr "फिल्टरहरू" + +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" + +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" + +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "कुनै पनि होइन" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "चयन गर्न वस्तुहरू छैनन् ।" + +#: validators.py:39 +msgid "This field must be unique." +msgstr "यो फिल्ड अद्वितीय हुनुपर्छ ।" + +#: validators.py:89 +#, python-brace-format +msgid "The fields {field_names} must make a unique set." +msgstr "फिल्ड {field_names} ले एक अद्वितीय सेट गर्नु पर्छ ।" + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" date." +msgstr "यो फिल्ड \"{date_field}\" मितिको लागि अद्वितीय हुनुपर्छ ।" + +#: validators.py:258 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" month." +msgstr "यो फिल्ड \"{date_field}\" महिनाको लागि अद्वितीय हुनुपर्छ ।" + +#: validators.py:271 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" year." +msgstr "यो फिल्ड \"{date_field}\" वर्षको लागि अद्वितीय हुनुपर्छ ।" + +#: versioning.py:40 +msgid "Invalid version in \"Accept\" header." +msgstr "\"Accept\" हेडरमा अमान्य संस्करण ।" + +#: versioning.py:71 +msgid "Invalid version in URL path." +msgstr "यूआरएल मार्गमा अमान्य संस्करण ।" + +#: versioning.py:116 +msgid "Invalid version in URL path. Does not match any version namespace." +msgstr "यूआरएल मार्गमा अमान्य संस्करण । कुनै पनि संस्करणको नामसँग मेल खाँदैन ।" + +#: versioning.py:148 +msgid "Invalid version in hostname." +msgstr "होस्ट नाममा अमान्य संस्करण ।" + +#: versioning.py:170 +msgid "Invalid version in query parameter." +msgstr " क्वेरी परिमितिमा अमान्य संस्करण ।" diff --git a/rest_framework/locale/nl/LC_MESSAGES/django.mo b/rest_framework/locale/nl/LC_MESSAGES/django.mo index ed74bffa0d3ab6df64a05899180d5a7f4049766b..4691ca63fffdb0a1e8a48941f95a499d7f227728 100644 GIT binary patch delta 2249 zcmYk+e@smjqH7{xN&iM+zjUjBkI?S&Q`#doMhWk8pj?zkVCFvX4+Jx{XTZpZ;|; z$NRn;)3`r^%D_vw1jkVCdml^iCg$Vsm~iOK=IIcYU^+G+S+{0<9NUp?w3mHfLA_uM zRVJ5lAzs4>euLW5Db$1;dAYvtLtP(3)y#zu`LCgKl^gC}qpk}Ucr)%oJ$M)$JcpXl z6;uZ9U=R!QyooHum0X9h3VW~*UqdbI7c9X0s0IC-NB(sqljW?!Wk@|*FDe6jaXesl z2(_n~l)fxO2Rl$J8oP$~Wm^};e{u?vS#dwdHS)26T(=QFzstnl59dSBu^9S!h3W?(iI z<6u5UaSb-$0Jh_Ms8jHl|9u^Ar;aR+O?VnrtY4$5{2nS3A;){J36+s9tY&;WO-D2N z3~O;3mBMQByc}Cl103?NPa=!52~@_uLgsF{%e?0!s8i63jBS0W37tTaY~!d5eU3|X z{(q)Z%8du8l!bXZ9oHjA+FG$3dr^CM#&--U3%i7R{zFuie}b#=7HW^R3Qc4?Qm;0I zTEOd)@$DTtH8|<}H!AfJevLHagGjdR6tY-*3stN)unVU!h%JwKr=T6Rpl(#EPvU<3 z09D+%EMM;_!h}}XL`NyyiW>MZ>Ud1}Uc~^{X+#H+OU$&*bU00J)32NcP(iC^bQIO< z2rWQId>^rw(1K|Rcdymcz}n}hi58`?uM<{m3mw)Rd37|ep)~A|pATxj1<@vocjWClE~4 zW)}sM&xLAImqR6K$@}@Y+g3#?s`y*&RMpj2M^{E@b30-~!xcS)v7b_5e(Z%4PUG>o*K0n~e>6VQAFF&eKJf9P!a%a6pfvSH!DKMCr+74&e6YMa U)wrT)R$w^QSJD_rUJqaR5ACzs{{R30 delta 2591 zcmZYAe@so4b6_Wqo6T(zF@y|3pv9L{;3 z@AG{wnN449N&gbb88Wo}#5AHKWXuVCK9>*LnS5hr;(KV}Wt@#Sa5|11V@xT|Mg4s% zPQ`sV77t@J9>e+g0p{U#Y&9lrZqixJh56hqleh*y#^snj&Y11E66CkA)8z z(}=rKzaPR59KlK~D=;R4&8P`(MqPJ3)5g3`M-RM!b@(UpW2y@MnJz)CxEb}L7&hQ( zY{4IKC6*Q$Q-gbuOXei%zG1Aw?@*Z>#d$b=Jn!NCrk&0L+<|()acswra2aMz@E_QM zWsEllV+Xa8XHhGA8%e3T7>q}Q-%nxrA+9e&Wnu*%+PW@G>w!r+bMO@`!3${N2u{S? zn1k62*cFq9k75KV1JfF~9=X+Yp`H^*mC+HLj4uV_cTfvHJCXg@UVqO8t)P68e`6zR zyaiQE30#6FQG0wT7~exJptjh*uLCW{PoXAw0=9 z?hxkSYpCyoA)JqAQCs<~?+>6Rb{RPc z<~LL=-3=^$*grK310B>>oIx$%2TW_`cj%BkQ^ayq)wS4(F%08}n2VpFR(J`O`db*o z@+yCVFQPJZ3iZ76sFhzsW%fF10{1Z=>*kVwb((o<7VaP#3BC@zVgj!zoyvgHr>68h zXv&D19?VX9t%*++8Sn9$3VR))#VBK{BMyvNq5a=XM@=(a<@NkZ-$VaNVhy1$3$0j9 zD=H@RKVmgOw!PNIr;cI|5h2zRYC48$O9(AiO<7ditNmB1)oO{AUe7-|`UZQP$R_p^ z9A)o6p|(sdM5sPmh+Tx*ctW)hCA2kpgqpTP)&HQ`bW~&MeSGkr(rh5uPP38FIqfEN zthN#L1SR4X-7-|u5#8|TzUdkH?x9qE2==%8ho~TXIs^%_86g~*r5Tf zohb5peqnmuio=e3U?BBEh2466SKM#iC#PE)(lqVuPv`Q%*6rw|95TM z*4~zJ^F!frc5lqu@ARhZ{$$22m=TV2+Wmb_GU+5-D`vZn9jh5U6{*kNI?&zcq%xNx zRiR1Io}R>jo6@UePBPlvtD9~V|5R98U%jBAdch*Ac5!1}!{A8CYTU!28kLqy#N3J diff --git a/rest_framework/locale/nl/LC_MESSAGES/django.po b/rest_framework/locale/nl/LC_MESSAGES/django.po index 370f3aa412..5f7ff05b47 100644 --- a/rest_framework/locale/nl/LC_MESSAGES/django.po +++ b/rest_framework/locale/nl/LC_MESSAGES/django.po @@ -8,13 +8,14 @@ # Mike Dingjan , 2017 # Mike Dingjan , 2015 # Hans van Luttikhuizen , 2016 +# Tom Hendrikx , 2017 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Mike Dingjan \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Dutch (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -22,40 +23,40 @@ msgstr "" "Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Ongeldige basic header. Geen logingegevens opgegeven." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Ongeldige basic header. logingegevens kunnen geen spaties bevatten." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Ongeldige basic header. logingegevens zijn niet correct base64-versleuteld." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Ongeldige gebruikersnaam/wachtwoord." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Gebruiker inactief of verwijderd." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Ongeldige token header. Geen logingegevens opgegeven" -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Ongeldige token header. Token kan geen spaties bevatten." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Ongeldige token header. Token kan geen ongeldige karakters bevatten." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Ongeldige token." @@ -63,382 +64,515 @@ msgstr "Ongeldige token." msgid "Auth Token" msgstr "Autorisatietoken" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Key" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Gebruiker" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Aangemaakt" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokens" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Gebruikersnaam" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Wachtwoord" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Gebruikersaccount is gedeactiveerd." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Kan niet inloggen met opgegeven gegevens." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Moet \"username\" en \"password\" bevatten." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Er is een serverfout opgetreden." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Ongeldig samengestelde request." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Ongeldige authenticatiegegevens." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Authenticatiegegevens zijn niet opgegeven." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Je hebt geen toestemming om deze actie uit te voeren." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Niet gevonden." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Methode \"{method}\" niet toegestaan." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Kan niet voldoen aan de opgegeven Accept header." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Ongeldige media type \"{media_type}\" in aanvraag." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Aanvraag was verstikt." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Dit veld is vereist." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Dit veld mag niet leeg zijn." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" is een ongeldige booleanwaarde." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Dit veld mag niet leeg zijn." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Zorg ervoor dat dit veld niet meer dan {max_length} karakters bevat." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Zorg ervoor dat dit veld minimaal {min_length} karakters bevat." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Voer een geldig e-mailadres in." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." -msgstr "Deze waarde voldoet niet aan het vereisde formaat." +msgstr "Deze waarde voldoet niet aan het vereiste formaat." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Voer een geldige \"slug\" in, bestaande uit letters, cijfers, lage streepjes of streepjes." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Voer een geldige URL in." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" is een ongeldige UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Voer een geldig IPv4- of IPv6-adres in." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Een geldig getal is vereist." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Zorg ervoor dat deze waarde kleiner is dan of gelijk is aan {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Zorg ervoor dat deze waarde groter is dan of gelijk is aan {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Tekstwaarde is te lang." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Een geldig nummer is vereist." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Zorg ervoor dat er in totaal niet meer dan {max_digits} cijfers zijn." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Zorg ervoor dat er niet meer dan {max_decimal_places} cijfers achter de komma zijn." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Zorg ervoor dat er niet meer dan {max_whole_digits} cijfers voor de komma zijn." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datetime heeft een ongeldig formaat, gebruik 1 van de volgende formaten: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Verwachtte een datetime, maar kreeg een date." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Date heeft het verkeerde formaat, gebruik 1 van deze formaten: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Verwachtte een date, maar kreeg een datetime." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Time heeft het verkeerde formaat, gebruik 1 van onderstaande formaten: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Tijdsduur heeft een verkeerd formaat, gebruik 1 van onderstaande formaten: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" is een ongeldige keuze." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Meer dan {count} items..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Verwachtte een lijst met items, maar kreeg type \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Deze selectie mag niet leeg zijn." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" is niet een geldig pad." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Er is geen bestand opgestuurd." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "De verstuurde data was geen bestand. Controleer de encoding type op het formulier." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Bestandsnaam kon niet vastgesteld worden." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Het verstuurde bestand is leeg." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Zorg ervoor dat deze bestandsnaam hoogstens {max_length} karakters heeft (het heeft er {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Upload een geldige afbeelding, de geüploade afbeelding is geen afbeelding of is beschadigd geraakt," -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Deze lijst mag niet leeg zijn." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Verwachtte een dictionary van items, maar kreeg type \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Waarde moet valide JSON zijn." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Verzenden" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Zoek" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Sorteer op" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "oplopend" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "aflopend" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Ongeldige pagina." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Ongeldige cursor." -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Ongeldige pk \"{pk_value}\" - object bestaat niet." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Ongeldig type. Verwacht een pk-waarde, ontving {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Ongeldige hyperlink - Geen overeenkomende URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Ongeldige hyperlink - Ongeldige URL" -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Ongeldige hyperlink - Object bestaat niet." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Ongeldig type. Verwacht een URL, ontving {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Object met {slug_name}={value} bestaat niet." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Ongeldige waarde." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Ongeldige data. Verwacht een dictionary, kreeg een {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filters" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Veldfilters" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Sorteer op" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Zoek" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Geen" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Geen items geselecteerd." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Dit veld moet uniek zijn." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "De velden {field_names} moeten een unieke set zijn." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Dit veld moet uniek zijn voor de \"{date_field}\" datum." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Dit veld moet uniek zijn voor de \"{date_field}\" maand." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Dit veld moet uniek zijn voor de \"{date_field}\" year." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Ongeldige versie in \"Accept\" header." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Ongeldige versie in URL-pad." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Ongeldige versie in het URL pad, komt niet overeen met een geldige versie namespace" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Ongeldige versie in hostnaam." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Ongeldige versie in query parameter." - -#: views.py:88 -msgid "Permission denied." -msgstr "Toestemming geweigerd." diff --git a/rest_framework/locale/pl/LC_MESSAGES/django.mo b/rest_framework/locale/pl/LC_MESSAGES/django.mo index 436580b3566dd8aeb7a9b70494ada40df969625d..f265186aef076fe1a2729562bc13a39773879530 100644 GIT binary patch delta 2239 zcmYk-e@s8~<%am2K z-0oOyMa|}_XT(kJ-#D0m6bATv;*`d>d9uOWhw_+ahjQfC3rt>#e4B7)c0S&0(=J-;597A?{GE# zjq|ZM-E1RH=d}gOk{ZnTyOe;dZRRQRL6QWmbti(?2!Lz81-NAgEMs4Wd9OADNX>6woS0eRjU8o4`$Iv{p zCs4KVI;F21UqyxVH(Z4Q7STGT95`i7s8H`kt$PNw?#H+uui-9Cim}Qz2EEvYmr%u~ zZFfKf>oJZhzVGod3=+pC9QOSJpJkr1)VuI$NV9ZakO8O0WaTu04Y~e;y-T z-@awg#)*Y1y#;%by0N!VkI#ol^zCOX#2Iw3fJZ_RYC!JAUO}bi15`~+qEa;Fe?E)+ z)bh!fQqzDrdj1bG&UP&>Pg%2^e+s|EHU!Lng2$2YJUzrg@zvi(%d z!*#d}Yj6)Lb)%>a%plix^Ac023rUdfxEA&NK8b1gCZ^-Nn1so6rAoh6 z>HD2@Pw7v8c zdL6xruG}h(YN{Khu!~OJx#4d?9(%Vv#4$q8(Djv5Rk|9rWskTcFPHw%&7+D?`Cjgy zR1K&ucKdU(qErE@=)f=S+pb&e>_;7xEL%-j!);_*jyDVujFryQ(0TLHe42>Eoh4l9xLx0 zh#na{96i=QQ0MH54)r}c;A|T>*njltlTO2les2^x+;^mZs4tqB%^OdOPvwUb)r)@! XByyeh)cEnj#>D4^SCZqemX7}iAokpP delta 2596 zcmZYAS!`5Q9LMoPrz?d5EtIkpE)=jVt+dd^LQ5$KlosqlK@h38?X+HII=AV}P%BD9 z0xCibIsp@qh>4n*@Pfq}H;h1uF9cCACMGTkCPJbQJV-I@;lUIB^Ev0t<(}<- zre|Bf-a7PCcJeVp=_Y0pjXq-z;R~sJP)?^CGY8*63oqh)yos}LY=$v;xDfUK>u?6P z<5=8_Mfegf!E=~~zha#+L*^Ehb#yG@X&J`#_$k(6Ql>FmaSc}B30#5SVgqK4Go}i6 zpuRtbjd%qMaQb*-vatp=!Ddu{$aBb;H>l`^BUpxakbkByD?Zb5)QW3R19f2~zKyH! z2V8@B6O1Xwok*8CjC$?_7UFlPoxG2WaMnb|VSH0hWhrh$z2FtB$B(fZ$4rX9a1~Cc zy*bf#P%C*3wX(O6Ej6Dc+V>ORPhGZ-4-0u3&T_%!PhVsN6^A6n1g>} zGA7aBR7@H^irGjSn7V`w$fITp>ODQEG}?z#@nE8T3bo*Ob2xvU^$j|-g8a$x2dhx+ z7F061SdNELXM7>izK2>s$&~nWjcCz+7B#^`=p6h&cz&Nt%;Xw z|L0L@Mh&K-W+MC9@qhn1xT`CVT?l$2R;6m6VNaUke_=75FnMNoVB8 zuX;PKr9E^xQOP7D_R-OV8u%*mmAQ*LiYc?>TULtNv0bqQSCa00(F~4CA{)1&$#eQi8eYgo#s)JSi)10qxCbF}{gX(N-Nt7GZ9o&MJe0Q__?oyKgE{-?t)TnlNgo zj-w`iKGA<2NfUDylQE0Yxbh|km6Qd@Z$RJiJ<8`*6$pfB(^Di=;+8jhfn z>O8K&JIHaHa%Q0cTTn;lAZsu$<0d?V+wdN0p)IUV+5a96=>^xQu-oPjEW!!w^E#}? z6g+^b_$pT8>!@G0tEjU~XMeS#9%Q#o7+Jh|6Lkb%peFte>fR?adODUZX8$v&)X*^o zHOneO=c^Y!DB1-*wi3Wo8cttm5Bca9U2$_d>q^ouQ%`{ZB zb&q?s_%7_E{tU66V1C}r!TW93c`MtsOIoSQT4D>)Nods@2o+sVm2yI>R$=43^0dys zDbdii(z&Wwgp%bcB8liGbW~dj<%o)p;CIHm@7vWuQ6BN5Xl$a3NF!8qypz0I{KF4Q z&Y^Zb_)F;}3i)9+6Dx=gLif9ks2~;)N*)rDXbpT~@afjQIP*!=drBQ?i@<{k={&?6@JT z%O7(5UB$5@*%hg+(T-jxGJGMs&^Ouc>~y1{h=%TR!gfbc58a&dV^&E;(bCGIrOT|6 zsu`9V#r}wtoosK_Xt?ZS%i``(rt?lWxo#1ft)U#vqE*-Q6?EZ*5da~1@9&~%0 z&gVmy4#vLBn;H8kuWZi4H82@#w?7=Q`rN*LdmuE}YkBMVJ@0zT_$D`Kdkg8mI6N@? z*{tT&iFN&Uz>0d0I{wiE#j(u!wQ1W1M^6Tw-NPH_kN652+)%`|qNAS=Mgs0Y$O+i- T0T{^+L!{^Hobg&+P0mvTK{ diff --git a/rest_framework/locale/pl/LC_MESSAGES/django.po b/rest_framework/locale/pl/LC_MESSAGES/django.po index 6114265569..9e8d3eac31 100644 --- a/rest_framework/locale/pl/LC_MESSAGES/django.po +++ b/rest_framework/locale/pl/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: -# Janusz Harkot , 2015 +# Janusz Harkot , 2015 # Piotr Jakimiak , 2015 # m_aciek , 2016 # m_aciek , 2015-2016 @@ -11,9 +11,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: m_aciek \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Polish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -21,40 +21,40 @@ msgstr "" "Language: pl\n" "Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Niepoprawny podstawowy nagłówek. Brak danych uwierzytelniających." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Niepoprawny podstawowy nagłówek. Ciąg znaków danych uwierzytelniających nie powinien zawierać spacji." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Niepoprawny podstawowy nagłówek. Niewłaściwe kodowanie base64 danych uwierzytelniających." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Niepoprawna nazwa użytkownika lub hasło." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Użytkownik nieaktywny lub usunięty." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Niepoprawny nagłówek tokena. Brak danych uwierzytelniających." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Niepoprawny nagłówek tokena. Token nie może zawierać odstępów." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Błędny nagłówek z tokenem. Token nie może zawierać błędnych znaków." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Niepoprawny token." @@ -62,382 +62,515 @@ msgstr "Niepoprawny token." msgid "Auth Token" msgstr "Token uwierzytelniający" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Klucz" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Użytkownik" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Stworzono" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokeny" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Nazwa użytkownika" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Hasło" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Konto użytkownika jest nieaktywne." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Podane dane uwierzytelniające nie pozwalają na zalogowanie." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Musi zawierać \"username\" i \"password\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Wystąpił błąd serwera." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Zniekształcone żądanie." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Błędne dane uwierzytelniające." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Nie podano danych uwierzytelniających." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Nie masz uprawnień, by wykonać tę czynność." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Nie znaleziono." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Niedozwolona metoda \"{method}\"." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Nie można zaspokoić nagłówka Accept żądania." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Brak wsparcia dla żądanego typu danych \"{media_type}\"." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Żądanie zostało zdławione." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "To pole jest wymagane." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Pole nie może mieć wartości null." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" nie jest poprawną wartością logiczną." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "To pole nie może być puste." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Upewnij się, że to pole ma nie więcej niż {max_length} znaków." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Upewnij się, że pole ma co najmniej {min_length} znaków." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Podaj poprawny adres e-mail." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Ta wartość nie pasuje do wymaganego wzorca." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Wprowadź poprawną wartość pola typu \"slug\", składającą się ze znaków łacińskich, cyfr, podkreślenia lub myślnika." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Wprowadź poprawny adres URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" nie jest poprawnym UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Wprowadź poprawny adres IPv4 lub IPv6." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Wymagana poprawna liczba całkowita." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Upewnij się, że ta wartość jest mniejsza lub równa {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Upewnij się, że ta wartość jest większa lub równa {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Za długi ciąg znaków." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Wymagana poprawna liczba." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Upewnij się, że liczba ma nie więcej niż {max_digits} cyfr." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Upewnij się, że liczba ma nie więcej niż {max_decimal_places} cyfr dziesiętnych." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Upewnij się, że liczba ma nie więcej niż {max_whole_digits} cyfr całkowitych." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Wartość daty z czasem ma zły format. Użyj jednego z dostępnych formatów: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Oczekiwano datę z czasem, otrzymano tylko datę." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Data ma zły format. Użyj jednego z tych formatów: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Oczekiwano daty a otrzymano datę z czasem." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Błędny format czasu. Użyj jednego z dostępnych formatów: {format}" -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Czas trwania ma zły format. Użyj w zamian jednego z tych formatów: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" nie jest poprawnym wyborem." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Więcej niż {count} elementów..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Oczekiwano listy elementów, a otrzymano dane typu \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Zaznaczenie nie może być puste." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" nie jest poprawną ścieżką." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Nie przesłano pliku." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Przesłane dane nie były plikiem. Sprawdź typ kodowania formatki." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Nie można określić nazwy pliku." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Przesłany plik jest pusty." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Upewnij się, że nazwa pliku ma długość co najwyżej {max_length} znaków (aktualnie ma {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Prześlij poprawny plik graficzny. Przesłany plik albo nie jest grafiką lub jest uszkodzony." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Lista nie może być pusta." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Oczekiwano słownika, ale otrzymano \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Wartość musi być poprawnym ciągiem znaków JSON" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Wyślij" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Szukaj" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Kolejność" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "rosnąco" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "malejąco" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Niepoprawna strona." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Niepoprawny wskaźnik" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Błędny klucz główny \"{pk_value}\" - obiekt nie istnieje." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Błędny typ danych. Oczekiwano wartość klucza głównego, otrzymano {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Błędny hyperlink - nie znaleziono pasującego adresu URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Błędny hyperlink - błędne dopasowanie adresu URL." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Błędny hyperlink - obiekt nie istnieje." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Błędny typ danych. Oczekiwano adresu URL, otrzymano {data_type}" -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Obiekt z polem {slug_name}={value} nie istnieje" -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Niepoprawna wartość." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Niepoprawne dane. Oczekiwano słownika, otrzymano {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtry" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Pola filtrów" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Kolejność" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Szukaj" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "None" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Nie wybrano wartości." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Wartość dla tego pola musi być unikalna." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Pola {field_names} muszą tworzyć unikalny zestaw." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "To pole musi mieć unikalną wartość dla jednej daty z pola \"{date_field}\"." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "To pole musi mieć unikalną wartość dla konkretnego miesiąca z pola \"{date_field}\"." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "To pole musi mieć unikalną wartość dla konkretnego roku z pola \"{date_field}\"." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Błędna wersja w nagłówku \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Błędna wersja w ścieżce URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Niepoprawna wersja w ścieżce URL. Nie pasuje do przestrzeni nazw żadnej wersji." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Błędna wersja w nazwie hosta." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Błędna wersja w parametrach zapytania." - -#: views.py:88 -msgid "Permission denied." -msgstr "Brak uprawnień." diff --git a/rest_framework/locale/pt/LC_MESSAGES/django.mo b/rest_framework/locale/pt/LC_MESSAGES/django.mo index c88991bfa6fadadcefa6b6f09f12214ec7c5ff1d..653cce97ebb8df40090f661e659d22b6d322575d 100644 GIT binary patch literal 10382 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 diff --git a/rest_framework/locale/pt/LC_MESSAGES/django.po b/rest_framework/locale/pt/LC_MESSAGES/django.po index 9f1de19389..4cdf2bc4fc 100644 --- a/rest_framework/locale/pt/LC_MESSAGES/django.po +++ b/rest_framework/locale/pt/LC_MESSAGES/django.po @@ -3,13 +3,18 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Craig Blaszczyk , 2015 +# Ederson Mota Pereira , 2015 +# Filipe Rinaldi , 2015 +# Hugo Leonardo Chalhoub Mendonça , 2015 +# Jonatas Baldin , 2017 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2016-07-12 15:14+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Portuguese (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -17,423 +22,556 @@ msgstr "" "Language: pt\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." -msgstr "" +msgstr "Cabeçalho básico inválido. Credenciais não fornecidas." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." -msgstr "" +msgstr "Cabeçalho básico inválido. String de credenciais não deve incluir espaços." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." -msgstr "" +msgstr "Cabeçalho básico inválido. Credenciais codificadas em base64 incorretamente." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." -msgstr "" +msgstr "Usuário ou senha inválido." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." -msgstr "" +msgstr "Usuário inativo ou removido." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." -msgstr "" +msgstr "Cabeçalho de token inválido. Credenciais não fornecidas." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." -msgstr "" +msgstr "Cabeçalho de token inválido. String de token não deve incluir espaços." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." -msgstr "" +msgstr "Cabeçalho de token inválido. String de token não deve possuir caracteres inválidos." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." -msgstr "" +msgstr "Token inválido." #: authtoken/apps.py:7 msgid "Auth Token" -msgstr "" +msgstr "Token de autenticação" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" -msgstr "" +msgstr "Chave" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "Usuário" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" -msgstr "" +msgstr "Criado" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" -msgstr "" +msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" -msgstr "" +msgstr "Tokens" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "Nome do usuário" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" - -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "" +msgstr "Senha" -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." -msgstr "" +msgstr "Impossível fazer login com as credenciais fornecidas." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." -msgstr "" +msgstr "Obrigatório incluir \"usuário\" e \"senha\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." +msgstr "Ocorreu um erro de servidor." + +#: exceptions.py:142 +msgid "Invalid input." msgstr "" -#: exceptions.py:84 +#: exceptions.py:161 msgid "Malformed request." -msgstr "" +msgstr "Pedido malformado." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." -msgstr "" +msgstr "Credenciais de autenticação incorretas." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." -msgstr "" +msgstr "As credenciais de autenticação não foram fornecidas." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." -msgstr "" +msgstr "Você não tem permissão para executar essa ação." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." -msgstr "" +msgstr "Não encontrado." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." -msgstr "" +msgstr "Método \"{method}\" não é permitido." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." -msgstr "" +msgstr "Não foi possível satisfazer a requisição do cabeçalho Accept." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." -msgstr "" +msgstr "Tipo de mídia \"{media_type}\" no pedido não é suportado." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." +msgstr "Pedido foi limitado." + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." msgstr "" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 -msgid "This field is required." +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." msgstr "" -#: fields.py:270 +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "Este campo é obrigatório." + +#: fields.py:317 msgid "This field may not be null." +msgstr "Este campo não pode ser nulo." + +#: fields.py:701 +msgid "Must be a valid boolean." msgstr "" -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." +#: fields.py:766 +msgid "Not a valid string." msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." -msgstr "" +msgstr "Este campo não pode ser em branco." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." -msgstr "" +msgstr "Certifique-se de que este campo não tenha mais de {max_length} caracteres." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." -msgstr "" +msgstr "Certifique-se de que este campo tenha mais de {min_length} caracteres." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." -msgstr "" +msgstr "Insira um endereço de email válido." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." -msgstr "" +msgstr "Este valor não corresponde ao padrão exigido." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." +msgstr "Entrar um \"slug\" válido que consista de letras, números, sublinhados ou hífens." + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." msgstr "" -#: fields.py:747 +#: fields.py:854 msgid "Enter a valid URL." -msgstr "" +msgstr "Entrar um URL válido." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." +#: fields.py:867 +msgid "Must be a valid UUID." msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "" +msgstr "Informe um endereço IPv4 ou IPv6 válido." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." -msgstr "" +msgstr "Um número inteiro válido é exigido." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." -msgstr "" +msgstr "Certifique-se de que este valor seja inferior ou igual a {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." -msgstr "" +msgstr "Certifque-se de que este valor seja maior ou igual a {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." -msgstr "" +msgstr "Valor da string é muito grande." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." -msgstr "" +msgstr "Um número válido é necessário." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." -msgstr "" +msgstr "Certifique-se de que não haja mais de {max_digits} dígitos no total." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." -msgstr "" +msgstr "Certifique-se de que não haja mais de {max_decimal_places} casas decimais." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." -msgstr "" +msgstr "Certifique-se de que não haja mais de {max_whole_digits} dígitos antes do ponto decimal." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "Formato inválido para data e hora. Use um dos formatos a seguir: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." +msgstr "Necessário uma data e hora mas recebeu uma data." + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." msgstr "" -#: fields.py:1103 -msgid "Date has wrong format. Use one of these formats instead: {format}." +#: fields.py:1151 +msgid "Datetime value out of range." msgstr "" -#: fields.py:1104 +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "Formato inválido para data. Use um dos formatos a seguir: {format}." + +#: fields.py:1237 msgid "Expected a date but got a datetime." -msgstr "" +msgstr "Necessário uma data mas recebeu uma data e hora." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "Formato inválido para Tempo. Use um dos formatos a seguir: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "Formato inválido para Duração. Use um dos formatos a seguir: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." -msgstr "" +msgstr "\"{input}\" não é um escolha válido." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." -msgstr "" +msgstr "Mais de {count} itens..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." -msgstr "" +msgstr "Necessário uma lista de itens, mas recebeu tipo \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." -msgstr "" +msgstr "Esta seleção não pode estar vazia." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." -msgstr "" +msgstr "\"{input}\" não é uma escolha válida para um caminho." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." -msgstr "" +msgstr "Nenhum arquivo foi submetido." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." -msgstr "" +msgstr "O dado submetido não é um arquivo. Certifique-se do tipo de codificação no formulário." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." -msgstr "" +msgstr "Nome do arquivo não pode ser determinado." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." -msgstr "" +msgstr "O arquivo submetido está vázio." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." -msgstr "" +msgstr "Certifique-se de que o nome do arquivo tem menos de {max_length} caracteres (tem {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." -msgstr "" +msgstr "Fazer upload de uma imagem válida. O arquivo enviado não é um arquivo de imagem ou está corrompido." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." +msgstr "Esta lista não pode estar vazia." + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." msgstr "" -#: fields.py:1502 +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "Esperado um dicionário de itens mas recebeu tipo \"{input_type}\"." + +#: fields.py:1683 +msgid "This dictionary may not be empty." msgstr "" -#: fields.py:1549 +#: fields.py:1755 msgid "Value must be valid JSON." +msgstr "Valor devo ser JSON válido." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Buscar" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Ordenando" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" -msgstr "" +msgstr "ascendente" -#: filters.py:337 +#: filters.py:288 msgid "descending" +msgstr "descendente" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." msgstr "" -#: pagination.py:193 +#: pagination.py:189 msgid "Invalid page." +msgstr "Página inválida." + +#: pagination.py:374 +msgid "The initial index from which to return the results." msgstr "" -#: pagination.py:427 -msgid "Invalid cursor" +#: pagination.py:581 +msgid "The pagination cursor value." msgstr "" -#: relations.py:207 +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "Cursor inválido" + +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "" +msgstr "Pk inválido \"{pk_value}\" - objeto não existe." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." -msgstr "" +msgstr "Tipo incorreto. Esperado valor pk, recebeu {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." -msgstr "" +msgstr "Hyperlink inválido - Sem combinação para a URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." -msgstr "" +msgstr "Hyperlink inválido - Combinação URL incorreta." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." -msgstr "" +msgstr "Hyperlink inválido - Objeto não existe." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." -msgstr "" +msgstr "Tipo incorreto. Necessário string URL, recebeu {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." -msgstr "" +msgstr "Objeto com {slug_name}={value} não existe." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." +msgstr "Valor inválido." + +#: schemas/utils.py:32 +msgid "unique integer value" msgstr "" -#: serializers.py:326 -msgid "Invalid data. Expected a dictionary, but got {datatype}." +#: schemas/utils.py:34 +msgid "UUID string" msgstr "" +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "Dado inválido. Necessário um dicionário mas recebeu {datatype}." + #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" +msgstr "Filtra" + +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:78 +msgid "request form" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:157 +msgid "main content" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 -msgid "None" +#: templates/rest_framework/base.html:173 +msgid "request info" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 -msgid "No items to select." +#: templates/rest_framework/base.html:177 +msgid "response info" msgstr "" -#: validators.py:43 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "Nenhum(a/as)" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "Nenhum item para escholher." + +#: validators.py:39 msgid "This field must be unique." -msgstr "" +msgstr "Esse campo deve ser único." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." +msgstr "Os campos {field_names} devem criar um set único." + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." msgstr "" -#: validators.py:245 +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." -msgstr "" +msgstr "O campo \"{date_field}\" deve ser único para a data." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." -msgstr "" +msgstr "O campo \"{date_field}\" deve ser único para o mês." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." -msgstr "" +msgstr "O campo \"{date_field}\" deve ser único para o ano." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." -msgstr "" +msgstr "Versão inválida no cabeçalho \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." -msgstr "" +msgstr "Versão inválida no caminho de URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "Versão inválida no caminho da URL. Não corresponde a nenhuma versão do namespace." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." -msgstr "" +msgstr "Versão inválida no hostname." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." -msgstr "" - -#: views.py:88 -msgid "Permission denied." -msgstr "" +msgstr "Versão inválida no parâmetro de query." diff --git a/rest_framework/locale/pt_BR/LC_MESSAGES/django.mo b/rest_framework/locale/pt_BR/LC_MESSAGES/django.mo index 008629823ddf0a55141d9d8e1bcae86c9d7189a6..5a6e3788e651dfed18d4d5cb4efe1ab658b6ed59 100644 GIT binary patch delta 2242 zcmYk-YfRO39LMqR;cyU+`6Yy@8t1JZdR_bFYJ| zo#*}N<^B|E1s=r>ID=~Mbu7b=F%N&nn4d}--_FJ|OvOfI)vX1$Vi&TF_L%FFs0LtMA;%gYdE2u5~12y0#8rSm?)b&wR&Ya0+{p+c`%8le+qptIFoDuIseeogm<4M$j zUO}zE0{XBp*BM9&ZsEEdYj6NZ@C0gN-(o&4q9*iDF6*xw=}e~gY`LCxe7?8TH~$9@cPeICg}`wBJFpHLmu_?;C!h-&Y7BpvJn)D~XDM*25z z2}gt*&De=Apc?!hnT-AJUau;38Y;n?x!;9@cmx@nU3H%?qH-dOBcl#;Q7cf6?1uHA zR%jH9bpD@pZ(KrF)xJdD4R!-HkW_k8Qsts@qZQRb7m|P0huYf*(2oyeEuKMb)n#N3 zR>UaOelyavg>eJ@Ta3y!Jm>m3s(~~zODoWTYG4R?WYef6JcsT08Tv4X!<~i&s3a{x zme_`I6rV$7{a?tgn~%3uOf%d_MIF^)9rmHJ{{*hUXV8OQqMOjNE|oo0I8Di-x13gJ zfKbO;HI)WJTckWXKJ_fy+jAWX-}5De6|po>R)@Sj8q|4tS4D>_R_5;e_*udyNP1=W)&)- z!tS+>S+85)iP}~M5yERyi9tMtUf`Fj&r^MCZEL?;sO V7ys&uU)j`{@NGWkjlWoN_Fw;|*lYj* delta 2599 zcmZYATWl0n9LMp~%l1MkmqKYlTZXnk!M3zuTPVdTz_*{^WNhxn~UR0P!fXDaV*ocr1?(+S>)j%)|3&;W*C6J6MU63XLhpMX29* z;v={ZC*e`7$LFyQFJnIbfStx9&ChgtI8evgGKTB$8m__YB4c)77q;SAT#2`FBTkuY zOdAfMzCVMTa007wPO&j1*p8au7SwUqGil7LbacTh*o?m+f2MXyW~MEujN4H+3gU8n z3p?;@?85S?#x&qwTd7IE;d=NFkQ|PS-1{gm?O(>h$J%)v|SpOzE%^cW` z!>CMe;B36*`#mbt2mUyRbe1x%KU<-z^3NN4%yp^Q0 zfXRtb-~|JMOMK&YL6RnCGN+aIEEYW zKI(cMJR&4*`cUH$)OAi^4fSU((%H^|2_z{~U6VPX6IC=})D5Gkl{krdGX1C<+o+m|Vj;eX0q$=u($NjxM+(~9KrP{yXyLd1_%GB-1QulW za5=IV<~7s}uOK;?+o(kTz-3s)=uaJ^WV0Hf!-O_{=sKZb;E<Qp4=q0AALsR8?t?H=b9!kWIS6NQXawBoewhr52Cuj{uBH@tjHl%6` z)>l*=WVT;DqC$Xe{j(&kdCHg`y))EapU9D;RQ}P_QBO zVo7V>w#49w6HnhPsm+;b4-G{UZd|txIx%}Ntds7Pelw-1wZ3U-ePfH&xT39jS!!Zd zS+tw_5>6+|e+|sAnX5Hqj~t4a9+HXLrYrSlc}4oU%G-fyUOxJ< z8ydEQc0;PKraiCQJ?z-g^h-5Ia^`kL+_-mzL@Y6WKI%lc8fiIUC+?k}Tl>a8W`R3^ diff --git a/rest_framework/locale/pt_BR/LC_MESSAGES/django.po b/rest_framework/locale/pt_BR/LC_MESSAGES/django.po index 9489f20a01..40651552d7 100644 --- a/rest_framework/locale/pt_BR/LC_MESSAGES/django.po +++ b/rest_framework/locale/pt_BR/LC_MESSAGES/django.po @@ -3,6 +3,7 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Cloves Oliveira , 2020 # Craig Blaszczyk , 2015 # Ederson Mota Pereira , 2015 # Filipe Rinaldi , 2015 @@ -12,9 +13,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-12-06 09:53+0000\n" -"Last-Translator: Jonatas Baldin \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Portuguese (Brazil) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/pt_BR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -22,40 +23,40 @@ msgstr "" "Language: pt_BR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Cabeçalho básico inválido. Credenciais não fornecidas." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Cabeçalho básico inválido. String de credenciais não deve incluir espaços." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Cabeçalho básico inválido. Credenciais codificadas em base64 incorretamente." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Usuário ou senha inválido." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Usuário inativo ou removido." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Cabeçalho de token inválido. Credenciais não fornecidas." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Cabeçalho de token inválido. String de token não deve incluir espaços." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Cabeçalho de token inválido. String de token não deve possuir caracteres inválidos." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Token inválido." @@ -63,382 +64,515 @@ msgstr "Token inválido." msgid "Auth Token" msgstr "Token de autenticação" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Chave" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Usuário" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Criado" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokens" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Nome do usuário" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Senha" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Conta de usuário desabilitada." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Impossível fazer login com as credenciais fornecidas." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Obrigatório incluir \"usuário\" e \"senha\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Ocorreu um erro de servidor." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Pedido malformado." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Credenciais de autenticação incorretas." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "As credenciais de autenticação não foram fornecidas." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Você não tem permissão para executar essa ação." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Não encontrado." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Método \"{method}\" não é permitido." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Não foi possível satisfazer a requisição do cabeçalho Accept." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Tipo de mídia \"{media_type}\" no pedido não é suportado." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Pedido foi limitado." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Este campo é obrigatório." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Este campo não pode ser nulo." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" não é um valor boleano válido." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Este campo não pode ser em branco." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Certifique-se de que este campo não tenha mais de {max_length} caracteres." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Certifique-se de que este campo tenha mais de {min_length} caracteres." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Insira um endereço de email válido." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Este valor não corresponde ao padrão exigido." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Entrar um \"slug\" válido que consista de letras, números, sublinhados ou hífens." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Entrar um URL válido." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" não é um UUID válido." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Informe um endereço IPv4 ou IPv6 válido." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Um número inteiro válido é exigido." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Certifique-se de que este valor seja inferior ou igual a {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Certifque-se de que este valor seja maior ou igual a {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Valor da string é muito grande." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Um número válido é necessário." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Certifique-se de que não haja mais de {max_digits} dígitos no total." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Certifique-se de que não haja mais de {max_decimal_places} casas decimais." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Certifique-se de que não haja mais de {max_whole_digits} dígitos antes do ponto decimal." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Formato inválido para data e hora. Use um dos formatos a seguir: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Necessário uma data e hora mas recebeu uma data." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Formato inválido para data. Use um dos formatos a seguir: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Necessário uma data mas recebeu uma data e hora." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Formato inválido para Tempo. Use um dos formatos a seguir: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Formato inválido para Duração. Use um dos formatos a seguir: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" não é um escolha válido." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Mais de {count} itens..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Necessário uma lista de itens, mas recebeu tipo \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Esta seleção não pode estar vazia." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" não é uma escolha válida para um caminho." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Nenhum arquivo foi submetido." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "O dado submetido não é um arquivo. Certifique-se do tipo de codificação no formulário." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Nome do arquivo não pode ser determinado." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "O arquivo submetido está vázio." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Certifique-se de que o nome do arquivo tem menos de {max_length} caracteres (tem {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Fazer upload de uma imagem válida. O arquivo enviado não é um arquivo de imagem ou está corrompido." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Esta lista não pode estar vazia." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Esperado um dicionário de itens mas recebeu tipo \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Valor devo ser JSON válido." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Enviar" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Buscar" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Ordenando" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "ascendente" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "descendente" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Página inválida." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Cursor inválido" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Pk inválido \"{pk_value}\" - objeto não existe." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Tipo incorreto. Esperado valor pk, recebeu {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Hyperlink inválido - Sem combinação para a URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Hyperlink inválido - Combinação URL incorreta." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Hyperlink inválido - Objeto não existe." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Tipo incorreto. Necessário string URL, recebeu {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objeto com {slug_name}={value} não existe." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Valor inválido." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Dado inválido. Necessário um dicionário mas recebeu {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" -msgstr "Filtra" +msgstr "Filtros" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Filtra de campo" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Ordenando" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Buscar" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Nenhum(a/as)" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Nenhum item para escholher." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Esse campo deve ser único." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Os campos {field_names} devem criar um set único." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "O campo \"{date_field}\" deve ser único para a data." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "O campo \"{date_field}\" deve ser único para o mês." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "O campo \"{date_field}\" deve ser único para o ano." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Versão inválida no cabeçalho \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Versão inválida no caminho de URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Versão inválida no caminho da URL. Não corresponde a nenhuma versão do namespace." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Versão inválida no hostname." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Versão inválida no parâmetro de query." - -#: views.py:88 -msgid "Permission denied." -msgstr "Permissão negada." diff --git a/rest_framework/locale/ro/LC_MESSAGES/django.mo b/rest_framework/locale/ro/LC_MESSAGES/django.mo index 0d6f6f942f6f06ee70e7c98f5953e43e51ad93df..0c9fb9c560564aef7eedc70fb9bb634d02ccf74a 100644 GIT binary patch delta 2438 zcmYk-eQXp(7{~D`SIQLuE%b$2puj|^bXoy0WrA* zkOm0ExMBojqQ)2^5MNG%O87&>h*1)aAwYr(CX%R$qTwH6jDCN+i*eGq&&=%G>@&}7 zZ#P|Dll*>o`Y}Vi7*@irGVq8IKVh zj?bbd_);+bLEv@N{r<)ZlQd?|P=AJT)En+ZJ>V651h3&d%o=7)9j?L>Jcj(4i~Q)k zZ?PDMJZQ{VtiUO_4!MQdi>3G+mf$DY!1K)=8gsGgA^(QE@L~G>LH~8so4t#AqbsOT z{u%Tuvi#$#F~oQuDgsaA7(9Zy-)WqLpJOimj!8=+gR@8ABuvL@B0Q)>Yq+_NZ$R~TpmOHu2;yH!<1GffwMO;xv;7&bM4i}) z7W+{XI*E$FH5`ZqIsQb(;#B&@ScXlw0S};F>?Y>pPpB9ACx`fJAk6E`#BoSInr2i4 zTJeiCW1d3gMmedkwQa%C_zWt`Bm!F9L^PbFzI)5v6j z7GoOHC&#DGy*Jz8!cQ8|f!U zGkrav1GScC(Bk{3tiOVfVosqk>#+$t@C?>tuJx04Bd(&~iJHK9R78J8O<)XRBOS~l zRQ9(ZOPDkVX*|imSGW#mjPq~Uj}z&?h`R7R>PBB9rHG!SReDKWS#6B2|N|o~aAQHzB8* zEvN?^MlJ1UsHG_2ZHT(5L{`Z(NuF`F*;b63r{iL0WNrkA9hGxDAb>Uvj#1ByyT);F8QI&MM=6+d3 zBPZx6YPzn8T20kfR8cfYBX5gu##X8(MoAhzVqVdM`5<{&yw)G`et9`>FKZilvk#87 zU_Dhyr|=SGFMpPx3jY?oqU{zA+6$1cv{w}IXNo-)8WN5{a+ye`O`D zc|mUg&ZiD?yTYU0)8UfTy>QyVRCw46Avc+oS!5T)ogN#D_u8&VZ(E0r#CF?0r@JQ_ zkJ(tH%jwx3X>(HjSuJVq-Qks~vJsO)Zg*~HcBH4xiM2;#TTQ!jzvGU%G zX4$eiRpk|KZC;*TAL)xa-F8KHdwfS?x2@R~_gl5wqOo{iG?LnxH$Tn&Ze(SuJb!jZ zYENM@y{;{?&579N)k{n4@`UYi?DlwXceKyBbiCKGZ3!ETwnY;$%k(b298K7E$Ho#} zj*V>dXP<~gqwcNo)dMc>b4N`$TbQsByCvS!Yq?2Rlo#f5%k^8LmyRJ)_eaw)%+Kf(I_zh delta 2605 zcmZY9S!`5Q9LMofrj(X$($WQia_Iu4($dlbr7VS(rKRj`kwup2^j0pJ&d|;bCZL%I z1%wzN85E472`Cc6i;jsgCL~l1Zq-QChye^yiSa>Wga`>Het$DpiSeZW`J8im?zv|l z?!46%y^)vtydn9B2}Fa(m_xWSjUUqPbYmvt09qKs3Va(&@H;HVd#LyGGK?9IGjKF^ zU^Q;Q>G%we!Z)$Sn5cP|%rYL_cWvdZ<&+QOBD{hdFhA3nIk*ew;c4u^8`y+ZS;_y~ zxRUZIEW=+g4|B4U9h{7MzA$0bm>x1}@Bq%jA>_~84zeF{dJI0uYu>@6~ ziyBEAYU=&2hur69(ZlmkP&4p3YRPV*+R?-4R2)yH5L?m0&6tn-Fcps?t6+}f415(? zZu5ofx5%Hl$&cE(joLn$G*X0lsB#t7VjXIUd-7R-_58eB@iMCXJ!%u($JtmsHo3NI zQ00TD5nM#oyNVXxL3OaOAhEg3G8{?y5UK-5@ezCh>u{)m`QJq5HV-t?)pUOxcDnYX zMtaaKAH$iH&*R4_##~1&!5bU_or+IUYy3M}m``I9u>m#p>)rAI@~Sx+B~wl2EH>a* zs2*o9yiUx;t?0vUyo7C7#IQAmJ8(Om!zGx`^l9&`LK{z@PQzcg3RlpZ$M7WTyU{yj zXw#I@J?+wkI2#{#eFaxg{u=cGYZ9G88#Sd7wD3u6##6Wuuj59XU>UO$Be)cApf+zg z)3i?Kzn+YGvL7|Iaa2ccA^XVWmL^X{DRN<$b=ZT)@lpH})!-V|VG3?Ty|*9L&Pk*x zbIC3LfHjn}$~Dtb&ORBvuo!8}bfJY$;9NZEmapJ+%738Fuf>9AV+XQ*%qGApTV7PZ!^Q7^oPtdhBiETg&RdJpGQF60c$ zPS<0o5f3ASHNPQMO+M41rENmBKY-DZWG<6Q$6=g>-=HdHO*5t!TT$o#9MV@4N4A5x ziki8*$fTG|rbX}9VhU~`G$2l~;g^pG5Gz#3o*ZYY8RI*pfspxxcoM-%czebO-1nN_=0Nr-0B}E+sTVrB*^mu!qnn zmlH~>lO$$Hv!=uZB$AHS8aJ;Crp3)$sEcVcF@jLy#F}o6UsI{%A#`2p_~_^;arGN6 zHM4{85u*qttuNR3e^M%?ZA2Eal3>{rXTAx&gzlFvLP`69<7cAUI7$nMO^MutpJuX! zM49_&E2a@zYo*1+ghVcRJu9oDrA@2UNUSG1R3Nnxx;vC+CZXxleNJF^B>YsV<%Fz2 zFl>3PJzl@lYxM+!e%l+UiB+bzO)TBV!-)O=THW34OX7ia&&W*M-)r?b{;)k5iU+b! zc;animZs#bwFmp1P{;`etX@0d*u6Ee=kn&Hbw+ynop5|8uhKKt>+=O8fv~>c>x8^L zepULhU?8@uusf%2PIYZ#b#1*>*U&VpF*aOSWOeNf_IpEC>&`(Z6n1QDemK~_i14`& z;;BU~DPtmme@2iTPfe`8xP8)taRefk9SYkP^)!ZLrTE_BxoH`e@BP#LyCZ{6{6fir z)Yy@-(%4&N@02(`N45K$k6v`_L5l{wR$nj_wn7nx7<;z-y@KRuok+m82JODcUT^q= zr)y#@73~?F)Ct=ko^fJ_D_$BAio`3Ycsv!Y!9Y0TXJ)KO*zr5m4-O`~)obtd`ohj0 LZ@4B_HSNgXP18*# diff --git a/rest_framework/locale/ro/LC_MESSAGES/django.po b/rest_framework/locale/ro/LC_MESSAGES/django.po index d144d847ef..0c9e900e3f 100644 --- a/rest_framework/locale/ro/LC_MESSAGES/django.po +++ b/rest_framework/locale/ro/LC_MESSAGES/django.po @@ -3,14 +3,15 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Bogdan Mateescu, 2019 # Elena-Adela Neacsu , 2016 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Romanian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ro/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -18,40 +19,40 @@ msgstr "" "Language: ro\n" "Plural-Forms: nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Antet de bază invalid. Datele de autentificare nu au fost furnizate." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Antet de bază invalid. Şirul de caractere cu datele de autentificare nu trebuie să conțină spații." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Antet de bază invalid. Datele de autentificare nu au fost corect codificate în base64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Nume utilizator / Parolă invalid(ă)." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Utilizator inactiv sau șters." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Antet token invalid. Datele de autentificare nu au fost furnizate." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Antet token invalid. Şirul de caractere pentru token nu trebuie să conțină spații." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Antet token invalid. Şirul de caractere pentru token nu trebuie să conțină caractere nevalide." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Token nevalid." @@ -59,382 +60,515 @@ msgstr "Token nevalid." msgid "Auth Token" msgstr "Token de autentificare" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Cheie" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Utilizator" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Creat" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokenuri" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Nume de utilizator" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Parola" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Contul de utilizator este dezactivat." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Nu se poate conecta cu datele de conectare furnizate." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Trebuie să includă \"numele de utilizator\" și \"parola\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "A apărut o eroare pe server." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Cerere incorectă." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Date de autentificare incorecte." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Datele de autentificare nu au fost furnizate." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Nu aveți permisiunea de a efectua această acțiune." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Nu a fost găsit(ă)." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Metoda \"{method}\" nu este permisa." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Antetul Accept al cererii nu a putut fi îndeplinit." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Cererea conține tipul media neacceptat \"{media_type}\"" -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Cererea a fost gâtuită." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Acest câmp este obligatoriu." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Acest câmp nu poate fi nul." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" nu este un boolean valid." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Acest câmp nu poate fi gol." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Asigurați-vă că acest câmp nu are mai mult de {max_length} caractere." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Asigurați-vă că acest câmp are cel puțin{min_length} caractere." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Introduceți o adresă de email validă." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Această valoare nu se potrivește cu şablonul cerut." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Introduceți un \"slug\" valid format din litere, numere, caractere de subliniere sau cratime." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Introduceți un URL valid." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" nu este un UUID valid." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Introduceți o adresă IPv4 sau IPv6 validă." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Este necesar un întreg valid." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Asigurați-vă că această valoare este mai mică sau egală cu {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Asigurați-vă că această valoare este mai mare sau egală cu {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Valoare șir de caractere prea mare." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Este necesar un număr valid." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Asigurați-vă că nu există mai mult de {max_digits} cifre în total." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Asigurați-vă că nu există mai mult de {max_decimal_places} zecimale." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Asigurați-vă că nu există mai mult de {max_whole_digits} cifre înainte de punctul zecimal." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Câmpul datetime are format greșit. Utilizați unul dintre aceste formate în loc: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Se aștepta un câmp datetime, dar s-a primit o dată." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Data are formatul greșit. Utilizați unul dintre aceste formate în loc: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Se aștepta o dată, dar s-a primit un câmp datetime." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Timpul are formatul greșit. Utilizați unul dintre aceste formate în loc: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Durata are formatul greșit. Utilizați unul dintre aceste formate în loc: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" nu este o opțiune validă." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Mai mult de {count} articole ..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Se aștepta o listă de elemente, dar s-a primit tip \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Această selecție nu poate fi goală." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" nu este o cale validă." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." -msgstr "Nici un fișier nu a fost sumis." +msgstr "Nu a fost trimis nici un fișier." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Datele prezentate nu sunt un fișier. Verificați tipul de codificare de pe formular." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Numele fișierului nu a putut fi determinat." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." -msgstr "Fișierul sumis este gol." +msgstr "Fișierul trimis este gol." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Asigurați-vă că acest nume de fișier are cel mult {max_length} caractere (momentan are {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Încărcați o imagine validă. Fișierul încărcat a fost fie nu o imagine sau o imagine coruptă." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Această listă nu poate fi goală." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Se aștepta un dicționar de obiecte, dar s-a primit tipul \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Valoarea trebuie să fie JSON valid." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Sumiteţi" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Căutare" -#: filters.py:336 -msgid "ascending" +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Ordonare" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:337 +#: filters.py:287 +msgid "ascending" +msgstr "ascendent" + +#: filters.py:288 msgid "descending" +msgstr "descendent" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." msgstr "" -#: pagination.py:193 +#: pagination.py:189 msgid "Invalid page." msgstr "Pagină nevalidă." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Cursor nevalid" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Pk \"{pk_value}\" nevalid - obiectul nu există." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Tip incorect. Se aștepta un pk, dar s-a primit \"{data_type}\"." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Hyperlink nevalid - Nici un URL nu se potrivește." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Hyperlink nevalid - Potrivire URL incorectă." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Hyperlink nevalid - Obiectul nu există." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Tip incorect. Se aștepta un URL, dar s-a primit \"{data_type}\"." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Obiectul cu {slug_name}={value} nu există." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Valoare nevalidă." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Date nevalide. Se aștepta un dicționar de obiecte, dar s-a primit \"{datatype}\"." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtre" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Filtre câmpuri" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Ordonare" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Căutare" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Nici unul/una" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Nu există elemente pentru a fi selectate." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Acest câmp trebuie să fie unic." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Câmpurile {field_names} trebuie să formeze un set unic." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Acest câmp trebuie să fie unic pentru data \"{date_field}\"." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Acest câmp trebuie să fie unic pentru luna \"{date_field}\"." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Acest câmp trebuie să fie unic pentru anul \"{date_field}\"." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Versiune nevalidă în antetul \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Versiune nevalidă în calea URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "Versiune nevalidă în calea URL. Nu se potrivește cu niciun spațiu de nume al versiunii." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Versiune nevalidă în numele de gazdă." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." -msgstr "Versiune nevalid în parametrul de interogare." - -#: views.py:88 -msgid "Permission denied." -msgstr "Permisiune refuzată." +msgstr "Versiune nevalidă în parametrul de interogare." diff --git a/rest_framework/locale/ru/LC_MESSAGES/django.mo b/rest_framework/locale/ru/LC_MESSAGES/django.mo index 85918d65abc9769451f01eb9ecc1add82728bfcb..82688a41205544a65abef56ffcbdd70f883c70f4 100644 GIT binary patch delta 2405 zcmZYAeN0tl9Ki7hE-x2_EAIsOa?lh|AYKF!C_)?)U2rJAt=Yw!(}&u6g((-W0i zhHgy7V<-!J*}ndv?T;w)-N#B5P-<egPngjQ69|%;_}pUq$CVE|_PHGR{u7R=fq} z#(vcBILd_*u@5GBReaXsG0ow$ztQ*Y{ZJb=?Uj!Oe< zZ;(z9<${a+bl?<@;4SRM_C?k!dI{zJ91eCHp0xcH&oQoDVlDJ4N-^eY6f8DjEuOQz ziXO%R7o{pI96_mO11s?swqeFnrH)`99>X~#X=;#)^x_>{fn5)hB|L)N_y=yrmgQE) zUd2MjbJ&mRJTxj(9j5aV7yiNtJVE|=R>iS=nP3&lYcz)I@jO=H9LoK9%vz5@Y{r}D z#6liot*RW0umk0_JBo5tGq^&w@;jY6E+kVq#kd*etR|59QZL{m7({suXYJ2rRw=S1 zoQL%&HL(c~;y7-{yC^$vW^$>G11K8^Vu_^k8XdXO!SZ(DE|l>lT#Kp9Dhq5u`GZp^ zXa6b6^J%uFR}7qqO-J>PeJ(kk&-F%$}9eA~S#X1wpc~!ydC~v$EwV z)wg)!L0QL=8d0%x}caC~X1JM#!ryHM57|m5%cY+l!C}2A}y9q zT4aDy5B2sq8XJjteZaX$pLUjn{&q&ihMcZ54n2^Puv}~MdxkZie?%K_kM#6uZr_+T z>KPjL`hA+uJ>VH0boY2d$5XnZ^n0mQp|Z4mhdz|qpY9&+@%VbZzJ03K6Y1)+nVqik z(&93HDzvh-HLEN2hO8`YyL;5@8PZyZdj0zkjA?a;{8q2w8L!Vj>UD=Evc8GZU&-zX zt;v~jhN71Z#Ou{76CIin)ZZyxyv+FA_*gS88z0cQYE;csdAi(GQnD0iUT+wBPbK5XDT%wC!w@w zqdZ9?%GP^h4#xW5K@}Kcr=SkvbbJFj zZgnl@b>vUo;Ue?gM@gSlCdow$O1lKhu>$3Y9a)^eto&qb;1!hiZImQ>h_lc-HhQ)# zDD451Cm2B)_akcf0A+zW*+z1yC73{a5M=?w_&6TM3cQ@n{;#8QpALD_W|lt=TVr;i zJn2BJ{R+;YeG*IKl==yuqMgc@8qtX@_#R5GJVaSgHLI0eX-7%o<0w0SJ4mITN)nH; z0h^G7RqtXSeuU4Wjcw`1<0x(GM5Wp=gaN#VF5JigZ^qA1#urYGF7Qyy-|;a0^;&en z!JAYhJBvuoRoIMk@y(bw(M{W05MAIdZHgnJ!zCL4`L))#46p#kKe~vUpXp_(J{wB&wynHGv-GMILhimX2eu}lE zycOeSMw2EFYiYZ1G9JT4_yw-Sq)Memq0Au*$s$sTe~ZkOLa-6Cj_7t66)wzS6iF&M z7AcaD%LqwSDe@Fci3CCxz=;{Z2el{*kRn^Y*r-L1YXkL7#1ev&Gk%DS`3ekwi$zCo+xNqf0#1jf5QK3PN(GiI_v!2~I^lNl1}|UPS~Y zL8Qzl))}=&mxWYwh$*qIXE2eF*G-C?`2?dDeN&`LE*ifNQm-La6U(JRX(ZB(TJ%?x zDpMEqdwkt}f#(Y}Pp{_l2Q<63-R||cG>6~sb=!Sqq2lDmi3R<1_PPH*s;zD5;;=8- zl#uH7y0k4GZ@}Ht8}_BWW(qfE){nBRboX?5dV4*7pXPG=JZ@K6Xvk8X*xKjl@&v+{ zEybp>cBj+d=L^XFU7lXM!z+V+&fXU4$Z0cIRF{_5l$OubDk|qx)r7vy$t`T?@wEGU zc4|$Y-mT89ZeOQgo7dh$qtoB%57f4I**)HHeD0T{Qcg!+&@bp`^)sQ*tu3LeR%gn8 z&k@QjwI#^n%fhyCXOdGj{gOUn3_c&emj7H*_{y|~xCsaKk;tCNUZ%OE=^yK-^h=T5 z`q{`Jb6yOMvH3G!kL)%2PU&a#a||2`@3dV_7=JV}z_7jgMg3G{IQC6G-xC=MEi2Bm VjPTJX`i02LGE4@Qg%-_t@DJ8-ZFc|w diff --git a/rest_framework/locale/ru/LC_MESSAGES/django.po b/rest_framework/locale/ru/LC_MESSAGES/django.po index 7e09b227e8..30df5d496c 100644 --- a/rest_framework/locale/ru/LC_MESSAGES/django.po +++ b/rest_framework/locale/ru/LC_MESSAGES/django.po @@ -3,18 +3,20 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Anton Bazhanov , 2018 # Grigory Mishchenko , 2017 # Kirill Tarasenko, 2015 # koodjo , 2015 # Mike TUMS , 2015 # Sergei Sinitsyn , 2016 +# Val Grom , 2020 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Grigory Mishchenko \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Russian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -22,40 +24,40 @@ msgstr "" "Language: ru\n" "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Недопустимый заголовок. Не предоставлены учетные данные." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Недопустимый заголовок. Учетные данные не должны содержать пробелов." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Недопустимый заголовок. Учетные данные некорректно закодированны в base64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Недопустимые имя пользователя или пароль." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Пользователь неактивен или удален." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Недопустимый заголовок токена. Не предоставлены учетные данные." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Недопустимый заголовок токена. Токен не должен содержать пробелов." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Недопустимый заголовок токена. Токен не должен содержать недопустимые символы." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Недопустимый токен." @@ -63,382 +65,515 @@ msgstr "Недопустимый токен." msgid "Auth Token" msgstr "Токен аутентификации" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Ключ" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Пользователь" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Создан" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Токен" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Токены" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Имя пользователя" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Пароль" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Учетная запись пользователя отключена." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Невозможно войти с предоставленными учетными данными." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Должен включать \"username\" и \"password\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Произошла ошибка сервера." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Искаженный запрос." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Некорректные учетные данные." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Учетные данные не были предоставлены." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "У вас нет прав для выполнения этой операции." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Не найдено." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Метод \"{method}\" не разрешен." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Невозможно удовлетворить \"Accept\" заголовок запроса." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Неподдерживаемый тип данных \"{media_type}\" в запросе." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Запрос был проигнорирован." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Это поле обязательно." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Это поле не может быть null." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" не является корректным булевым значением." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Это поле не может быть пустым." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Убедитесь, что в этом поле не больше {max_length} символов." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Убедитесь, что в этом поле как минимум {min_length} символов." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Введите корректный адрес электронной почты." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Значение не соответствует требуемому паттерну." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Введите корректный \"slug\", состоящий из букв, цифр, знаков подчеркивания или дефисов." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Введите корректный URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" не является корректным UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Введите действительный адрес IPv4 или IPv6." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Требуется целочисленное значение." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Убедитесь, что значение меньше или равно {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Убедитесь, что значение больше или равно {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Слишком длинное значение." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Требуется численное значение." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Убедитесь, что в числе не больше {max_digits} знаков." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Убедитесь, что в числе не больше {max_decimal_places} знаков в дробной части." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Убедитесь, что в числе не больше {max_whole_digits} знаков в целой части." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Неправильный формат datetime. Используйте один из этих форматов: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Ожидался datetime, но был получен date." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Неправильный формат date. Используйте один из этих форматов: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Ожидался date, но был получен datetime." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Неправильный формат времени. Используйте один из этих форматов: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Неправильный формат. Используйте один из этих форматов: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" не является корректным значением." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Элементов больше чем {count}" -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Ожидался list со значениями, но был получен \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Выбор не может быть пустым." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" не является корректным путем до файла" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Не был загружен файл." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Загруженный файл не является корректным файлом." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Невозможно определить имя файла." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Загруженный файл пуст." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Убедитесь, что имя файла меньше {max_length} символов (сейчас {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Загрузите корректное изображение. Загруженный файл не является изображением, либо является испорченным." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Этот список не может быть пустым." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Ожидался словарь со значениями, но был получен \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Значение должно быть правильным JSON." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Отправить" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Поиск" -#: filters.py:336 -msgid "ascending" +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Порядок сортировки" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:337 +#: filters.py:287 +msgid "ascending" +msgstr "по возрастанию" + +#: filters.py:288 msgid "descending" +msgstr "по убыванию" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." msgstr "" -#: pagination.py:193 +#: pagination.py:189 msgid "Invalid page." msgstr "Неправильная страница" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Не корректный курсор" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Недопустимый первичный ключ \"{pk_value}\" - объект не существует." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Некорректный тип. Ожидалось значение первичного ключа, получен {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Недопустимая ссылка - нет совпадения по URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Недопустимая ссылка - некорректное совпадение по URL," -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Недопустимая ссылка - объект не существует." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Некорректный тип. Ожидался URL, получен {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Объект с {slug_name}={value} не существует." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Недопустимое значение." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Недопустимые данные. Ожидался dictionary, но был получен {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Фильтры" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Фильтры полей" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Порядок сортировки" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Поиск" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Ничего" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Нет элементов для выбора" -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Это поле должно быть уникально." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Поля {field_names} должны производить массив с уникальными значениями." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Это поле должно быть уникально для даты \"{date_field}\"." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Это поле должно быть уникально для месяца \"{date_field}\"." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Это поле должно быть уникально для года \"{date_field}\"." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Недопустимая версия в заголовке \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Недопустимая версия в пути URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "Недопустимая версия в пути URL. Не соответствует ни одному version namespace." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Недопустимая версия в имени хоста." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Недопустимая версия в параметре запроса." - -#: views.py:88 -msgid "Permission denied." -msgstr "Доступ запрещен" diff --git a/rest_framework/locale/ru_RU/LC_MESSAGES/django.mo b/rest_framework/locale/ru_RU/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..d6da8f432d9025af1a83c7c408f46d16e8368f68 GIT binary patch literal 5208 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, 2018-2019 +msgid "" +msgstr "" +"Project-Id-Version: Django REST framework\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" +"Language-Team: Russian (Russia) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/ru_RU/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: ru_RU\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" + +#: authentication.py:70 +msgid "Invalid basic header. No credentials provided." +msgstr "" + +#: authentication.py:73 +msgid "Invalid basic header. Credentials string should not contain spaces." +msgstr "" + +#: authentication.py:83 +msgid "Invalid basic header. Credentials not correctly base64 encoded." +msgstr "" + +#: authentication.py:101 +msgid "Invalid username/password." +msgstr "Пожалуйста, введите корректные имя пользователя и пароль учётной записи. Оба поля могут быть чувствительны к регистру." + +#: authentication.py:104 authentication.py:206 +msgid "User inactive or deleted." +msgstr "Пользователь неактивен или удален." + +#: authentication.py:184 +msgid "Invalid token header. No credentials provided." +msgstr "" + +#: authentication.py:187 +msgid "Invalid token header. Token string should not contain spaces." +msgstr "" + +#: authentication.py:193 +msgid "" +"Invalid token header. Token string should not contain invalid characters." +msgstr "" + +#: authentication.py:203 +msgid "Invalid token." +msgstr "" + +#: authtoken/apps.py:7 +msgid "Auth Token" +msgstr "" + +#: authtoken/models.py:13 +msgid "Key" +msgstr "Ключ" + +#: authtoken/models.py:16 +msgid "User" +msgstr "Пользователь" + +#: authtoken/models.py:18 +msgid "Created" +msgstr "" + +#: authtoken/models.py:27 authtoken/serializers.py:19 +msgid "Token" +msgstr "" + +#: authtoken/models.py:28 +msgid "Tokens" +msgstr "" + +#: authtoken/serializers.py:9 +msgid "Username" +msgstr "Имя пользователя" + +#: authtoken/serializers.py:13 +msgid "Password" +msgstr "Пароль" + +#: authtoken/serializers.py:35 +msgid "Unable to log in with provided credentials." +msgstr "" + +#: authtoken/serializers.py:38 +msgid "Must include \"username\" and \"password\"." +msgstr "" + +#: exceptions.py:102 +msgid "A server error occurred." +msgstr "Ошибка сервера." + +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 +msgid "Malformed request." +msgstr "" + +#: exceptions.py:167 +msgid "Incorrect authentication credentials." +msgstr "" + +#: exceptions.py:173 +msgid "Authentication credentials were not provided." +msgstr "" + +#: exceptions.py:179 +msgid "You do not have permission to perform this action." +msgstr "У вас недостаточно прав для выполнения данного действия." + +#: exceptions.py:185 +msgid "Not found." +msgstr "Страница не найдена." + +#: exceptions.py:191 +#, python-brace-format +msgid "Method \"{method}\" not allowed." +msgstr "" + +#: exceptions.py:202 +msgid "Could not satisfy the request Accept header." +msgstr "" + +#: exceptions.py:212 +#, python-brace-format +msgid "Unsupported media type \"{media_type}\" in request." +msgstr "" + +#: exceptions.py:223 +msgid "Request was throttled." +msgstr "" + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "Обязательное поле." + +#: fields.py:317 +msgid "This field may not be null." +msgstr "Это поле не может быть пустым." + +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 +msgid "This field may not be blank." +msgstr "Это поле не может быть пустым." + +#: fields.py:768 fields.py:1881 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} characters." +msgstr "Убедитесь, что это значение содержит не более {max_length} символов." + +#: fields.py:769 +#, python-brace-format +msgid "Ensure this field has at least {min_length} characters." +msgstr "Убедитесь, что это значение содержит не менее {min_length} символов." + +#: fields.py:816 +msgid "Enter a valid email address." +msgstr "Введите правильный адрес электронной почты." + +#: fields.py:827 +msgid "This value does not match the required pattern." +msgstr "" + +#: fields.py:838 +msgid "" +"Enter a valid \"slug\" consisting of letters, numbers, underscores or " +"hyphens." +msgstr "Значение должно состоять только из букв, цифр, символов подчёркивания или дефисов, входящих в стандарт Юникод." + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 +msgid "Enter a valid URL." +msgstr "Введите правильный URL." + +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" + +#: fields.py:903 +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "Введите действительный IPv4 или IPv6 адрес." + +#: fields.py:931 +msgid "A valid integer is required." +msgstr "Введите правильное число." + +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format +msgid "Ensure this value is less than or equal to {max_value}." +msgstr "Убедитесь, что это значение меньше либо равно {max_value}." + +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format +msgid "Ensure this value is greater than or equal to {min_value}." +msgstr "Убедитесь, что это значение больше либо равно {min_value}." + +#: fields.py:934 fields.py:971 fields.py:1010 +msgid "String value too large." +msgstr "" + +#: fields.py:968 fields.py:1004 +msgid "A valid number is required." +msgstr "" + +#: fields.py:1007 +#, python-brace-format +msgid "Ensure that there are no more than {max_digits} digits in total." +msgstr "Убедитесь, что вы ввели не более {max_digits} цифры." + +#: fields.py:1008 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_decimal_places} decimal places." +msgstr "Убедитесь, что вы ввели не более {max_decimal_places} цифры после запятой." + +#: fields.py:1009 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_whole_digits} digits before the " +"decimal point." +msgstr "Убедитесь, что вы ввели не более {max_whole_digits} цифры перед запятой." + +#: fields.py:1148 +#, python-brace-format +msgid "Datetime has wrong format. Use one of these formats instead: {format}." +msgstr "" + +#: fields.py:1149 +msgid "Expected a datetime but got a date." +msgstr "" + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "" + +#: fields.py:1237 +msgid "Expected a date but got a datetime." +msgstr "" + +#: fields.py:1303 +#, python-brace-format +msgid "Time has wrong format. Use one of these formats instead: {format}." +msgstr "" + +#: fields.py:1365 +#, python-brace-format +msgid "Duration has wrong format. Use one of these formats instead: {format}." +msgstr "" + +#: fields.py:1399 fields.py:1456 +#, python-brace-format +msgid "\"{input}\" is not a valid choice." +msgstr "Значения {input} нет среди допустимых вариантов." + +#: fields.py:1402 +#, python-brace-format +msgid "More than {count} items..." +msgstr "" + +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format +msgid "Expected a list of items but got type \"{input_type}\"." +msgstr "" + +#: fields.py:1458 +msgid "This selection may not be empty." +msgstr "" + +#: fields.py:1495 +#, python-brace-format +msgid "\"{input}\" is not a valid path choice." +msgstr "" + +#: fields.py:1514 +msgid "No file was submitted." +msgstr "Ни одного файла не было отправлено." + +#: fields.py:1515 +msgid "" +"The submitted data was not a file. Check the encoding type on the form." +msgstr "" + +#: fields.py:1516 +msgid "No filename could be determined." +msgstr "" + +#: fields.py:1517 +msgid "The submitted file is empty." +msgstr "Отправленный файл пуст." + +#: fields.py:1518 +#, python-brace-format +msgid "" +"Ensure this filename has at most {max_length} characters (it has {length})." +msgstr "Убедитесь, что это имя файла содержит не более {max_length} символов (сейчас {length})." + +#: fields.py:1566 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "Загрузите правильное изображение. Файл, который вы загрузили, поврежден или не является изображением." + +#: fields.py:1604 relations.py:486 serializers.py:571 +msgid "This list may not be empty." +msgstr "" + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format +msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "" + +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 +msgid "Value must be valid JSON." +msgstr "" + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Сортировка" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 +msgid "ascending" +msgstr "по возрастанию" + +#: filters.py:288 +msgid "descending" +msgstr "по убыванию" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 +msgid "Invalid page." +msgstr "" + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "" + +#: relations.py:246 +#, python-brace-format +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "" + +#: relations.py:247 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "" + +#: relations.py:280 +msgid "Invalid hyperlink - No URL match." +msgstr "" + +#: relations.py:281 +msgid "Invalid hyperlink - Incorrect URL match." +msgstr "" + +#: relations.py:282 +msgid "Invalid hyperlink - Object does not exist." +msgstr "" + +#: relations.py:283 +#, python-brace-format +msgid "Incorrect type. Expected URL string, received {data_type}." +msgstr "" + +#: relations.py:448 +#, python-brace-format +msgid "Object with {slug_name}={value} does not exist." +msgstr "" + +#: relations.py:449 +msgid "Invalid value." +msgstr "Введите правильное значение." + +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "" + +#: templates/rest_framework/admin.html:116 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 +msgid "Filters" +msgstr "Фильтры" + +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" + +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" + +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "" + +#: validators.py:39 +msgid "This field must be unique." +msgstr "Значения поля должны быть уникальны." + +#: validators.py:89 +#, python-brace-format +msgid "The fields {field_names} must make a unique set." +msgstr "" + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" date." +msgstr "" + +#: validators.py:258 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" month." +msgstr "" + +#: validators.py:271 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" year." +msgstr "" + +#: versioning.py:40 +msgid "Invalid version in \"Accept\" header." +msgstr "" + +#: versioning.py:71 +msgid "Invalid version in URL path." +msgstr "" + +#: versioning.py:116 +msgid "Invalid version in URL path. Does not match any version namespace." +msgstr "" + +#: versioning.py:148 +msgid "Invalid version in hostname." +msgstr "" + +#: versioning.py:170 +msgid "Invalid version in query parameter." +msgstr "" diff --git a/rest_framework/locale/sk/LC_MESSAGES/django.mo b/rest_framework/locale/sk/LC_MESSAGES/django.mo index c82ae3e09733c4e8a03e23eec9a04bfdbba7635c..561c98e98847ec949c767ff3a251f017130b8dc9 100644 GIT binary patch delta 1748 zcmY+^eQ4EH9LMpmYtD!1u5LG{*0!@vHy^fn@7;rW@9yq(o6f02G!res4V89tY;NK# zur5(*#QN(p{}6*Dk_Lg;`e%v{K?s8X7!m|#5R%A(#0V*)e1Cqw+hm9Py{~hAkLR54 zIlnWVw>r}I3Ufz|(m}jT?94Ii!I514C=2q;94^Gg7(rdP1z*Bmti=zp2EWEu{24c5 z#Vr5XDNNG7h`aDHt~EA@^;h-3eX0D^A{vUh`qYSeJzrbd^kNdHlw`s&DumaCvCw{k>`AgpTU@iWJn!%;yA%UB5FMf>e_#jP1 z+oYz1Erv(X;R!6l^Qg6O3Cr-8tU1i17Gnjj!!FcJp28OV3U&Qt_IN%GJwJp=Y(>p@ z`U5I8RIcJqoWwUV>Bt*?iOjCuKz{a9)_<{$c3HW%zF~w z!vtQ#VeS9_sA$m)SNgdb!x^--AYULf?M*~0!E739a9XLF*77@}RA{v9Xm1^8AS;RW z1pTpD1WUl?YyYb^|Ceei>b(lvda7t-d}l04C@rLas%T5r6ZG3lNgQK@3S;wjfY;!U zb!>d!yuw=7_@5W$LxnG=wEEj-PbN*fU(>WFqig*9t438hUb&{t54 zF+iv&-Hr$oOUn3T4SF94eeJZrwV+BfU3n#$k-XXC<9QSLnUjT|7DPg|VSe>4ye`_% zmej#to+ z>uW^zVi*T-67R)V@c>@Mgh)v`H;5djqk#Ky4STVwR^)!{!zez7di^4f;$PT^$vSrc zk7FzClNHaQ{?8}ag@5B+7}@Cl-y_(=`(>F*KNr5hLEOAaB!Ca%W;~5Ocoy67Q`C%H z!w&S;iv+PB^|?8W&y{u!ojTC%*u?uKsug^=a-oPivlmcPy@I=N4Y%Vp zG+4Vugh@#U?!zwB>uJ0NpGFMo;5*wKI1m-b;m+%D) zuAyck!0>&T#1Wju1ip(8;U74Lqol>)In<1NjGF2y{$vu9IE*W(FZ>mmyaY+RK0m1W z_fuKMeYlFc8GpmaFx)IMglADdpRbTCOB4As7{G1#0O}q|;ZA(M;tJ{}{S5EM-%&Fe zO;FJXmiW;N@1VZmYfRuzs43rT-0yiB$7wI&G+sf*Bx5b^=F1^n@=V3^I7s^v z>Q}Uhw07Yz^1hNRQqh^ef`{=-OyI6oHxo0sK>H2U0q*2SH`5Sut2~A4PzT8^PBwL> zS~T@K%K#B3w6JBI&$tF7`u$%oES-~jZ4VIaLAgmA6qZeP6S}rKi#CES;0*jej1m7Y zoKH?BwOY+p*|)f0W#`kUd?r&ccCKhFLq}%HOiYXnT?l$U z*tpF~rE>FjQE!;a6s+Uf^i=uBhS!Ee`~ATle=ux9k!V-Xo*<=e)G8GH<9W+2WUXQ@ zA2s7Mxml}V250h_LNSvzv0`pEL8K}j<`IT-FWbJ_X4m5sOC@px#r={G?WHSKm^ zywkL`89Nq_XlIDd&fYrlEoSVw`67cpk#>h`uYP21%&W=U{jYi1yp`ML7?8dCraP}g U-uRZ\n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Slovak (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/sk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sk\n" -"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Nesprávna hlavička. Neboli poskytnuté prihlasovacie údaje." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Nesprávna hlavička. Prihlasovacie údaje nesmú obsahovať medzery." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Nesprávna hlavička. Prihlasovacie údaje nie sú správne zakódované pomocou metódy base64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Nesprávne prihlasovacie údaje." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Daný používateľ je neaktívny, alebo zmazaný." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Nesprávna token hlavička. Neboli poskytnuté prihlasovacie údaje." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Nesprávna token hlavička. Token hlavička nesmie obsahovať medzery." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "" -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Nesprávny token." @@ -59,382 +59,515 @@ msgstr "Nesprávny token." msgid "Auth Token" msgstr "" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Daný používateľ je zablokovaný." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "S danými prihlasovacími údajmi nebolo možné sa prihlásiť." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Musí obsahovať parametre \"používateľské meno\" a \"heslo\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Vyskytla sa chyba na strane servera." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Požiadavok má nesprávny formát, alebo je poškodený." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Nesprávne prihlasovacie údaje." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Prihlasovacie údaje neboli zadané." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "K danej akcii nemáte oprávnenie." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Nebolo nájdené." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Metóda \"{method}\" nie je povolená." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Nie je možné vyhovieť požiadavku v hlavičke \"Accept\"." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Požiadavok obsahuje nepodporovaný media type: \"{media_type}\"." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Požiadavok bol obmedzený, z dôvodu prekročenia limitu." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Toto pole je povinné." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Toto pole nemôže byť nulové." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" je validný boolean." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." msgstr "Toto pole nemože byť prázdne." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Uistite sa, že toto pole nemá viac ako {max_length} znakov." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Uistite sa, že toto pole má viac ako {min_length} znakov." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Vložte správnu emailovú adresu." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Toto pole nezodpovedá požadovanému formátu." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Zadajte platný \"slug\", ktorý obsahuje len malé písmená, čísla, spojovník alebopodtržítko." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Zadajte platnú URL adresu." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" nie je platné UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "" -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Je vyžadované celé číslo." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Uistite sa, že hodnota je menšia alebo rovná {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Uistite sa, že hodnota je väčšia alebo rovná {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Zadaný textový reťazec je príliš dlhý." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Je vyžadované číslo." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Uistite sa, že hodnota neobsahuje viac ako {max_digits} cifier." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Uistite sa, že hodnota neobsahuje viac ako {max_decimal_places} desatinných miest." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Uistite sa, že hodnota neobsahuje viac ako {max_whole_digits} cifier pred desatinnou čiarkou." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Nesprávny formát dátumu a času. Prosím použite jeden z nasledujúcich formátov: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Vložený len dátum - date namiesto dátumu a času - datetime." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Nesprávny formát dátumu. Prosím použite jeden z nasledujúcich formátov: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Vložený dátum a čas - datetime namiesto jednoduchého dátumu - date." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Nesprávny formát času. Prosím použite jeden z nasledujúcich formátov: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" je nesprávny výber z daných možností." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "" -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Bol očakávaný zoznam položiek, no namiesto toho bol nájdený \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "" -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Nebol odoslaný žiadny súbor." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Odoslané dáta neobsahujú súbor. Prosím skontrolujte kódovanie - encoding type daného formuláru." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Nebolo možné určiť meno súboru." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Odoslaný súbor je prázdny." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Uistite sa, že meno súboru neobsahuje viac ako {max_length} znakov. (V skutočnosti ich má {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Uploadujte prosím obrázok. Súbor, ktorý ste uploadovali buď nie je obrázok, alebo daný obrázok je poškodený." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "" -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Bol očakávaný slovník položiek, no namiesto toho bol nájdený \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" msgstr "" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Nesprávny kurzor." -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Nesprávny primárny kľúč \"{pk_value}\" - objekt s daným primárnym kľúčom neexistuje." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Nesprávny typ. Bol prijatý {data_type} namiesto primárneho kľúča." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Nesprávny hypertextový odkaz - žiadna zhoda." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Nesprávny hypertextový odkaz - chybná URL." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Nesprávny hypertextový odkaz - požadovný objekt neexistuje." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Nesprávny typ {data_type}. Požadovaný typ: hypertextový odkaz." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objekt, ktorého atribút \"{slug_name}\" je \"{value}\" neexistuje." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Nesprávna hodnota." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Bol očakávaný slovník položiek, no namiesto toho bol nájdený \"{datatype}\"." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "" -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Táto položka musí byť unikátna." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Dané položky: {field_names} musia tvoriť musia spolu tvoriť unikátnu množinu." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Položka musí byť pre špecifický deň \"{date_field}\" unikátna." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Položka musí byť pre mesiac \"{date_field}\" unikátna." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Položka musí byť pre rok \"{date_field}\" unikátna." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Nesprávna verzia v \"Accept\" hlavičke." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Nesprávna verzia v URL adrese." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Nesprávna verzia v \"hostname\"." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Nesprávna verzia v parametri požiadavku." - -#: views.py:88 -msgid "Permission denied." -msgstr "" diff --git a/rest_framework/locale/sl/LC_MESSAGES/django.mo b/rest_framework/locale/sl/LC_MESSAGES/django.mo index 33aba7cf4cf2c5b5626d26774d5915b2daedfe53..7ec83f821634b41b2c0a9f5ffef75ec33aedf0d6 100644 GIT binary patch delta 2244 zcmYk+e@vBC9LMo5!R3b%#YK^;Xcr@afE%C}`7MfR{78`aA!f?$#~OuR1tYVzXO`w_ zgIag4+G0_o#>ju#t<{?UXl=GyYpc<8W2RfS{-|ip*{n5sf1dleb;kF;&hzlx^PF?O z-*bQKI<~{PiM99JAV66ElpWR`@jP1(R5V7jP41XPLF&P7L8Gfu;;M?U&Rof#a7;Lf6!^fy4w>29>JAdpY*QZMXl^Z)QaX%TluGV z9nMbtzZ;XeKZe?YqqqjgQRAJ)B0P_|xPVTOPAX65U=gNZJ+kZ8jQ3zWl16*h^EhgN zaa5U1;|lx$EAVqvO0S_N+`!=a{{ZUx5UOS-a@hZBI!w=Ge|aV9!v07+<>{fMB}z&8}?!^PC0Zs=v+rtWebT? z(KxsjXHhFzKn+yDcHEDBsQc5NKcL1*XMfjWFKU9%p(gkms^~sJ?a;b;ClQTNwY0t4f@#zy|5kmiXBAl!~{}r_9-fbUm+=X z>~A_6xGHG28IPg{JcAnOTO`eP4T~^?+0nI1)E0K5UND1Y_#J8?i>Pr@$rp380BS-t zSdM*|r}KZDjs}=SRq1(5!5>jI@Du7>FQCqG9%oTSw+_jQm0=ogM^2=5Vm%JxW_%0v z+$GdPZXmmFS*&g)@3&Gq+LCRkoQ|Q6(G2Rri>LvvBb#N}{7R`tD)BztgIe)>sEM3L z?Zii@ajsw|`Y8+*;ls%0*f2Vp@q~BdZRF3+^1+Xp{et?!Uzmi+go;etdb25YDl|3i zj-Kx!v;!(MwOT?c&=DUb_7SQrn&WPndSOBZvWMs+ZZ_pyyQ1n>I)a4SZsJ~IC!yV8 zk=9SBIMtRWum@0QU2Q9&g{d_VWy=5moANc_-B5LFH+B(CgmR~*Emb`{M5GXPL>0kL zgnKl>{SD|w6)5Spy+kX)DR-OF)=XsT{mO;%sYZ%zhucf!)a$xO%dHV^s0P$j#TrclWaax}kLOg!r*n#uV^?$M+NvtbEBRX=tgNlu5H78tE$N7i9xm@0i5wUm zjvS7T)CG4%#)cjn33iS=8a?#XGr@)<(L}F#c<4ZMY$y_XB)Y#T{`-oyq*#9-6rT#@ Y`{G}&dBqpoUsxTVE%YbHE*Fje2Ra4c5C8xG delta 2549 zcmZYATWl0n9LMp~ZnrHIS}hbxu`rZdxwKMfX|1-U7lD?F1qw(7ako3vDcc#cyKO|o zl?Mz2A;yX^-~|#)3@^r%p!gsW&}a-V5)BE7(MV!^z!)Mv)SCGH%?{zg6aMo#b2>Y7 z&j0*pIJEIjPx`0goRfyOhj@hO4jFS4U(V$~JCkqBJUojQUd4rY8|UEUNye1o64dY8 za5nD7$#?*(@HJeH7cdWh!8T*k=65=48CcHMGKuT(b6kVj1;%W{cC5!!_&9!#8?bPS zF^$-V`uj=j#_KpA%cdGrjLoPCZbFT_zG-9Lq@x>N#5%l-{4nVGIet+*NWpcppb z2iSr)u^mf`jH$+*$dEaPy6zNK;tf|G?K=2CDz5&V4?%MUT$gvvxK2W?$1rgg(4oyB+pXW&J&@H$Sz zKQITgIbl~!9zKf2NEw*6zzxWyrWbXe0aO{if-~`OaQ+@@!RMy2|Jv&x7|;sJr)Mr~ zM4fL&6_ban@fd24uLS4gs0GxNWUlK*i}M#y6FiE3Mlj6z1ZqOr%x)P@nnC_+=+rT= z0|!tm{Ss&4*MUEyRyrP>hgi-^&dZVdH9e@z^x@?!V{BB47jlzw?7#|)q9%M8nTt7- zrn7*~MVyPbkoqwH;1Vq0>RPNrJ!lZSFoj$3GOF53NRx^-$FEwGSu>OO1m zDI7+Pr>_Q`d#D>NX1a?og__}M)C50473Ytr)P-4|wyp-7aX<1#n$J)vzJ{vy?{GEV z!6sb5D)qZ4^7^LDc{-~Y_#0I`ORdan(}a5P2=YvG8kLbtNMV~_QG0w3>v17Z(~Wmw zD}IQ&-*2ek4%CGDu?i2N zzJMR2e*X=!{pK#_;AEbz+9*PO2g*?2mv#*Ee6xv;QoSA7PBVmgcm(+bm^V-pIg8rk zkFXVQqpn}X*GVgDMRw6_Lyad<89IU5>n~8R-JhuI=CRNI``=85?3*2^;@OYu@f_yj z9n^&WLS<$Gb)zNZ@oDTs73X9l?(I?Fo()m!ED8JVSI5VL}VlB9)<5?f)h^YTDB$ z{a$8YchY~JSVt@)v|=^QT-&8RTuYEmzjbizCHe`~i!!LD45{fY$oG4hI#G9*_P-|> z)E>15eG9dB&l1_h9)efd|DRArs1_nrXf4DJLQUybEkuYIkw>U$JM`AvZ#JDABE6f# z6ha>bw#jTHRAf=2me8A_*Gv1hnb13|)=c#Iy$6RibooH~HK5Vwh+Kj?^;;W96`oo{ zhAsX_g^!)z9^<%$=+2z_{|goc{gr{H;(+7s8%`apu$-jjdMPVnjYQ&3%!+zm+>W@_ zqm}vV%PS5rIBfs_s;yf)JI37nP`JR3$E-b0JY^@6WA4`HNEXnLf--y3#QdUVW5Mxt?Dbi3qMVNHG2iiWBctE`&Z#=3^l z>oaB+txMPgUczd3hPZFU9h)?>E33eEAI!6*w5{yHJYB~cvEzf05iap3`D$r-IL}U4 z`@Hzz*gNHKg-bfT5&E_}Xj?BuMiS_H)uYSi*XN`XF=uS|{F2b@ug>mutVHD6@nP5E TI?uI-yaDgpaht^KUG(;Ua?K=& diff --git a/rest_framework/locale/sl/LC_MESSAGES/django.po b/rest_framework/locale/sl/LC_MESSAGES/django.po index 9af0fc8fc9..2051903830 100644 --- a/rest_framework/locale/sl/LC_MESSAGES/django.po +++ b/rest_framework/locale/sl/LC_MESSAGES/django.po @@ -8,9 +8,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Gregor Cimerman\n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Slovenian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/sl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -18,40 +18,40 @@ msgstr "" "Language: sl\n" "Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Napačno enostavno zagalvje. Ni podanih poverilnic." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Napačno enostavno zaglavje. Poverilniški niz ne sme vsebovati presledkov." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Napačno enostavno zaglavje. Poverilnice niso pravilno base64 kodirane." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Napačno uporabniško ime ali geslo." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Uporabnik neaktiven ali izbrisan." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Neveljaven žeton v zaglavju. Ni vsebovanih poverilnic." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Neveljaven žeton v zaglavju. Žeton ne sme vsebovati presledkov." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Neveljaven žeton v zaglavju. Žeton ne sme vsebovati napačnih znakov." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Neveljaven žeton." @@ -59,382 +59,515 @@ msgstr "Neveljaven žeton." msgid "Auth Token" msgstr "Prijavni žeton" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Ključ" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Uporabnik" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Ustvarjen" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Žeton" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Žetoni" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Uporabniško ime" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Geslo" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Uporabniški račun je onemogočen." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Neuspešna prijava s podanimi poverilnicami." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Mora vsebovati \"uporabniško ime\" in \"geslo\"." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Napaka na strežniku." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Okvarjen zahtevek." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Napačni avtentikacijski podatki." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Avtentikacijski podatki niso bili podani." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Nimate dovoljenj za izvedbo te akcije." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Ni najdeno" -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Metoda \"{method}\" ni dovoljena" -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Ni bilo mogoče zagotoviti zaglavja Accept zahtevka." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Nepodprt medijski tip \"{media_type}\" v zahtevku." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Zahtevek je bil pridržan." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "To polje je obvezno." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "To polje ne sme biti null." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" ni veljaven boolean." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "To polje ne sme biti prazno." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "To polje ne sme biti daljše od {max_length} znakov." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "To polje mora vsebovati vsaj {min_length} znakov." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Vnesite veljaven elektronski naslov." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Ta vrednost ne ustreza zahtevanemu vzorcu." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Vnesite veljaven \"slug\", ki vsebuje črke, številke, podčrtaje ali vezaje." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Vnesite veljaven URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" ni veljaven UUID" +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Vnesite veljaven IPv4 ali IPv6 naslov." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Zahtevano je veljavno celo število." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Vrednost mora biti manjša ali enaka {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Vrednost mora biti večija ali enaka {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Niz je prevelik." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Zahtevano je veljavno število." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Vnesete lahko največ {max_digits} števk." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Vnesete lahko največ {max_decimal_places} decimalnih mest." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Vnesete lahko največ {max_whole_digits} števk pred decimalno piko." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datim in čas v napačnem formatu. Uporabite eno izmed naslednjih formatov: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Pričakovan datum in čas, prejet le datum." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Datum je v napačnem formatu. Uporabnite enega izmed naslednjih: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Pričakovan datum vendar prejet datum in čas." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Čas je v napačnem formatu. Uporabite enega izmed naslednjih: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Trajanje je v napačnem formatu. Uporabite enega izmed naslednjih: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" ni veljavna izbira." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Več kot {count} elementov..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Pričakovan seznam elementov vendar prejet tip \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Ta izbria ne sme ostati prazna." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" ni veljavna izbira poti." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Datoteka ni bila oddana." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Oddani podatki niso datoteka. Preverite vrsto kodiranja na formi." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Imena datoteke ni bilo mogoče določiti." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Oddana datoteka je prazna." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Ime datoteke lahko vsebuje največ {max_length} znakov (ta jih ima {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Naložite veljavno sliko. Naložena datoteka ni bila slika ali pa je okvarjena." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Seznam ne sme biti prazen." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Pričakovan je slovar elementov, prejet element je tipa \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Vrednost mora biti veljaven JSON." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Potrdi" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Iskanje" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Razvrščanje" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "naraščujoče" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "padajoče" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Neveljavna stran." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Neveljaven kazalec" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Neveljaven pk \"{pk_value}\" - objekt ne obstaja." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Neveljaven tip. Pričakovana vrednost pk, prejet {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Neveljavna povezava - Ni URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Ni veljavna povezava - Napačen URL." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Ni veljavna povezava - Objekt ne obstaja." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Napačen tip. Pričakovan URL niz, prejet {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objekt z {slug_name}={value} ne obstaja." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Neveljavna vrednost." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Napačni podatki. Pričakovan slovar, prejet {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtri" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Filter polj" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Razvrščanje" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Iskanje" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "None" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Ni elementov za izbiro." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "To polje mora biti unikatno." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Polja {field_names} morajo skupaj sestavljati unikaten niz." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Polje mora biti unikatno za \"{date_field}\" dan." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Polje mora biti unikatno za \"{date_field} mesec.\"" -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Polje mora biti unikatno za \"{date_field}\" leto." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Neveljavna verzija v \"Accept\" zaglavju." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Neveljavna različca v poti URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Neveljavna različica v poti URL. Se ne ujema z nobeno različico imenskega prostora." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Neveljavna različica v imenu gostitelja." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Neveljavna verzija v poizvedbenem parametru." - -#: views.py:88 -msgid "Permission denied." -msgstr "Dovoljenje zavrnjeno." diff --git a/rest_framework/locale/sv/LC_MESSAGES/django.mo b/rest_framework/locale/sv/LC_MESSAGES/django.mo index 7abf311b9686b66f9707b29da53763576aa6e928..fb1a9f6f9e3f6ec7ddc3294224384284e7f8d122 100644 GIT binary patch delta 2244 zcmYk-e@s-yzt z(rPz1tudEl%~fl2H#gV%qaSXyR;^`hxUp7$_(%VQYq`amtM})eW9!-dp4ao7bMJYc z^E}_@+}Xal-uTZ;)6N)WFHxvSHG5H}bD&gYnmJgBW!Q!H;t|yMU&R&p9%kVeScBhT zHU5nmSh2`#1J>bY9QTadCsaB(k;1Pd*ooWmHGBwv#TE=MHtWU#4CA|~>vPzH`PpWr z7{#UdGHQTl{qvW7ucPjF8$%X1YhL1wFot@gJq<_0fWgUj^@osn=@1uRnZ(l$?*(KBy&7h|8 z55FDC^{)3}D(5FqGw>YVkCUkTeTe1w6&Bz;#vLl@d^-=zF%4UgS+@wT#%^R8?Rnpq zP&b%FrO7ld#}BX$zeFwRpQr)1a&uiDM758ia_03s=D&%``8}D&D3f->%=DP#*=aX#J8yAD&VdW z+=c!44mROUT#w~U$2L5OoA3(iceimnwllw4KWKrO*EYB&6VmAHV~1*ML+g!LF9 z|13&HFN6!&j=v#^ZB^_cKGw=X?LAnEG2~vBz;;X`W3gMPj`K^r2Zv);(eN7b>Zi(3|#m9%>(#U{`-sCHoIxdZBfZm=wP9RqvY(2BW2j_Hpti?1zSl5?b}FG9U|auJI;gRm+@gDF zW|X)sgqA}^d88#$9_=ND3H3vXyKAdjh{z>|2xan}Lf`ImYTIe_4xzG(c$m->Yh5)X zI|ywnmBk+R80ukEHW6A2rZ$ezms5>yX6s%5h7dt7ZXY_6|KvoZp~Xuzv}+M=*_p8DDqF%pb|^l zQAJtZ>(;#dQ8KD@dALXY@oXg6)kdCMh!>OlZU(mmIO@f5r);It<~Q!b4Mav_DsyFG zF0(dSxhN%&T$@uENIa3(l$_2hPfgq?nCV(mS5wcY!KrT!H-@U}C|Nzx@nbdpW6{Io zqtRorv9Plz=9Y|bWwL2LsJDr-iTJhGuApza> delta 2559 zcmZYATWl0n9LMp~ZnqaMEi6z9S~?b#7D}N&DHhpoF9M|qg+h^v;I=!I$!>SCJ4H#+ zO*9Y@A<@bU7e!O!gT9yw3Oq@Ti7_M!A;EaVga_q;5E36G#Q6Qq4)MX0{mSrW;$a0Z-y8yow!IJjs|w z>_L5h0yp3loQtz38xz7Xs)L(R=aZfpW8R^n8=l8{yoY>DRdKebD^Md2qaGB)mH0k3 z;SFrXvJzuzaVK)h97SDs607hUY9=4x0-Q62=kR>fPGt#hN8R8JY{xHfHReps-mnR0 z)86Q}9n?r(Mvd$=GNtB%-+thKKa1f9IKLV-6U`j7bXzf_8>Xqu$KzOv=h4C|I1TS$ z9_G?uRZIac#t@PQrp31dxzub$-KQUwMz7*bJmj~}pawiTjrG@BU+08IP(D3-VI!)& z1(i%GT!BYXYkb*n-$xCgZbtUH4QSEci|XJJ^kxKuv>%{4luPdxVo@pcUq_{$6FaaU zHPWwe7JlP<6E)KNemlT$meMXq^4D~tW~K+<%rVABb>J>rLrY|Fv*p-^Y9By#{7lBL zeCc})nGACWYcQ9qp2Wqdo;#@A*oVsgi+($wX=>uA(|}A9;o;CS%m0YTs_u(!Gum{0`ZXW>Q7Aqh-j)RB+G$ny>*6 zptk2{_?-6tKU5-|=&_7pXP8qM#!padbQ`sOO4&Qr*o4}i4r+>D!U%qhdVlV2>SweR<00ghl1&o`&2XyhMZBc8)Nyo;=k`3u#N5N{zR>nzk7 zb|A}Z&LDj-7f?(03-U3+C$cZ7g&3h7$JO{L>iXN5QKgt)$)%_tcH*-^DFhp3VP)!jvWNg^UAXv*Ap5H>ElP}z*&-&F*h6Toy9rIPN`T-!0SI5Dd?l}gyrWbH^* z;kxq5VNMR&|G#S6w)VEMWMLpU(N4syJx;>42h(H8$rZs+r#(2}q|;6+X~pcMW5;So z-U>D3cMbIpIPTcxP*q@hw68BUlyvpzn3In7CUnuQ89x=*H7r`Pa?z4yR^9T(`jsPB zN@q@4pNhtv0jnz&vj-DH$=F!YOe?3v>t#CbTKmUG2V?d~P1!u6q4HseElyib(zPwy zb*+B-=k&Xl*XwB6U~t0tXu{3*dhCbt\n" "Language-Team: Swedish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/sv/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,40 +19,40 @@ msgstr "" "Language: sv\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Ogiltig \"basic\"-header. Inga användaruppgifter tillhandahölls." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Ogiltig \"basic\"-header. Strängen för användaruppgifterna ska inte innehålla mellanslag." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Ogiltig \"basic\"-header. Användaruppgifterna är inte korrekt base64-kodade." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Ogiltigt användarnamn/lösenord." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Användaren borttagen eller inaktiv." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Ogiltig \"token\"-header. Inga användaruppgifter tillhandahölls." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Ogiltig \"token\"-header. Strängen ska inte innehålla mellanslag." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Ogiltig \"token\"-header. Strängen ska inte innehålla ogiltiga tecken." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Ogiltig \"token\"." @@ -60,382 +60,515 @@ msgstr "Ogiltig \"token\"." msgid "Auth Token" msgstr "Autentiseringstoken" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Nyckel" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Användare" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Skapad" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Tokens" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Användarnamn" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Lösenord" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Användarkontot är borttaget." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Kunde inte logga in med de angivna inloggningsuppgifterna." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Användarnamn och lösenord måste anges." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Ett serverfel inträffade." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Ogiltig förfrågan." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Ogiltiga inloggningsuppgifter. " -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Autentiseringsuppgifter ej tillhandahållna." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Du har inte tillåtelse att utföra denna förfrågan." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Hittades inte." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Metoden \"{method}\" tillåts inte." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "Kunde inte tillfredsställa förfrågans \"Accept\"-header." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Medietypen \"{media_type}\" stöds inte." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Förfrågan stoppades eftersom du har skickat för många." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Det här fältet är obligatoriskt." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Det här fältet får inte vara null." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" är inte ett giltigt booleskt värde." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Det här fältet får inte vara blankt." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Se till att detta fält inte har fler än {max_length} tecken." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Se till att detta fält har minst {min_length} tecken." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Ange en giltig mejladress." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Det här värdet matchar inte mallen." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Ange en giltig \"slug\" bestående av bokstäver, nummer, understreck eller bindestreck." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Ange en giltig URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value} är inte ett giltigt UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Ange en giltig IPv4- eller IPv6-adress." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Ett giltigt heltal krävs." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Se till att detta värde är mindre än eller lika med {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Se till att detta värde är större än eller lika med {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Textvärdet är för långt." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Ett giltigt nummer krävs." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Se till att det inte finns fler än totalt {max_digits} siffror." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Se till att det inte finns fler än {max_decimal_places} decimaler." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Se till att det inte finns fler än {max_whole_digits} siffror före decimalpunkten." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datumtiden har fel format. Använd ett av dessa format istället: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Förväntade en datumtid men fick ett datum." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Datumet har fel format. Använde ett av dessa format istället: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Förväntade ett datum men fick en datumtid." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Tiden har fel format. Använd ett av dessa format istället: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Perioden har fel format. Använd ett av dessa format istället: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" är inte ett giltigt val." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Fler än {count} objekt..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Förväntade en lista med element men fick typen \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Det här valet får inte vara tomt." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" är inte ett giltigt val för en sökväg." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Ingen fil skickades." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Den skickade informationen var inte en fil. Kontrollera formulärets kodningstyp." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Inget filnamn kunde bestämmas." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Den skickade filen var tom." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Se till att det här filnamnet har högst {max_length} tecken (det har {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Ladda upp en giltig bild. Filen du laddade upp var antingen inte en bild eller en skadad bild." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Den här listan får inte vara tom." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Förväntade en \"dictionary\" med element men fick typen \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Värdet måste vara giltig JSON." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Skicka" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Sök" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Ordning" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "stigande" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "fallande" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Ogiltig sida." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Ogiltig cursor." -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Ogiltigt pk \"{pk_value}\" - Objektet finns inte." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Felaktig typ. Förväntade pk-värde, fick {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Ogiltig hyperlänk - Ingen URL matchade." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Ogiltig hyperlänk - Felaktig URL-matching." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Ogiltig hyperlänk - Objektet finns inte." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Felaktig typ. Förväntade URL-sträng, fick {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Objekt med {slug_name}={value} finns inte." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Ogiltigt värde." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Ogiltig data. Förväntade en dictionary, men fick {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filter" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Fältfilter" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Ordning" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Sök" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Inget" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Inga valbara objekt." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Det här fältet måste vara unikt." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Fälten {field_names} måste skapa ett unikt set." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Det här fältet måste vara unikt för datumet \"{date_field}\"." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Det här fältet måste vara unikt för månaden \"{date_field}\"." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Det här fältet måste vara unikt för året \"{date_field}\"." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Ogiltig version i \"Accept\"-headern." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Ogiltig version i URL-resursen." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "Ogiltig version i URL-resursen. Matchar inget versions-namespace." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Ogiltig version i värdnamnet." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Ogiltig version i förfrågningsparametern." - -#: views.py:88 -msgid "Permission denied." -msgstr "Åtkomst nekad." diff --git a/rest_framework/locale/th/LC_MESSAGES/django.mo b/rest_framework/locale/th/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..4a2b85a9bb977c0b5c54ea19106fc5b912da14fa GIT binary patch literal 8880 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 literal 0 HcmV?d00001 diff --git a/rest_framework/locale/th/LC_MESSAGES/django.po b/rest_framework/locale/th/LC_MESSAGES/django.po new file mode 100644 index 0000000000..353244db93 --- /dev/null +++ b/rest_framework/locale/th/LC_MESSAGES/django.po @@ -0,0 +1,573 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Translators: +# Preeti Yuankrathok , 2018 +msgid "" +msgstr "" +"Project-Id-Version: Django REST framework\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" +"Language-Team: Thai (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/th/)\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: th\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: authentication.py:70 +msgid "Invalid basic header. No credentials provided." +msgstr "" + +#: authentication.py:73 +msgid "Invalid basic header. Credentials string should not contain spaces." +msgstr "" + +#: authentication.py:83 +msgid "Invalid basic header. Credentials not correctly base64 encoded." +msgstr "" + +#: authentication.py:101 +msgid "Invalid username/password." +msgstr "ชื่อผู้ใช้งานหรือรหัสผ่านไม่ถูกต้อง" + +#: authentication.py:104 authentication.py:206 +msgid "User inactive or deleted." +msgstr "ผู้ใช้ไม่ได้เปิดใช้งานหรือถูกลบ" + +#: authentication.py:184 +msgid "Invalid token header. No credentials provided." +msgstr "" + +#: authentication.py:187 +msgid "Invalid token header. Token string should not contain spaces." +msgstr "" + +#: authentication.py:193 +msgid "" +"Invalid token header. Token string should not contain invalid characters." +msgstr "" + +#: authentication.py:203 +msgid "Invalid token." +msgstr "Token ไม่ถูกต้อง" + +#: authtoken/apps.py:7 +msgid "Auth Token" +msgstr "Auth Token" + +#: authtoken/models.py:13 +msgid "Key" +msgstr "คีย์" + +#: authtoken/models.py:16 +msgid "User" +msgstr "ผู้ใช้" + +#: authtoken/models.py:18 +msgid "Created" +msgstr "" + +#: authtoken/models.py:27 authtoken/serializers.py:19 +msgid "Token" +msgstr "Token" + +#: authtoken/models.py:28 +msgid "Tokens" +msgstr "Token" + +#: authtoken/serializers.py:9 +msgid "Username" +msgstr "ชื่อผู้ใช้งาน" + +#: authtoken/serializers.py:13 +msgid "Password" +msgstr "รหัสผ่าน" + +#: authtoken/serializers.py:35 +msgid "Unable to log in with provided credentials." +msgstr "ไม่สามารถเข้าสู่ระบบได้" + +#: authtoken/serializers.py:38 +msgid "Must include \"username\" and \"password\"." +msgstr "จำเป็นต้องใส่ชื่อผู้ใช้งานและรหัสผ่าน" + +#: exceptions.py:102 +msgid "A server error occurred." +msgstr "เซิร์ฟเวอร์เกิดข้อผิดพลาด" + +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 +msgid "Malformed request." +msgstr "" + +#: exceptions.py:167 +msgid "Incorrect authentication credentials." +msgstr "ข้อมูลการเข้าสู่ระบบไม่ถูกต้อง" + +#: exceptions.py:173 +msgid "Authentication credentials were not provided." +msgstr "ไม่พบข้อมูลการเข้าสู่ระบบ" + +#: exceptions.py:179 +msgid "You do not have permission to perform this action." +msgstr "คุณไม่มีสิทธิ์ที่จะดำเนินการ" + +#: exceptions.py:185 +msgid "Not found." +msgstr "ไม่พบ" + +#: exceptions.py:191 +#, python-brace-format +msgid "Method \"{method}\" not allowed." +msgstr "ไม่ใช่อนุญาติให้ใช้ Method \"{method}\"" + +#: exceptions.py:202 +msgid "Could not satisfy the request Accept header." +msgstr "" + +#: exceptions.py:212 +#, python-brace-format +msgid "Unsupported media type \"{media_type}\" in request." +msgstr "ไม่รองรับมีเดียประเภท \"{media_type}\" ใน Request" + +#: exceptions.py:223 +msgid "Request was throttled." +msgstr "" + +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "ฟิลด์นี้จำเป็น" + +#: fields.py:317 +msgid "This field may not be null." +msgstr "ฟิลด์นี้จำเป็นต้องมีค่า" + +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" + +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 +msgid "This field may not be blank." +msgstr "ฟิลด์นี้ไม่สามารถเว้นว่างได้" + +#: fields.py:768 fields.py:1881 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} characters." +msgstr "ตรวจสอบฟิลด์ว่ามีความยาวไม่เกิน {max_length} ตัวอักษร" + +#: fields.py:769 +#, python-brace-format +msgid "Ensure this field has at least {min_length} characters." +msgstr "ตรวตสอบฟิลด์ว่ามีความยาวอย่างน้อย {min_length} ตัวอักษร" + +#: fields.py:816 +msgid "Enter a valid email address." +msgstr "กรอกอีเมลให้ถูกต้อง" + +#: fields.py:827 +msgid "This value does not match the required pattern." +msgstr "ค่านี้ไม่ตรงกับรูปแบบที่ต้องการ" + +#: fields.py:838 +msgid "" +"Enter a valid \"slug\" consisting of letters, numbers, underscores or " +"hyphens." +msgstr "กรอกข้อมูลที่ประกอบด้วยตัวอักษร ตัวเลข สัญประกาศ และยัติภังค์เท่านั้น" + +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 +msgid "Enter a valid URL." +msgstr "กรอก URL ให้ถูกต้อง" + +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" + +#: fields.py:903 +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "กรอก IPv4 หรือ IPv6 ให้ถูกต้อง" + +#: fields.py:931 +msgid "A valid integer is required." +msgstr "ต้องการค่าจำนวนเต็มที่ถูกต้อง" + +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format +msgid "Ensure this value is less than or equal to {max_value}." +msgstr "ตรวจสอบว่าค่านี้น้อยกว่าหรือเท่ากับ {max_value}" + +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format +msgid "Ensure this value is greater than or equal to {min_value}." +msgstr "ตรวจสอบว่าค่านี้มากกว่าหรือเท่ากับ {min_value}" + +#: fields.py:934 fields.py:971 fields.py:1010 +msgid "String value too large." +msgstr "ข้อความยาวเกินไป" + +#: fields.py:968 fields.py:1004 +msgid "A valid number is required." +msgstr "ต้องการตัวเลขที่ถูกต้อง" + +#: fields.py:1007 +#, python-brace-format +msgid "Ensure that there are no more than {max_digits} digits in total." +msgstr "ตรวจสอบตัวเลขว่ามีไม่เกิน {max_digits} ตัว" + +#: fields.py:1008 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_decimal_places} decimal places." +msgstr "ตรวจสอบทศนิยมว่ามีไม่เกิน {max_decimal_places} หลัก" + +#: fields.py:1009 +#, python-brace-format +msgid "" +"Ensure that there are no more than {max_whole_digits} digits before the " +"decimal point." +msgstr "ตรวจสอบตัวเลขและทศนิยมรวมกันว่ามีไม่เกิน {max_whole_digits} ตัว" + +#: fields.py:1148 +#, python-brace-format +msgid "Datetime has wrong format. Use one of these formats instead: {format}." +msgstr "รูปแบบวันที่และเวลาไม่ถูกต้อง โปรดใช้รูปแบบใดรูปแบบหนึ่งจาก: {format}" + +#: fields.py:1149 +msgid "Expected a datetime but got a date." +msgstr "ต้องการวันที่และเวลา แต่ได้รับเพียงวันที่" + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "รูปแบบวันที่ไม่ถูกต้อง โปรดใช้รูปแบบใดรูปแบบหนึ่งจาก: {format}" + +#: fields.py:1237 +msgid "Expected a date but got a datetime." +msgstr "ต้องการวันที่ แต่ได้รับวันที่และเวลา" + +#: fields.py:1303 +#, python-brace-format +msgid "Time has wrong format. Use one of these formats instead: {format}." +msgstr "รูปแบบเวลาไม่ถูกต้อง โปรดใช้รูปแบบใดรูปแบบหนึ่งจาก: {format}" + +#: fields.py:1365 +#, python-brace-format +msgid "Duration has wrong format. Use one of these formats instead: {format}." +msgstr "รูปแบบระยะเวลาไม่ถูกต้อง โปรดใช้รูปแบบใดรูปแบบหนึ่งจาก: {format}" + +#: fields.py:1399 fields.py:1456 +#, python-brace-format +msgid "\"{input}\" is not a valid choice." +msgstr "\"{input}\" ไม่ใช่ตัวเลือกที่ถูกต้อง" + +#: fields.py:1402 +#, python-brace-format +msgid "More than {count} items..." +msgstr "มีมากกว่า {count} ไอเทม..." + +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format +msgid "Expected a list of items but got type \"{input_type}\"." +msgstr "ต้องการ List ของข้อมูล แต่ได้รับ \"{input_type}\"" + +#: fields.py:1458 +msgid "This selection may not be empty." +msgstr "" + +#: fields.py:1495 +#, python-brace-format +msgid "\"{input}\" is not a valid path choice." +msgstr "" + +#: fields.py:1514 +msgid "No file was submitted." +msgstr "ไม่พบไฟล์" + +#: fields.py:1515 +msgid "" +"The submitted data was not a file. Check the encoding type on the form." +msgstr "ข้อมูลที่ส่งไม่ใช่ไฟล์ โปรดตรวจสอบการเข้ารหัสของฟอร์ม" + +#: fields.py:1516 +msgid "No filename could be determined." +msgstr "ไม่สามารถระบุชื่อไฟล์ได้" + +#: fields.py:1517 +msgid "The submitted file is empty." +msgstr "ไฟล์ที่ส่งว่างเปล่า" + +#: fields.py:1518 +#, python-brace-format +msgid "" +"Ensure this filename has at most {max_length} characters (it has {length})." +msgstr "" + +#: fields.py:1566 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: fields.py:1604 relations.py:486 serializers.py:571 +msgid "This list may not be empty." +msgstr "List นี้ไม่สามารถว่างได้" + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format +msgid "Expected a dictionary of items but got type \"{input_type}\"." +msgstr "ต้องการ Dictionary แต่ได้รับ \"{input_type}\"" + +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 +msgid "Value must be valid JSON." +msgstr "ค่าจะต้องเป็น JSON ที่ถูกต้อง" + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "ค้นหา" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "การเรียงลำดับ" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 +msgid "ascending" +msgstr "น้อยไปมาก" + +#: filters.py:288 +msgid "descending" +msgstr "มากไปน้อย" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 +msgid "Invalid page." +msgstr "หน้าไม่ถูกต้อง" + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 +msgid "Invalid cursor" +msgstr "เคอร์เซอร์ไม่ถูกต้อง" + +#: relations.py:246 +#, python-brace-format +msgid "Invalid pk \"{pk_value}\" - object does not exist." +msgstr "" + +#: relations.py:247 +#, python-brace-format +msgid "Incorrect type. Expected pk value, received {data_type}." +msgstr "" + +#: relations.py:280 +msgid "Invalid hyperlink - No URL match." +msgstr "" + +#: relations.py:281 +msgid "Invalid hyperlink - Incorrect URL match." +msgstr "" + +#: relations.py:282 +msgid "Invalid hyperlink - Object does not exist." +msgstr "" + +#: relations.py:283 +#, python-brace-format +msgid "Incorrect type. Expected URL string, received {data_type}." +msgstr "" + +#: relations.py:448 +#, python-brace-format +msgid "Object with {slug_name}={value} does not exist." +msgstr "" + +#: relations.py:449 +msgid "Invalid value." +msgstr "ค่าไม่ถูกต้อง" + +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "ข้อมูลไม่ถูกต้อง ต้องการ Dictionary แต่ได้รับ {datatype}" + +#: templates/rest_framework/admin.html:116 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 +msgid "Filters" +msgstr "การกรองข้อมูล" + +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" + +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" + +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "ไม่มี" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 +msgid "No items to select." +msgstr "" + +#: validators.py:39 +msgid "This field must be unique." +msgstr "" + +#: validators.py:89 +#, python-brace-format +msgid "The fields {field_names} must make a unique set." +msgstr "" + +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" date." +msgstr "" + +#: validators.py:258 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" month." +msgstr "" + +#: validators.py:271 +#, python-brace-format +msgid "This field must be unique for the \"{date_field}\" year." +msgstr "" + +#: versioning.py:40 +msgid "Invalid version in \"Accept\" header." +msgstr "" + +#: versioning.py:71 +msgid "Invalid version in URL path." +msgstr "" + +#: versioning.py:116 +msgid "Invalid version in URL path. Does not match any version namespace." +msgstr "" + +#: versioning.py:148 +msgid "Invalid version in hostname." +msgstr "" + +#: versioning.py:170 +msgid "Invalid version in query parameter." +msgstr "" diff --git a/rest_framework/locale/tr/LC_MESSAGES/django.mo b/rest_framework/locale/tr/LC_MESSAGES/django.mo index fcdff0a983176ba0193a41a95a3141ea69bf66f2..6386aab5236ae56a95525a2b2c525baa555c3963 100644 GIT binary patch delta 2167 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 diff --git a/rest_framework/locale/tr/LC_MESSAGES/django.po b/rest_framework/locale/tr/LC_MESSAGES/django.po index d327ab9e22..106fe71778 100644 --- a/rest_framework/locale/tr/LC_MESSAGES/django.po +++ b/rest_framework/locale/tr/LC_MESSAGES/django.po @@ -8,16 +8,16 @@ # Ertaç Paprat , 2015 # José Luis , 2016 # Mesut Can Gürle , 2015 -# Murat Çorlu , 2015 +# Murat Çorlu , 2015 # Recep KIRMIZI , 2015 # Ülgen Sarıkavak , 2015 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Turkish (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/tr/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -25,40 +25,40 @@ msgstr "" "Language: tr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Geçersiz yetkilendirme başlığı. Gerekli uygunluk kriterleri sağlanmamış." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Geçersiz yetkilendirme başlığı. Uygunluk kriterine ait veri boşluk karakteri içermemeli." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Geçersiz yetkilendirme başlığı. Uygunluk kriterleri base64 formatına uygun olarak kodlanmamış." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Geçersiz kullanıcı adı/parola" -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Kullanıcı aktif değil ya da silinmiş." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Geçersiz token başlığı. Kimlik bilgileri eksik." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Geçersiz token başlığı. Token'da boşluk olmamalı." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Geçersiz token başlığı. Token geçersiz karakter içermemeli." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Geçersiz token." @@ -66,382 +66,515 @@ msgstr "Geçersiz token." msgid "Auth Token" msgstr "Kimlik doğrulama belirteci" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Anahtar" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Kullanan" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Oluşturulan" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "İşaret" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "İşaretler" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Kullanıcı adı" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Şifre" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Kullanıcı hesabı devre dışı bırakılmış." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Verilen bilgiler ile giriş sağlanamadı." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "\"Kullanıcı Adı\" ve \"Parola\" eklenmeli." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Sunucu hatası oluştu." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Bozuk istek." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Giriş bilgileri hatalı." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Giriş bilgileri verilmedi." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Bu işlemi yapmak için izniniz bulunmuyor." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Bulunamadı." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "\"{method}\" metoduna izin verilmiyor." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "İsteğe ait Accept başlık bilgisi yanıt verilecek başlık bilgileri arasında değil." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "İstekte desteklenmeyen medya tipi: \"{media_type}\"." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Üst üste çok fazla istek yapıldı." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Bu alan zorunlu." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Bu alan boş bırakılmamalı." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" geçerli bir boolean değil." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Bu alan boş bırakılmamalı." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Bu alanın {max_length} karakterden fazla karakter barındırmadığından emin olun." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Bu alanın en az {min_length} karakter barındırdığından emin olun." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Geçerli bir e-posta adresi girin." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Bu değer gereken düzenli ifade deseni ile uyuşmuyor." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Harf, rakam, altçizgi veya tireden oluşan geçerli bir \"slug\" giriniz." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Geçerli bir URL girin." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" geçerli bir UUID değil." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Geçerli bir IPv4 ya da IPv6 adresi girin." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Geçerli bir tam sayı girin." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Değerin {max_value} değerinden küçük ya da eşit olduğundan emin olun." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Değerin {min_value} değerinden büyük ya da eşit olduğundan emin olun." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "String değeri çok uzun." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Geçerli bir numara gerekiyor." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Toplamda {max_digits} haneden fazla hane olmadığından emin olun." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Ondalık basamak değerinin {max_decimal_places} haneden fazla olmadığından emin olun." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Ondalık ayracından önce {max_whole_digits} basamaktan fazla olmadığından emin olun." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datetime alanı yanlış biçimde. {format} biçimlerinden birini kullanın." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Datetime değeri bekleniyor, ama date değeri geldi." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Tarih biçimi yanlış. {format} biçimlerinden birini kullanın." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Date tipi beklenmekteydi, fakat datetime tipi geldi." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Time biçimi yanlış. {format} biçimlerinden birini kullanın." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Duration biçimi yanlış. {format} biçimlerinden birini kullanın." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" geçerli bir seçim değil." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "{count} elemandan daha fazla..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Elemanların listesi beklenirken \"{input_type}\" alındı." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Bu seçim boş bırakılmamalı." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" geçerli bir yol seçimi değil." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Hiçbir dosya verilmedi." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Gönderilen veri dosya değil. Formdaki kodlama tipini kontrol edin." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Hiçbir dosya adı belirlenemedi." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Gönderilen dosya boş." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Bu dosya adının en fazla {max_length} karakter uzunluğunda olduğundan emin olun. (şu anda {length} karakter)." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Geçerli bir resim yükleyin. Yüklediğiniz dosya resim değil ya da bozuk." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Bu liste boş olmamalı." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Sözlük tipi bir değişken beklenirken \"{input_type}\" tipi bir değişken alındı." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Değer geçerli bir JSON olmalı." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Gönder" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Arama" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Sıralama" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Geçersiz sayfa." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Sayfalandırma imleci geçersiz" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Geçersiz pk \"{pk_value}\" - obje bulunamadı." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Hatalı tip. Pk değeri beklenirken, alınan {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Geçersiz bağlantı - Hiçbir URL eşleşmedi." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Geçersiz bağlantı - Yanlış URL eşleşmesi." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Geçersiz bağlantı - Obje bulunamadı." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Hatalı tip. URL metni bekleniyor, {data_type} alındı." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "{slug_name}={value} değerini taşıyan obje bulunamadı." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Geçersiz değer." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Geçersiz veri. Sözlük bekleniyordu fakat {datatype} geldi. " #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtreler" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Alan filtreleri" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Sıralama" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Arama" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Hiçbiri" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Seçenek yok." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Bu alan eşsiz olmalı." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "{field_names} hep birlikte eşsiz bir küme oluşturmalılar." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Bu alan \"{date_field}\" tarihine göre eşsiz olmalı." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Bu alan \"{date_field}\" ayına göre eşsiz olmalı." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Bu alan \"{date_field}\" yılına göre eşsiz olmalı." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "\"Accept\" başlığındaki sürüm geçersiz." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "URL dizininde geçersiz versiyon." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Host adında geçersiz versiyon." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Sorgu parametresinde geçersiz versiyon." - -#: views.py:88 -msgid "Permission denied." -msgstr "Erişim engellendi." diff --git a/rest_framework/locale/tr_TR/LC_MESSAGES/django.mo b/rest_framework/locale/tr_TR/LC_MESSAGES/django.mo index 2999352a2ab57f2e586958fad1b49c03fd194728..3751732bed5bf46bde9aeb814b71f2a8e1d419a5 100644 GIT binary patch delta 2430 zcmZwHU2IfE7{>8gmX?BmmM*jfk)gD9i?pGMq`LkTOv0YzeoU)NQ^fmL?Oh)pwa)cduqIJ(szF|=bX-& zd1vO_YWkoter?j|qlU7TxL@Hl=Ag{vhvLsN#$pK;;Tp`tov7yz;WT^)$Ke-PiWhJW z-o-KKA8X7qEXS4Dn>KDfp|Xk#9{#%)>#+r2#E0=Z1~LC0V;Zm-tMEAL{#9IyzVXJ) zLI)?|^QZy7=3f8M^)jlzzc66p#w?nU9$^$U!vWL_-oS-;32QKWqA|;{3Cr*(@@Izm z(R1ISAIINo%uEd6Y}|x&VV=cudE zrSi7h4rHhAuSYM}`%xL#hY#RkRDY+j7{A0R_$$UOl}w(_!D1YZK_u&@7UyCEvW#ZG z>vO0M4x_fo+n9^*VL1+=mh=v4z>Dcz_uEkIU8p_tat`@lK;>O7q}CeM&iAE9+=zN` z2U;9N4d@gq1D7xZr%g@|WF|gD+m98v5!>(u)Wm+meEbD9p}!}Se_hC8I+a*}>_^j# z%0LIc?=j{n)ZWNr>uXJ`Q8Vhs>G%vP)yGf+{?zpfZl?V^vfoS-FI$RRa4o(Wr&34d zCbnTE3#Co=5~}?*DwVV8wHad=!}HjHLGr1N25~ihj~d`~Mx_C+#sb`m^YNhT$Eg0| zH>oV5vYg>-=1(JIF@v}ekE8Bi!6(o+gRJ0wY{VasePrfX))}i&ORygw#FMB2e1+^+ za|=sx6i0*s#Z5UCtz`)H!c(XRuVW$JM!g`HUUY0qkg=Fn)Qks^IhfB-FT93hFoPqb zlutnQGZh)DslgKLz&xG*LsWQ&Ie|K-!>G0Y7S+Lzs0`ddWvGzeGO-Nx!T@R$wxcq) z4Xf|~*5OB}rT7yyF&`UA6DX3r-_%hFU?RrKRAjB7;asVD=NQEPV>Gq9MaGO-jr=q1z<$NJy0 ziW;XWRg^ttXd@9M)R&5~r-^9LTZs;00YQnUQlgqNpmlC1T8NRtSW;(MyI0F%36;&n z3ZjYNkfe_1CTt*7lnIp$gl4Ux<=`uwD%z}bwEq7sOKG&Z7nC|OXj+MC!bhl-6H4FX z#Arfmt#1dP2=fS`!WZ4FCv?QvHf9S^PiTLrtR`xS@p`{fuk})4kC;bOwRGy;YU*U@ z%eRu4;a=1+;xk|#bKBb0>)iShPC~T%FP~v9i3hg1>pT zVo}xnK%zD`*S0$S-JzIW7wd}d>f37_NmPnUlw}*yKYJ~f2@6g%U(Al0c)4YDI-5U+} z{r_#dA^n_ny24Iu_>7(kojVpLLJ_;IkNe*lRWCGY?M delta 2571 zcmZY9e@s@g{aCiPYW|qbKbmVbi#D6K_5PfDTx~tO-}8E&hr@H8 z-zVE{u8m*GO@G6XI*I#rLfnlOCU7>M#2NS_`tdK+|L2Z1W*W}L zakvW0a08a&9?ZmHY&0frPLWwo#U0Nk{%Zy0S8)km#0@xkyfHPn3+wR+uEHx=hb0qI zf46Zp<#%u<-oRYU$xd~!5LLg(HEv7?88!GaR^wUZXKwLOPcw2-BhEp6s0wQ_f{XEe zY{H+h97`wiG2DuJZxxgDdw!4n%rzcrXB4%4#?#1D%te(;umUSlOWZM;^;gf2doO;DDqlryqB}Sr{ZmqF z+lnghMUCK7)O$amg@2+tn4jlvF0&lRP##2eU_U;Lhp-aQ<}v@9$&6B=k*=Zp1-Q<$ z8#U6sUio!=gz|AbmS)Ule30@>908q*6R5Slh+3MzQ8Q3TqtkH#Y6&)?I^G}mG6!%A z6(1oR){JF%OEDj{n>S%I9>vXg1GVXzX+`B%P*eI7wqps?7{%vNr{oV*JB#T>3+_X8 zH2xhK^-$d^0zXz^y=M=q!K1hUZzCJmR6jr`aT(HQ(~7m|px!%zJMj{3$0o~|HXK6s zpBcr4I{z7ksWoXrO=SetkweJFH>a=+FQ7Vh3mdVFm-N9N)O#P`EIf_+z*W>~x`~>4 zi}jF;P~X{xnS9^8NJdls8mfV}kWQO3_z+%3meFMLN4{+=)cw$iIu&iG|KExlc^7JC zj$j6!Ky@gI`tW(wru+p9_`dm_j4ql9?4UZFg3Z{9TAG8X5uQM1-<R9<2?4^rJS#3#bM^L~WXDsQ1c?Q^%$T`5A|Y zI(7gxGefB3bsp1j1ECq=6ua$%o75@IP^s2mNz-^Y>BCAI2}|dudY+FGwCkpgJXaE9 z2rZR5&4F-7)rLx1Yt5pP_Rm&gJF%QlCo2f|`?^Z=2(9Tdg4uLa6VG)-2f>Br9xpI! z^dQYAvfNy1pD4SHX!YuJ!8Ce#3w1GVA?_iRIH{&xFc}lF(++Xmvy)Av+K*PZVdhOfMRsvd{kiTkY*Do0H)z-rpt)Lxt>|l9fUv5pty1tHXr#E>vx7ar&(AgR33-{{#K_?dI z22t!H|^gcJ!PLeW=BJg)!{@fcR)M* z(=#S64F$sPh@y7LjylO_XI%Fsek;mPepb|%HfiMJXPmGVwSz%B=!Ak!d7^4oOXiAm vpM-;UG&wM6Ae5)G92nwMS0@gIfMTIWFJG% diff --git a/rest_framework/locale/tr_TR/LC_MESSAGES/django.po b/rest_framework/locale/tr_TR/LC_MESSAGES/django.po index 94856c70fd..b0c96ddd18 100644 --- a/rest_framework/locale/tr_TR/LC_MESSAGES/django.po +++ b/rest_framework/locale/tr_TR/LC_MESSAGES/django.po @@ -3,55 +3,56 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Deniz , 2019 # José Luis , 2015-2016 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Turkish (Turkey) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/tr_TR/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: tr_TR\n" -"Plural-Forms: nplurals=1; plural=0;\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Geçersiz yetkilendirme başlığı. Gerekli uygunluk kriterleri sağlanmamış." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "Geçersiz yetkilendirme başlığı. Uygunluk kriterine ait veri boşluk karakteri içermemeli." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "Geçersiz yetkilendirme başlığı. Uygunluk kriterleri base64 formatına uygun olarak kodlanmamış." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Geçersiz kullanıcı adı / şifre." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Kullanıcı aktif değil ya da silinmiş" -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "Geçersiz token başlığı. Kimlik bilgileri eksik." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "Geçersiz token başlığı. Token'da boşluk olmamalı." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "Geçersiz token başlığı. Token geçersiz karakter içermemeli." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Geçersiz simge." @@ -59,382 +60,515 @@ msgstr "Geçersiz simge." msgid "Auth Token" msgstr "Kimlik doğrulama belirteci" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Anahtar" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Kullanan" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Oluşturulan" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "İşaret" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "İşaretler" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Kullanıcı adı" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Şifre" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Kullanıcı hesabı devre dışı bırakılmış." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Verilen bilgiler ile giriş sağlanamadı." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "\"Kullanıcı Adı\" ve \"Parola\" eklenmeli." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Sunucu hatası oluştu." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Bozuk istek." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Giriş bilgileri hatalı." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Giriş bilgileri verilmedi." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "Bu işlemi yapmak için izniniz bulunmuyor." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Bulunamadı." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "\"{method}\" metoduna izin verilmiyor." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "İsteğe ait Accept başlık bilgisi yanıt verilecek başlık bilgileri arasında değil." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "İstekte desteklenmeyen medya tipi: \"{media_type}\"." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Üst üste çok fazla istek yapıldı." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Bu alan zorunlu." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Bu alan boş bırakılmamalı." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" geçerli bir boolean değil." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Bu alan boş bırakılmamalı." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Bu alanın {max_length} karakterden fazla karakter barındırmadığından emin olun." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Bu alanın en az {min_length} karakter barındırdığından emin olun." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Geçerli bir e-posta adresi girin." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Bu değer gereken düzenli ifade deseni ile uyuşmuyor." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Harf, rakam, altçizgi veya tireden oluşan geçerli bir \"slug\" giriniz." -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Geçerli bir URL girin." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" geçerli bir UUID değil." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "Geçerli bir IPv4 ya da IPv6 adresi girin." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Geçerli bir tam sayı girin." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Değerin {max_value} değerinden küçük ya da eşit olduğundan emin olun." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Değerin {min_value} değerinden büyük ya da eşit olduğundan emin olun." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "String değeri çok uzun." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Geçerli bir numara gerekiyor." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Toplamda {max_digits} haneden fazla hane olmadığından emin olun." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Ondalık basamak değerinin {max_decimal_places} haneden fazla olmadığından emin olun." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Ondalık ayracından önce {max_whole_digits} basamaktan fazla olmadığından emin olun." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Datetime alanı yanlış biçimde. {format} biçimlerinden birini kullanın." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Datetime değeri bekleniyor, ama date değeri geldi." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Tarih biçimi yanlış. {format} biçimlerinden birini kullanın." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Date tipi beklenmekteydi, fakat datetime tipi geldi." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Time biçimi yanlış. {format} biçimlerinden birini kullanın." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Duration biçimi yanlış. {format} biçimlerinden birini kullanın." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" geçerli bir seçim değil." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "{count} elemandan daha fazla..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Elemanların listesi beklenirken \"{input_type}\" alındı." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Bu seçim boş bırakılmamalı." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" geçerli bir yol seçimi değil." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Hiçbir dosya verilmedi." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Gönderilen veri dosya değil. Formdaki kodlama tipini kontrol edin." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Hiçbir dosya adı belirlenemedi." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Gönderilen dosya boş." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Bu dosya adının en fazla {max_length} karakter uzunluğunda olduğundan emin olun. (şu anda {length} karakter)." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Geçerli bir resim yükleyin. Yüklediğiniz dosya resim değil ya da bozuk." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Bu liste boş olmamalı." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Sözlük tipi bir değişken beklenirken \"{input_type}\" tipi bir değişken alındı." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Değer geçerli bir JSON olmalı." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Gönder" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Arama" -#: filters.py:336 -msgid "ascending" +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Sıralama" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:337 +#: filters.py:287 +msgid "ascending" +msgstr "artan" + +#: filters.py:288 msgid "descending" +msgstr "azalan" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." msgstr "" -#: pagination.py:193 +#: pagination.py:189 msgid "Invalid page." msgstr "Geçersiz sayfa." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Geçersiz imleç." -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Geçersiz pk \"{pk_value}\" - obje bulunamadı." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Hatalı tip. Pk değeri beklenirken, alınan {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Geçersiz hyper link - URL maçı yok." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Geçersiz hyper link - Yanlış URL maçı." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Geçersiz hyper link - Nesne yok.." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Hatalı tip. URL metni bekleniyor, {data_type} alındı." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "{slug_name}={value} değerini taşıyan obje bulunamadı." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Geçersiz değer." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Geçersiz veri. Bir sözlük bekleniyor, ama var {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Filtreler" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Alan filtreleri" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Sıralama" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Arama" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Hiç kimse" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Seçenek yok." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Bu alan benzersiz olmalıdır." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "{field_names} alanları benzersiz bir set yapmak gerekir." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Bu alan \"{date_field}\" tarihine göre eşsiz olmalı." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Bu alan \"{date_field}\" ayına göre eşsiz olmalı." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Bu alan \"{date_field}\" yılına göre eşsiz olmalı." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "\"Kabul et\" başlığında geçersiz sürümü." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." -msgstr "URL yolu geçersiz sürümü." +msgstr "URL yolunda geçersiz sürüm." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "URL yolunda geçersiz sürüm. Sürüm adlarında eşleşen bulunamadı." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Hostname geçersiz sürümü." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Sorgu parametresi geçersiz sürümü." - -#: views.py:88 -msgid "Permission denied." -msgstr "İzin reddedildi." diff --git a/rest_framework/locale/uk/LC_MESSAGES/django.mo b/rest_framework/locale/uk/LC_MESSAGES/django.mo index 9772bedc56fe9bc27e1165f523ee45b920246a8b..18c3242bbed7b0a0ad675349c155e3939d2d3c1d 100644 GIT binary patch delta 2803 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, 2016 +# Denis Podlesniy , 2016 # Illarion , 2016 -# Kirill Tarasenko, 2016 +# Kirill Tarasenko, 2016,2018 # Victor Mireyev , 2017 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Victor Mireyev \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Ukrainian (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/uk/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: uk\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "Недійсний основний заголовок. Облікові дані відсутні." -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." -msgstr "Недійсний основний заголовок. Облікові дані мають бути без пробілів." +msgstr "Недійсний основний заголовок. Строка з обліковими даними має бути без пробілів." -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." -msgstr "Недійсний основний заголовок. Облікові дані невірно закодовані у Base64." +msgstr "Недійсний основний заголовок. Облікові дані невірно закодовані у base64." -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "Недійсне iм'я користувача/пароль." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "Користувач неактивний або видалений." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." -msgstr "Недійсний заголовок токена. Облікові дані відсутні." +msgstr "Недійсний токен. Облікові дані відсутні." -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." -msgstr "Недійсний заголовок токена. Значення токена не повинне містити пробіли." +msgstr "Недійсний токен. Значення токена не повинне містити пробіли." -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." -msgstr "Недійсний заголовок токена. Значення токена не повинне містити некоректні символи." +msgstr "Недійсний токен. Значення токена не повинне містити некоректні символи." -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "Недійсний токен." @@ -62,382 +62,515 @@ msgstr "Недійсний токен." msgid "Auth Token" msgstr "Авторизаційний токен" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "Ключ" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "Користувач" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "Створено" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "Токен" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "Токени" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "Ім'я користувача" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "Пароль" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "Обліковий запис деактивований." - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "Неможливо зайти з введеними даними." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "Має включати iм'я користувача та пароль" -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "Помилка сервера." -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "Некоректний запит." -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "Некоректні реквізити перевірки достовірності." -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "Реквізити перевірки достовірності не надані." -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "У вас нема дозволу робити цю дію." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "Не знайдено." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "Метод \"{method}\" не дозволений." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." -msgstr "Неможливо виконати запит прийняття заголовку." +msgstr "Неможливо виконати запит заголовку Accept." -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "Непідтримуваний тип даних \"{media_type}\" в запиті." -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "Запит було проігноровано." -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "Це поле обов'язкове." -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "Це поле не може бути null." -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "\"{input}\" не є коректним бульовим значенням." +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "Це поле не може бути порожнім." -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "Переконайтесь, що кількість символів в цьому полі не перевищує {max_length}." -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "Переконайтесь, що в цьому полі мінімум {min_length} символів." -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "Введіть коректну адресу електронної пошти." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "Значення не відповідає необхідному патерну." -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "Введіть коректний \"slug\", що складається із букв, цифр, нижніх підкреслень або дефісів. " -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "Введіть коректний URL." -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "\"{value}\" не є коректним UUID." +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Введіть дійсну IPv4 або IPv6 адресу." +msgstr "Введіть коректну IPv4 або IPv6 адресу." -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "Необхідне цілочисельне значення." -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "Переконайтесь, що значення менше або дорівнює {max_value}." -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "Переконайтесь, що значення більше або дорівнює {min_value}." -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "Строкове значення занадто велике." -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "Необхідне чисельне значення." -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "Переконайтесь, що в числі не більше {max_digits} знаків." -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "Переконайтесь, що в числі не більше {max_decimal_places} знаків у дробовій частині." -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "Переконайтесь, що в числі не більше {max_whole_digits} знаків у цілій частині." -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "Невірний формат дата з часом. Використайте один з цих форматів: {format}." -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "Очікувалась дата з часом, але було отримано дату." -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "Невірний формат дати. Використайте один з цих форматів: {format}." -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "Очікувалась дата, але було отримано дату з часом." -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "Неправильний формат часу. Використайте один з цих форматів: {format}." -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "Невірний формат тривалості. Використайте один з цих форматів: {format}." -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "\"{input}\" не є коректним вибором." -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "Елементів більше, ніж {count}..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "Очікувався список елементів, але було отримано \"{input_type}\"." -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "Вибір не може бути порожнім." -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\" вибраний шлях не є коректним." -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "Файл не було відправленно." -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "Відправленні дані не є файл. Перевірте тип кодування у формі." -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "Неможливо визначити ім'я файлу." -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "Відправленний файл порожній." -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "Переконайтесь, що ім'я файлу становить менше {max_length} символів (зараз {length})." -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "Завантажте коректне зображення. Завантажений файл або не є зображенням, або пошкоджений." -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "Цей список не може бути порожнім." -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "Очікувався словник зі елементами, але було отримано \"{input_type}\"." -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "Значення повинно бути коректним JSON." -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "Відправити" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Пошук" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Впорядкування" + +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" -msgstr "в порядку зростання" +msgstr "у порядку зростання" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "у порядку зменшення" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "Недійсна сторінка." -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "Недійсний курсор." -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "Недопустимий первинний ключ \"{pk_value}\" - об'єкт не існує." -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "Некоректний тип. Очікувалось значення первинного ключа, отримано {data_type}." -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "Недійсне посилання - немає збігу за URL." -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "Недійсне посилання - некоректний збіг за URL." -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "Недійсне посилання - об'єкт не існує." -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "Некоректний тип. Очікувався URL, отримано {data_type}." -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "Об'єкт із {slug_name}={value} не існує." -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "Недійсне значення." -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "Недопустимі дані. Очікувався словник, але було отримано {datatype}." #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "Фільтри" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "Фільтри поля" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "Впорядкування" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "Пошук" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "Нічого" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "Немає елементів для вибору." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "Це поле повинне бути унікальним." -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "Поля {field_names} повинні створювати унікальний масив значень." -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "Це поле повинне бути унікальним для дати \"{date_field}\"." -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "Це поле повинне бути унікальним для місяця \"{date_field}\"." -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "Це поле повинне бути унікальним для року \"{date_field}\"." -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "Недопустима версія в загаловку \"Accept\"." -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "Недопустима версія в шляху URL." -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." -msgstr "" +msgstr "Недопустима версія в шляху URL. Не співпадає з будь-яким простором версій." -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "Недопустима версія в імені хоста." -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "Недопустима версія в параметрі запиту." - -#: views.py:88 -msgid "Permission denied." -msgstr "Доступ заборонено." diff --git a/rest_framework/locale/vi/LC_MESSAGES/django.mo b/rest_framework/locale/vi/LC_MESSAGES/django.mo index c76cfe595858a600a265c1bad77f18fb670ba781..6809b650c1d85cfa5ec65341ba957cd98eaa09da 100644 GIT binary patch literal 2179 zcmZvcO>Epm6vqcB6qfG-2R`)_4p6oBCIP9{mLO=8qR=J{&9;IAhgo}KPh-y*d%T;d zs#MfNg(^OB;804XMiqymR%rnVkv$;fs28MOxuLojgpjy$-~j*e?zTx(YiEC+ulN4% zy&3<$ZOfMo&ogK*psk_3h!)+0KRh?VtzZf61^)!?x}Enj_8d3~-Us^NOJE4T0-gq+ z0$0EqSbz_LH~Rbyd;tBMptbi0_&m4;#&&=NZUGO1&w}rPcK>|8Z@^LXKLgJ}(=VX) z^WpuBwZIUxc7E;i9}qwGGMtWq+rddN0ImOPpw)W=d>Fh1J_7#T@Ba(#M1L!iVfi|^ z8|2_);8)<=;3d%7`3u|y{s$facR@U4vTbOPT0K^e^|k(a2py!NiXB_4x;))qVD;GN zNi^%z`WZYn_9xIDMYDN&91W5g+m{M%gwMwDPa)5!lWG;voK9nITso2o(h6;kOeoOl zO=KihW(4;ebi6lm!etZ6h?F?Bc_e)jF_#f>-%mxBxolGPhVx9MipQd!@GR@-)OXnd zvB;)`3AIl{$K&ed#1JWMh)0p`2rQY(GXs}il>35)1~!I>E9@Cq8gkjRCTT>Rk*hq9 zm_?TzQM?ri>n_p(6jDc;kdjolr7wK)QsE0_B#)2+?n_NL9q=PB6?|iclOY<%RbOK`MQ!9a%@jT8p|w`T3H3?*;=4b2*x_l%4IXDK87?n zlfH9Qq;_e8#*c9oXgWMG+oZV^E>Va1+F_CC)HqXFAbn?V9%N2aH>fsq$T=+9((ZKZ zA!*R)@aV8Jg5PU+Z48g@7{*gO$TQcprcY4n*^M+S3_fxM!nwYbX;TY$~m!MH`V=0zk`EioZI@H zk-LxAQN+qay)En8Yg3a`6L&H(;ttpLX`EXH?TZP5Gh)HilZZ>ThrE!dnJ{BVn){vI zch*@gb0T#nl&5{1rUvb9Npm}r2DN2vCdyMDIr~wMtU)TNy4l#s9_qc04ezP3wJ(Z` zWXjcL&8CCm!Xjl{(%MqlJ)=VMF|Goa#*3eDALFun){v?0rV7jM3dT0B#OAyjI)wv( zGi<_g_2>21a~cCzlD?&CFGqxB_mlor#p}g{qOyD1WwQ`sif#m2;q|N6A=vuuUh?SH$*%`@`PRwvSX}&2Nxps+ZanLF^_!$; z7pHV@xw?YWo+C<+63ktcJcyPu;tWp@b` zV>HfDzpPmH!VSx9W=;)CG*~>4)Sp|Q?RMEchcfg6GWfQvE#bWMN`cSXhLmjT4itLD zcdDw|vZ)-ORl&|d6)S5{eufrMKz!zGy*DPBy(5d|DzG!ux=V>76k*dT?0u+E&t)|b ov9>fQdR23L0?O{kHX54_o0MxFg%aluQi}^not=TwDtRvZAC2wn00000 delta 140 zcmZn`yvl5GPl#nI0}wC*u?!Ha05LNV>i{tbSOD>Cprj>`2C0F8$p@IjTn){14a{{7 zjT8(`tqe`H4S;~lC$YFhH>4;ruQ(^MB)`Z?AtWO|H?dg3Iio1ExFj=G!KNfX*8zx= Lp#qzuS(q3B`#l}{ diff --git a/rest_framework/locale/vi/LC_MESSAGES/django.po b/rest_framework/locale/vi/LC_MESSAGES/django.po index ea43efb951..8054fb880d 100644 --- a/rest_framework/locale/vi/LC_MESSAGES/django.po +++ b/rest_framework/locale/vi/LC_MESSAGES/django.po @@ -3,13 +3,15 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Nguyen Tuan Anh , 2019 +# Xavier Ordoquy , 2020 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2016-07-12 15:14+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 20:02+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Vietnamese (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/vi/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -17,40 +19,40 @@ msgstr "" "Language: vi\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "" -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "" -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "" -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." -msgstr "" +msgstr "Sai tên đăng nhập hoặc mật khẩu." -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." -msgstr "" +msgstr "Người dùng không còn hoạt động, hoặc đã bị xoá." -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "" -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "" -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "" -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "" @@ -58,382 +60,515 @@ msgstr "" msgid "Auth Token" msgstr "" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" -msgstr "" +msgstr "Khoá" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "Người dùng" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" -msgstr "" +msgstr "Đã tạo" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "Tên người dùng" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" - -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "" +msgstr "Mật khẩu" -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." -msgstr "" +msgstr "Không thể đăng nhập với thông tin đã nhập." -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." -msgstr "" +msgstr "Bắt buộc phải có “tên người dùng” và “mật khẩu”." -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "" -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "" -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "" -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "" -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." -msgstr "" +msgstr "Bạn không được cấp quyền để thực hiện hành động này." -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." -msgstr "" +msgstr "Không tìm thấy." -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." -msgstr "" +msgstr "Phương thức “{method}” không được chấp nhận." -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "" -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "" -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "" -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "" -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." +#: fields.py:701 +msgid "Must be a valid boolean." msgstr "" -#: fields.py:674 -msgid "This field may not be blank." +#: fields.py:766 +msgid "Not a valid string." msgstr "" -#: fields.py:675 fields.py:1675 +#: fields.py:767 +msgid "This field may not be blank." +msgstr "Trường này không được bỏ trống." + +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "" -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "" -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." -msgstr "" +msgstr "Nhập một địa chỉ email hợp lệ." -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "" -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "" -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "" -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." +#: fields.py:867 +msgid "Must be a valid UUID." msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "" -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "" -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "" -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "" -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "" -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "" -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "" -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "" -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "" -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "" -#: fields.py:1103 -msgid "Date has wrong format. Use one of these formats instead: {format}." +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." msgstr "" -#: fields.py:1104 +#: fields.py:1236 +#, python-brace-format +msgid "Date has wrong format. Use one of these formats instead: {format}." +msgstr "Ngày sai định dạng. Dùng một trong những định dạng sau để thay thế: {format}." + +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "" -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "" -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "" -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "" -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "" -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "" -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "" -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "" -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "" -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "" -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." +msgstr " Danh sách này không thể để trống." + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." msgstr "" -#: fields.py:1502 +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "" -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." +msgstr "Giá trị bắt buộc phải là định dạng JSON." + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "Tìm kiếm" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "Sắp xếp" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" msgstr "" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "" -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "" -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "" -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "" -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "" -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "" -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "" -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "" -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "" #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" + +#: templates/rest_framework/base.html:157 +msgid "main content" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." -msgstr "" +msgstr "Không có gì để chọn." -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "" -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "" -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "" -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "" -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "" -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "" -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "" -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "" - -#: views.py:88 -msgid "Permission denied." -msgstr "" diff --git a/rest_framework/locale/zh_CN/LC_MESSAGES/django.mo b/rest_framework/locale/zh_CN/LC_MESSAGES/django.mo index f30b04ea1247a5137aa9f627878327df308812bf..3bd1fd3efa65ee170719887e0f0eff038e4accb8 100644 GIT binary patch delta 2239 zcmYk-drXye9LMo5!9hf_@F0c=9*hKnLkIC7Hx(~fM=(%9rHgQu%|TA$5>dI%W~J3; zYo6)c%s(>by77k{TT9nk$?T8T`or1GTsP-GZO&?~*__q;^E^LV-|>B3zu$A7%kTQ| zN#~gjiSOrR95SMhT%b%hdqHM$Aj-4MLRf+K;Cj3ZpF~~%3KrrC%*M|#j9+0D-ozPL zKGSS9MsOXD_$KT$h5I>?#@#I#!!7tKF2kR&2@7VKwP80l;+v@7FJn9A<(S=#ah!uM zp&EF^o&V7JE$VrI=fJCEg< zbB9?8M)6+Uiaf&hVgz5uYWxgiyx)GMupArj^dC5m^QrH5_2a0XokaEM0%|CKclBtl z|NACP=lmFI1fIoWJb-%M`&fowU_M^QM2JErR|l~SGq4F6b!)-H!B( z%VYxQ;d>atv#2Tk1J&SW9dI| z+o%z^hyg5|>o=qXE2)=b4R&Dx=|z0izm{| z22fLw%hK1JMo|sxLk;mBS3ioZEBnZupTfn|ub`fr&C6C`9XQ~hH;(4Hs8gLC6TnjGp zM=p$o+W*_V0^0?3!(r3|-a);164m0`fP+2-`p^_@dh; zvYTXfZ95qwwEz{`SuJFa-p|7H<_o-O&~{X43Z|o;g1&s~$c65t_OsT*!>+E~p&g{q zj$&tfq3_Fd=pWA-lHF{qTkk{WeVZwJ5#Y#I_`m3+w9?gX!`0-B^3$^C{{@9S+!g=; delta 2556 zcmZYAe@vBC9LMnkmy0O)3l*;#`h=obAfoakP*5O+1Vs^lkfj&6;$4?}n-|JW^OC=Y zGn)%3buhJ>>mSRB`75hsn`TgF!J&vT#ZkIwks*ExrC&-0w~ zJZIo0!|)!K8xt{)s4S%;o6$0i%kT;=#`t7o)?x(~;&Cj&A87NAyKin>q>7U2mj z!ymB%J)@18gBy`9a}YJ|IA-BZ)J_iJGdOh&*Wvo6n#x>Uhnk=ZtMMW(!nm=q3CnOY z?G;Yjk6Oug)XI91Ej5>&_K@@cNi5$*|02{*Ea5|`TZ0iz7^X4{-^Mh&fEM1sarhf1 zU_1@7VuoQZrXt6{lshg*hMF~~d0J7&XctbzeNMX%wcroOk$>g-HXT|)`uNzuVpMxI z>M(_{01u*ae9dV;K`kI}LTp?OTC}&KZtwuwJA!W7L#P{y=kA`x;c4uD9+iALHef4i zrC;GByzcl5YNbz{wu|M=qn(bNUsH$LnMOPjXN(VL({?k7Qk09zdA-x#g<9DA5h}{X z7f#1d$iX##IQ@w%ZzkH|j2ibYmf&s2v21Gt?OId{ zA|E)FTi8g4J3aQCZbm)Vhft^XH0Izpj*n4~>kRIG1-^nDY;y&5qu-*wcMGfVF)9^h z(_&A{ZshwBbDc^){}^e-R$hS`SciISx*e~g9>XYVhmx6n0eY|)YjF{F;Yz%Yq}!xr z#CA9v^}Xe&)U3Dr$^ULD<#e2~J9x9<4B9Rdr^hA>b*i&bH&BLT$N13RLS%Q%S;y~C z;~%06|3qE*FVqdEFu4xl63O*V6%}pWO4OEbat3TitsvsGdvHGOUVI+EM~zS8u4Jxb z6>=Pm7qzoH9nYc8#zWNjDIBZ_X)z0^Xa)7ioF;^&cmxyirsEydDZYn=n8HhA4OXJY z9Yalg64`xo8Fd!!qUMb{jv%iZpPNPgRVkn&4%ZRNC9ea+>;2!NNi|Wn)1yc2pQ0U6 z(MrjjT^91G-v*r@mGy+45;wtdvlpQqTEg|sDk>@*XnR{#*Gb<y?y*oO}ih{z^BbUJGZ(qli%CD=shv^EgR z^-N+lp+`(b=cCcC{r9n$DlbU;ji=t138h!LS1BiW&Df;t#a@wuSqHBd57j>wMeV{Nb=a6tr4=LBFqMPWM}>g^BeYO>O@6 z{%fgOuJPXH=1@nlU6*e0hrLY!4H}qmcT`?s&fKD$x$~{O1;zPA-8a%EPO9+*d{#{; z*lH~dH*fW|d7HzFTid+;K!3u-S#c>3j_-|L+7mr{B-(kkJI~|k&h(U6_d9!?8^7P# zH*o3D;L%T_hr0&*dl+rs`9@EsJ286Zy}^$^?SDOezkBrHxl@C^2L}80JUVvx*UsJD rLm7pM17G$<&m8TaH$B;v`tbbe!LEzZi{~Dke)GYRk2IuU=DxoHAU8W` diff --git a/rest_framework/locale/zh_CN/LC_MESSAGES/django.po b/rest_framework/locale/zh_CN/LC_MESSAGES/django.po index 345bcfac81..7e131db425 100644 --- a/rest_framework/locale/zh_CN/LC_MESSAGES/django.po +++ b/rest_framework/locale/zh_CN/LC_MESSAGES/django.po @@ -10,9 +10,9 @@ msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: Lele Long \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Chinese (China) (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/zh_CN/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -20,40 +20,40 @@ msgstr "" "Language: zh_CN\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "无效的Basic认证头,没有提供认证信息。" -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "认证字符串不应该包含空格(基本认证HTTP头无效)。" -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "认证字符串base64编码错误(基本认证HTTP头无效)。" -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "用户名或者密码错误。" -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "用户未激活或者已删除。" -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "没有提供认证信息(认证令牌HTTP头无效)。" -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "认证令牌字符串不应该包含空格(无效的认证令牌HTTP头)。" -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "无效的Token。Token字符串不能包含非法的字符。" -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "认证令牌无效。" @@ -61,382 +61,515 @@ msgstr "认证令牌无效。" msgid "Auth Token" msgstr "认证令牌" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "键" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "用户" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "已创建" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "令牌" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "令牌" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "用户名" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "密码" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "用户账户已禁用。" - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "无法使用提供的认证信息登录。" -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "必须包含 “用户名” 和 “密码”。" -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "服务器出现了错误。" -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "错误的请求。" -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "不正确的身份认证信息。" -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "身份认证信息未提供。" -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "您没有执行该操作的权限。" -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "未找到。" -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "方法 “{method}” 不被允许。" -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "无法满足Accept HTTP头的请求。" -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "不支持请求中的媒体类型 “{media_type}”。" -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "请求超过了限速。" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "该字段是必填项。" -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "该字段不能为 null。" -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "“{input}” 不是合法的布尔值。" +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "该字段不能为空。" -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "请确保这个字段不能超过 {max_length} 个字符。" -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "请确保这个字段至少包含 {min_length} 个字符。" -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "请输入合法的邮件地址。" -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "输入值不匹配要求的模式。" -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "请输入合法的“短语“,只能包含字母,数字,下划线或者中划线。" -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "请输入合法的URL。" -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "“{value}”不是合法的UUID。" +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "请输入一个有效的IPv4或IPv6地址。" -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "请填写合法的整数值。" -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "请确保该值小于或者等于 {max_value}。" -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "请确保该值大于或者等于 {min_value}。" -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "字符串值太长。" -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "请填写合法的数字。" -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "请确保总计不超过 {max_digits} 个数字。" -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "请确保总计不超过 {max_decimal_places} 个小数位。" -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "请确保小数点前不超过 {max_whole_digits} 个数字。" -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "日期时间格式错误。请从这些格式中选择:{format}。" -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "期望为日期时间,得到的是日期。" -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "日期格式错误。请从这些格式中选择:{format}。" -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "期望为日期,得到的是日期时间。" -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "时间格式错误。请从这些格式中选择:{format}。" -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "持续时间的格式错误。使用这些格式中的一个:{format}。" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "“{input}” 不是合法选项。" -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "多于{count}条记录。" -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "期望为一个包含物件的列表,得到的类型是“{input_type}”。" -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "这项选择不能为空。" -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "“{input}” 不是有效路径选项。" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "没有提交任何文件。" -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "提交的数据不是一个文件。请检查表单的编码类型。" -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "无法检测到文件名。" -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "提交的是空文件。" -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "确保该文件名最多包含 {max_length} 个字符 ( 当前长度为{length} ) 。" -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "请上传有效图片。您上传的该文件不是图片或者图片已经损坏。" -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "列表字段不能为空值。" -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "期望是包含类目的字典,得到类型为 “{input_type}”。" -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "值必须是有效的 JSON 数据。" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "保存" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "查找" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "排序" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "升序" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "降序" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "无效页。" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "无效游标" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "无效主键 “{pk_value}” - 对象不存在。" -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "类型错误。期望为主键,得到的类型为 {data_type}。" -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "无效超链接 -没有匹配的URL。" -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "无效超链接 -错误的URL匹配。" -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "无效超链接 -对象不存在。" -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "类型错误。期望为URL字符串,实际的类型是 {data_type}。" -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "属性 {slug_name} 为 {value} 的对象不存在。" -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "无效值。" -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "无效数据。期待为字典类型,得到的是 {datatype} 。" #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "过滤器" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "过滤器字段" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "排序" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr "查找" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "无" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "没有可选项。" -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "该字段必须唯一。" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "字段 {field_names} 必须能构成唯一集合。" -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "该字段必须在日期 “{date_field}” 唯一。" -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "该字段必须在月份 “{date_field}” 唯一。" -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "该字段必须在年 “{date_field}” 唯一。" -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "“Accept” HTTP头包含无效版本。" -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "URL路径包含无效版本。" -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "URL路径中存在无效版本。版本空间中无法匹配上。" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "主机名包含无效版本。" -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "请求参数里包含无效版本。" - -#: views.py:88 -msgid "Permission denied." -msgstr "没有权限。" diff --git a/rest_framework/locale/zh_Hans/LC_MESSAGES/django.mo b/rest_framework/locale/zh_Hans/LC_MESSAGES/django.mo index d85317ebc9205a26b27dd681d7f17aac6297dcaa..670228a8346076571906d56e43817d7e8354163c 100644 GIT binary patch delta 2239 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 k;Nazx;p&;Oajx6vPet}#9J+XJ_|!|oht6n7, 2015 # hunter007 , 2015 -# nypisces , 2015 +# 3eb8e7e672c2428f1223e00920bfd2b3, 2015 # ppppfly , 2017 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2017-08-03 14:58+0000\n" -"Last-Translator: ppppfly \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Chinese Simplified (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/zh-Hans/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -21,40 +21,40 @@ msgstr "" "Language: zh-Hans\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "无效的Basic认证头,没有提供认证信息。" -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "认证字符串不应该包含空格(基本认证HTTP头无效)。" -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "认证字符串base64编码错误(基本认证HTTP头无效)。" -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." msgstr "用户名或者密码错误。" -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." msgstr "用户未激活或者已删除。" -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "没有提供认证信息(认证令牌HTTP头无效)。" -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "认证令牌字符串不应该包含空格(无效的认证令牌HTTP头)。" -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "无效的Token。Token字符串不能包含非法的字符。" -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." msgstr "认证令牌无效。" @@ -62,382 +62,515 @@ msgstr "认证令牌无效。" msgid "Auth Token" msgstr "认证令牌" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" msgstr "键" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" msgstr "用户" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" msgstr "已创建" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" msgstr "令牌" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" msgstr "令牌" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" msgstr "用户名" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" msgstr "密码" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "用户账户已禁用。" - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "无法使用提供的认证信息登录。" -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." msgstr "必须包含 “用户名” 和 “密码”。" -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "服务器出现了错误。" -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "错误的请求。" -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "不正确的身份认证信息。" -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "身份认证信息未提供。" -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." msgstr "您没有执行该操作的权限。" -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." msgstr "未找到。" -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." msgstr "方法 “{method}” 不被允许。" -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "无法满足Accept HTTP头的请求。" -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "不支持请求中的媒体类型 “{media_type}”。" -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "请求超过了限速。" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." +msgstr "" + +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 msgid "This field is required." msgstr "该字段是必填项。" -#: fields.py:270 +#: fields.py:317 msgid "This field may not be null." msgstr "该字段不能为 null。" -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." -msgstr "“{input}” 不是合法的布尔值。" +#: fields.py:701 +msgid "Must be a valid boolean." +msgstr "" -#: fields.py:674 +#: fields.py:766 +msgid "Not a valid string." +msgstr "" + +#: fields.py:767 msgid "This field may not be blank." msgstr "该字段不能为空。" -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." msgstr "请确保这个字段不能超过 {max_length} 个字符。" -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." msgstr "请确保这个字段至少包含 {min_length} 个字符。" -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." msgstr "请输入合法的邮件地址。" -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "输入值不匹配要求的模式。" -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "请输入合法的“短语“,只能包含字母,数字,下划线或者中划线。" -#: fields.py:747 +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." +msgstr "" + +#: fields.py:854 msgid "Enter a valid URL." msgstr "请输入合法的URL。" -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." -msgstr "“{value}”不是合法的UUID。" +#: fields.py:867 +msgid "Must be a valid UUID." +msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." msgstr "请输入一个有效的IPv4或IPv6地址。" -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." msgstr "请填写合法的整数值。" -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." msgstr "请确保该值小于或者等于 {max_value}。" -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." msgstr "请确保该值大于或者等于 {min_value}。" -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." msgstr "字符串值太长。" -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." msgstr "请填写合法的数字。" -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "请确保总计不超过 {max_digits} 个数字。" -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "请确保总计不超过 {max_decimal_places} 个小数位。" -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "请确保小数点前不超过 {max_whole_digits} 个数字。" -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "日期时间格式错误。请从这些格式中选择:{format}。" -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." msgstr "期望为日期时间,获得的是日期。" -#: fields.py:1103 +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." +msgstr "" + +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "日期格式错误。请从这些格式中选择:{format}。" -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." msgstr "期望为日期,获得的是日期时间。" -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." msgstr "时间格式错误。请从这些格式中选择:{format}。" -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "持续时间的格式错误。使用这些格式中的一个:{format}。" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." msgstr "“{input}” 不是合法选项。" -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." msgstr "多于{count}条记录。" -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." msgstr "期望为一个包含物件的列表,得到的类型是“{input_type}”。" -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." msgstr "这项选择不能为空。" -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "\"{input}\"不是一个有效路径选项。" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." msgstr "没有提交任何文件。" -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." msgstr "提交的数据不是一个文件。请检查表单的编码类型。" -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "无法检测到文件名。" -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." msgstr "提交的是空文件。" -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "确保该文件名最多包含 {max_length} 个字符 ( 当前长度为{length} ) 。" -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "请上传有效图片。您上传的该文件不是图片或者图片已经损坏。" -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." msgstr "列表不能为空。" -#: fields.py:1502 +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." +msgstr "" + +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "期望是包含类目的字典,得到类型为 “{input_type}”。" -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." msgstr "值必须是有效的 JSON 数据。" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" -msgstr "提交" +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr " 搜索" + +#: filters.py:50 +msgid "A search term." +msgstr "" + +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "排序" -#: filters.py:336 +#: filters.py:181 +msgid "Which field to use when ordering the results." +msgstr "" + +#: filters.py:287 msgid "ascending" msgstr "正排序" -#: filters.py:337 +#: filters.py:288 msgid "descending" msgstr "倒排序" -#: pagination.py:193 +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." +msgstr "" + +#: pagination.py:189 msgid "Invalid page." msgstr "无效页面。" -#: pagination.py:427 +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." +msgstr "" + +#: pagination.py:583 msgid "Invalid cursor" msgstr "无效游标" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." msgstr "无效主键 “{pk_value}” - 对象不存在。" -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "类型错误。期望为主键,获得的类型为 {data_type}。" -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." msgstr "无效超链接 -没有匹配的URL。" -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." msgstr "无效超链接 -错误的URL匹配。" -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." msgstr "无效超链接 -对象不存在。" -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "类型错误。期望为URL字符串,实际的类型是 {data_type}。" -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "属性 {slug_name} 为 {value} 的对象不存在。" -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." msgstr "无效值。" -#: serializers.py:326 +#: schemas/utils.py:32 +msgid "unique integer value" +msgstr "" + +#: schemas/utils.py:34 +msgid "UUID string" +msgstr "" + +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format msgid "Invalid data. Expected a dictionary, but got {datatype}." msgstr "无效数据。期待为字典类型,得到的是 {datatype} 。" #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" msgstr "过滤器" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" -msgstr "过滤器字段" +#: templates/rest_framework/base.html:37 +msgid "navbar" +msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" -msgstr "排序" +#: templates/rest_framework/base.html:75 +msgid "content" +msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" -msgstr " 搜索" +#: templates/rest_framework/base.html:78 +msgid "request form" +msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 +#: templates/rest_framework/base.html:157 +msgid "main content" +msgstr "" + +#: templates/rest_framework/base.html:173 +msgid "request info" +msgstr "" + +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 msgid "None" msgstr "无" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "没有可选项。" -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." msgstr "该字段必须唯一。" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "字段 {field_names} 必须能构成唯一集合。" -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "该字段必须在日期 “{date_field}” 唯一。" -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "该字段必须在月份 “{date_field}” 唯一。" -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "该字段必须在年 “{date_field}” 唯一。" -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "“Accept” HTTP头包含无效版本。" -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "URL路径包含无效版本。" -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "在URL路径中发现无效的版本。无法匹配任何的版本命名空间。" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "主机名包含无效版本。" -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "请求参数里包含无效版本。" - -#: views.py:88 -msgid "Permission denied." -msgstr "没有权限。" diff --git a/rest_framework/locale/zh_Hant/LC_MESSAGES/django.mo b/rest_framework/locale/zh_Hant/LC_MESSAGES/django.mo index 0d8ccaa5557028d040bad7584707df8e7d6559f5..26ea0cee93d7fd2fd2dd8695ed3a50c0c1269b10 100644 GIT binary patch literal 4809 zcmai$YitzP6~}MeJX({sA#KvMY13OLZ6IXVHi48bAqo%EI0=D*p+S`zjd!niC!U>I zW@h~ea%CP4HrUuaY~y5km`7p^#s&&3el)EV+7_Ct4OcR%s5pDOi#?u_^0 z7#QvBZ|>uqbI;@6nSVX@zyQPbbKF0~{gMA7G4wS3w&80(=U*16F{G z9%Src@ClG~JPob}cYq=AsNerN_zc<)KE&8#;8KwEy$n+SPM_`IO0-8n()(9%75Hy( z3HT&Be*&%rp9jO>H^IYTHJAdw1%3|x0Q{%l{x3)_J@j>~5v%}dd@1-GxB+|u)Ihk# z_JQPI7lW{CVUXUjI^%+G|0I*Ka|x_nzM#06E%M!FAvl zAVS0*hf&i16iDl+1iueH2f_qf>$hJAm!lm4AueBz+Z+dUdiGBtKSyq~}*4<+BZ> z{GI?wZwe&237!s@@4>I2?@J#Fb4(=ClQw)%D z85<7%7F`^6ZvY+9mJwf}3E{Y>vROfTo z5NSZnkjZARZ~QY zgL*RF@TIY&9pm*zlhE0EQz*8Guv*2Bi>VvvtiC^gk2!GKoTRQM%Im?%g#o_ zjL+92mx`??;t9Kr1`B5Z9*QSQNq<~vlQC;R6>Ey#R3=n1s3$e8e2fAJsidy%N=k=d zLRNc;ROvb_p`mA+4-c|Id z!ME0KtLKd-)`9wNs@N(LhG_>jSy43-SeJ}ifqEmvE4FM7Y!%HaNd`9H6bkX>)yt~` zHTb>AYkm=0v2s~8u8KEtxCiP@MYlA?Hp~!zTWMBtLZUK_UCB27aw|3wJ-v#P!Dv>M zVAzPi0x>Yo z)(#XL3DhjyUxo6LrmEhH1zuBhyJ~60rn*hF#kkZ2t1H$UIJ0$lf-@|{ZP991B{W6V zSIc*mCG0iZ>o*2g73a}{8ig6C_4i1KuWC^3f?px-TdUZjB~3*OY((Cy5Z4p3X|1VQ z&ArDp)vGH?-qe|NuX|#`O?A5iUD*%z=l7)DV+a1zv6tm89m>t5oz4q|F?Zni?m*h< zn7P|<+8H0BvHZoM+<3SDzILXkvQX90NgA_fr*dPx?)Z84^8Ua+clUS@5V#vOZqMXlYR39 z3U6m$U|yK$<-CH!g7=noQj^Zfj)kY*|2i(D72o_c&GoSV(iVBNwd8wr! zj-u!|m%Y)C*Q?WWJKx!h#UOT6yW{6j7 za3p_Zl2M(Xz2tQ6$&Fq`1l+;f?)8C^uL4p&w-Njg0jJ}Tmqhv)a6akGpXqXXVDD1q z9=5%xWu~Vyvjgtfad)H(X5GI2%+z^*{mkwD1BI$Pu=d}qs~+x^ITwVf6AFW z#E|Dq`kJ@Ho%3UOkW-~DxZo8A>X%Z9V@Mh+)OF#&K-D>eS1><&YZUP*7TSGQih~Bb zh1^bccqfjuK6m_(2YnNmk-a#T9iClyjJ^oUM*JuqpU9-;wuTXJW0b1%C9n$+{x1!b zY{GocL~iUnwg8+(*=5p)pv;RvCOwX`7*?FV!OZlzJ2M~5eFl*BvJZhmHkp&Yo*O>x zrpBCs(S{a&5=I0hy2+7FLO)OS$&M3+(F3C()uqnyUbpYaIsK917Zc#=6NlzE# diff --git a/rest_framework/locale/zh_Hant/LC_MESSAGES/django.po b/rest_framework/locale/zh_Hant/LC_MESSAGES/django.po index 1960f1f5d6..6d2630f311 100644 --- a/rest_framework/locale/zh_Hant/LC_MESSAGES/django.po +++ b/rest_framework/locale/zh_Hant/LC_MESSAGES/django.po @@ -3,13 +3,14 @@ # This file is distributed under the same license as the PACKAGE package. # # Translators: +# Matsui Lin , 2019 msgid "" msgstr "" "Project-Id-Version: Django REST framework\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-12 16:13+0100\n" -"PO-Revision-Date: 2016-07-12 15:14+0000\n" -"Last-Translator: Thomas Christie \n" +"POT-Creation-Date: 2020-10-13 21:45+0200\n" +"PO-Revision-Date: 2020-10-13 19:45+0000\n" +"Last-Translator: Xavier Ordoquy \n" "Language-Team: Chinese Traditional (http://www.transifex.com/django-rest-framework-1/django-rest-framework/language/zh-Hant/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -17,423 +18,556 @@ msgstr "" "Language: zh-Hant\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: authentication.py:73 +#: authentication.py:70 msgid "Invalid basic header. No credentials provided." msgstr "" -#: authentication.py:76 +#: authentication.py:73 msgid "Invalid basic header. Credentials string should not contain spaces." msgstr "" -#: authentication.py:82 +#: authentication.py:83 msgid "Invalid basic header. Credentials not correctly base64 encoded." msgstr "" -#: authentication.py:99 +#: authentication.py:101 msgid "Invalid username/password." -msgstr "" +msgstr "無效的使用者名稱及密碼。" -#: authentication.py:102 authentication.py:198 +#: authentication.py:104 authentication.py:206 msgid "User inactive or deleted." -msgstr "" +msgstr "帳號未啟用或已被刪除。" -#: authentication.py:176 +#: authentication.py:184 msgid "Invalid token header. No credentials provided." msgstr "" -#: authentication.py:179 +#: authentication.py:187 msgid "Invalid token header. Token string should not contain spaces." msgstr "" -#: authentication.py:185 +#: authentication.py:193 msgid "" "Invalid token header. Token string should not contain invalid characters." msgstr "" -#: authentication.py:195 +#: authentication.py:203 msgid "Invalid token." -msgstr "" +msgstr "無效的token。" #: authtoken/apps.py:7 msgid "Auth Token" -msgstr "" +msgstr "驗證 Token" -#: authtoken/models.py:15 +#: authtoken/models.py:13 msgid "Key" -msgstr "" +msgstr "金鑰" -#: authtoken/models.py:18 +#: authtoken/models.py:16 msgid "User" -msgstr "" +msgstr "使用者" -#: authtoken/models.py:20 +#: authtoken/models.py:18 msgid "Created" -msgstr "" +msgstr "建立者" -#: authtoken/models.py:29 +#: authtoken/models.py:27 authtoken/serializers.py:19 msgid "Token" -msgstr "" +msgstr "Token" -#: authtoken/models.py:30 +#: authtoken/models.py:28 msgid "Tokens" -msgstr "" +msgstr "Tokens" -#: authtoken/serializers.py:8 +#: authtoken/serializers.py:9 msgid "Username" -msgstr "" +msgstr "使用者名稱" -#: authtoken/serializers.py:9 +#: authtoken/serializers.py:13 msgid "Password" -msgstr "" +msgstr "密碼" -#: authtoken/serializers.py:20 -msgid "User account is disabled." -msgstr "" - -#: authtoken/serializers.py:23 +#: authtoken/serializers.py:35 msgid "Unable to log in with provided credentials." msgstr "" -#: authtoken/serializers.py:26 +#: authtoken/serializers.py:38 msgid "Must include \"username\" and \"password\"." -msgstr "" +msgstr "必須包含使用者名稱及密碼。" -#: exceptions.py:49 +#: exceptions.py:102 msgid "A server error occurred." msgstr "" -#: exceptions.py:84 +#: exceptions.py:142 +msgid "Invalid input." +msgstr "" + +#: exceptions.py:161 msgid "Malformed request." msgstr "" -#: exceptions.py:89 +#: exceptions.py:167 msgid "Incorrect authentication credentials." msgstr "" -#: exceptions.py:94 +#: exceptions.py:173 msgid "Authentication credentials were not provided." msgstr "" -#: exceptions.py:99 +#: exceptions.py:179 msgid "You do not have permission to perform this action." -msgstr "" +msgstr "你沒有執行此操作的權限。" -#: exceptions.py:104 views.py:81 +#: exceptions.py:185 msgid "Not found." -msgstr "" +msgstr "找不到資源。" -#: exceptions.py:109 +#: exceptions.py:191 +#, python-brace-format msgid "Method \"{method}\" not allowed." -msgstr "" +msgstr "不被允許的方法 \"{method}\"。" -#: exceptions.py:120 +#: exceptions.py:202 msgid "Could not satisfy the request Accept header." msgstr "" -#: exceptions.py:132 +#: exceptions.py:212 +#, python-brace-format msgid "Unsupported media type \"{media_type}\" in request." msgstr "" -#: exceptions.py:145 +#: exceptions.py:223 msgid "Request was throttled." msgstr "" -#: fields.py:269 relations.py:206 relations.py:239 validators.py:98 -#: validators.py:181 -msgid "This field is required." +#: exceptions.py:224 +#, python-brace-format +msgid "Expected available in {wait} second." msgstr "" -#: fields.py:270 +#: exceptions.py:225 +#, python-brace-format +msgid "Expected available in {wait} seconds." +msgstr "" + +#: fields.py:316 relations.py:245 relations.py:279 validators.py:90 +#: validators.py:183 +msgid "This field is required." +msgstr "此為必需欄位。" + +#: fields.py:317 msgid "This field may not be null." +msgstr "此欄位不可為null。" + +#: fields.py:701 +msgid "Must be a valid boolean." msgstr "" -#: fields.py:608 fields.py:639 -msgid "\"{input}\" is not a valid boolean." +#: fields.py:766 +msgid "Not a valid string." msgstr "" -#: fields.py:674 +#: fields.py:767 msgid "This field may not be blank." -msgstr "" +msgstr "此欄位不可為空白。" -#: fields.py:675 fields.py:1675 +#: fields.py:768 fields.py:1881 +#, python-brace-format msgid "Ensure this field has no more than {max_length} characters." -msgstr "" +msgstr "請確認此欄位字元長度不超過 {max_length}。" -#: fields.py:676 +#: fields.py:769 +#, python-brace-format msgid "Ensure this field has at least {min_length} characters." -msgstr "" +msgstr "請確認此欄位字元長度最少超過 {min_length}。" -#: fields.py:713 +#: fields.py:816 msgid "Enter a valid email address." -msgstr "" +msgstr "請輸入有效的電子郵件地址。" -#: fields.py:724 +#: fields.py:827 msgid "This value does not match the required pattern." msgstr "" -#: fields.py:735 +#: fields.py:838 msgid "" "Enter a valid \"slug\" consisting of letters, numbers, underscores or " "hyphens." msgstr "" -#: fields.py:747 -msgid "Enter a valid URL." +#: fields.py:839 +msgid "" +"Enter a valid \"slug\" consisting of Unicode letters, numbers, underscores, " +"or hyphens." msgstr "" -#: fields.py:760 -msgid "\"{value}\" is not a valid UUID." +#: fields.py:854 +msgid "Enter a valid URL." +msgstr "請輸入有效的URL。" + +#: fields.py:867 +msgid "Must be a valid UUID." msgstr "" -#: fields.py:796 +#: fields.py:903 msgid "Enter a valid IPv4 or IPv6 address." -msgstr "" +msgstr "請輸入有效的 IPv4 或 IPv6 地址。" -#: fields.py:821 +#: fields.py:931 msgid "A valid integer is required." -msgstr "" +msgstr "請輸入有效的整數值。" -#: fields.py:822 fields.py:857 fields.py:891 +#: fields.py:932 fields.py:969 fields.py:1005 fields.py:1366 +#, python-brace-format msgid "Ensure this value is less than or equal to {max_value}." -msgstr "" +msgstr "請確認輸入值小於或等於 {max_value}。" -#: fields.py:823 fields.py:858 fields.py:892 +#: fields.py:933 fields.py:970 fields.py:1006 fields.py:1367 +#, python-brace-format msgid "Ensure this value is greater than or equal to {min_value}." -msgstr "" +msgstr "請確認輸入值大於或等於 {min_value}。" -#: fields.py:824 fields.py:859 fields.py:896 +#: fields.py:934 fields.py:971 fields.py:1010 msgid "String value too large." -msgstr "" +msgstr "字串長度太長。" -#: fields.py:856 fields.py:890 +#: fields.py:968 fields.py:1004 msgid "A valid number is required." -msgstr "" +msgstr "請輸入有效的數字。" -#: fields.py:893 +#: fields.py:1007 +#, python-brace-format msgid "Ensure that there are no more than {max_digits} digits in total." msgstr "" -#: fields.py:894 +#: fields.py:1008 +#, python-brace-format msgid "" "Ensure that there are no more than {max_decimal_places} decimal places." msgstr "" -#: fields.py:895 +#: fields.py:1009 +#, python-brace-format msgid "" "Ensure that there are no more than {max_whole_digits} digits before the " "decimal point." msgstr "" -#: fields.py:1025 +#: fields.py:1148 +#, python-brace-format msgid "Datetime has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1026 +#: fields.py:1149 msgid "Expected a datetime but got a date." +msgstr "應該為日期時間格式,並非日期格式。" + +#: fields.py:1150 +#, python-brace-format +msgid "Invalid datetime for the timezone \"{timezone}\"." +msgstr "" + +#: fields.py:1151 +msgid "Datetime value out of range." msgstr "" -#: fields.py:1103 +#: fields.py:1236 +#, python-brace-format msgid "Date has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1104 +#: fields.py:1237 msgid "Expected a date but got a datetime." -msgstr "" +msgstr "應該為日期格式,並非日期時間格式。" -#: fields.py:1170 +#: fields.py:1303 +#, python-brace-format msgid "Time has wrong format. Use one of these formats instead: {format}." -msgstr "" +msgstr "時間格式錯誤,請在下列格式中擇一取代:{format}。" -#: fields.py:1232 +#: fields.py:1365 +#, python-brace-format msgid "Duration has wrong format. Use one of these formats instead: {format}." msgstr "" -#: fields.py:1251 fields.py:1300 +#: fields.py:1399 fields.py:1456 +#, python-brace-format msgid "\"{input}\" is not a valid choice." -msgstr "" +msgstr "\"{input}\" 不是有效的選擇。" -#: fields.py:1254 relations.py:71 relations.py:441 +#: fields.py:1402 +#, python-brace-format msgid "More than {count} items..." -msgstr "" +msgstr "超過 {count} 個項目..." -#: fields.py:1301 fields.py:1448 relations.py:437 serializers.py:524 +#: fields.py:1457 fields.py:1603 relations.py:485 serializers.py:570 +#, python-brace-format msgid "Expected a list of items but got type \"{input_type}\"." -msgstr "" +msgstr "應該為項目組成的列表,並非 \"{input_type}\" 類型。" -#: fields.py:1302 +#: fields.py:1458 msgid "This selection may not be empty." -msgstr "" +msgstr "此選項不可為空。" -#: fields.py:1339 +#: fields.py:1495 +#, python-brace-format msgid "\"{input}\" is not a valid path choice." msgstr "" -#: fields.py:1358 +#: fields.py:1514 msgid "No file was submitted." -msgstr "" +msgstr "沒有任何檔案被提交。" -#: fields.py:1359 +#: fields.py:1515 msgid "" "The submitted data was not a file. Check the encoding type on the form." -msgstr "" +msgstr "提交的資料並不是檔案格式,請確認表單的編碼類型。" -#: fields.py:1360 +#: fields.py:1516 msgid "No filename could be determined." msgstr "" -#: fields.py:1361 +#: fields.py:1517 msgid "The submitted file is empty." -msgstr "" +msgstr "沒有任何檔案被提交。" -#: fields.py:1362 +#: fields.py:1518 +#, python-brace-format msgid "" "Ensure this filename has at most {max_length} characters (it has {length})." msgstr "" -#: fields.py:1410 +#: fields.py:1566 msgid "" "Upload a valid image. The file you uploaded was either not an image or a " "corrupted image." msgstr "" -#: fields.py:1449 relations.py:438 serializers.py:525 +#: fields.py:1604 relations.py:486 serializers.py:571 msgid "This list may not be empty." +msgstr "此列表不可為空。" + +#: fields.py:1605 +#, python-brace-format +msgid "Ensure this field has at least {min_length} elements." msgstr "" -#: fields.py:1502 +#: fields.py:1606 +#, python-brace-format +msgid "Ensure this field has no more than {max_length} elements." +msgstr "" + +#: fields.py:1682 +#, python-brace-format msgid "Expected a dictionary of items but got type \"{input_type}\"." msgstr "" -#: fields.py:1549 +#: fields.py:1683 +msgid "This dictionary may not be empty." +msgstr "" + +#: fields.py:1755 msgid "Value must be valid JSON." +msgstr "輸入值必須為有效的JSON。" + +#: filters.py:49 templates/rest_framework/filters/search.html:2 +msgid "Search" +msgstr "搜尋" + +#: filters.py:50 +msgid "A search term." msgstr "" -#: filters.py:36 templates/rest_framework/filters/django_filter.html:5 -msgid "Submit" +#: filters.py:180 templates/rest_framework/filters/ordering.html:3 +msgid "Ordering" +msgstr "排序" + +#: filters.py:181 +msgid "Which field to use when ordering the results." msgstr "" -#: filters.py:336 +#: filters.py:287 msgid "ascending" -msgstr "" +msgstr "升序排列" -#: filters.py:337 +#: filters.py:288 msgid "descending" +msgstr "降序排列" + +#: pagination.py:174 +msgid "A page number within the paginated result set." +msgstr "" + +#: pagination.py:179 pagination.py:372 pagination.py:590 +msgid "Number of results to return per page." msgstr "" -#: pagination.py:193 +#: pagination.py:189 msgid "Invalid page." +msgstr "無效的頁面。" + +#: pagination.py:374 +msgid "The initial index from which to return the results." +msgstr "" + +#: pagination.py:581 +msgid "The pagination cursor value." msgstr "" -#: pagination.py:427 +#: pagination.py:583 msgid "Invalid cursor" msgstr "" -#: relations.py:207 +#: relations.py:246 +#, python-brace-format msgid "Invalid pk \"{pk_value}\" - object does not exist." -msgstr "" +msgstr "無效的主鍵 \"{pk_value}\" - 物件不存在。" -#: relations.py:208 +#: relations.py:247 +#, python-brace-format msgid "Incorrect type. Expected pk value, received {data_type}." msgstr "" -#: relations.py:240 +#: relations.py:280 msgid "Invalid hyperlink - No URL match." -msgstr "" +msgstr "無效的超連結 - 沒有匹配的URL。" -#: relations.py:241 +#: relations.py:281 msgid "Invalid hyperlink - Incorrect URL match." -msgstr "" +msgstr "無效的超連結 - 匹配的URL不正確。" -#: relations.py:242 +#: relations.py:282 msgid "Invalid hyperlink - Object does not exist." -msgstr "" +msgstr "無效的超連結 - 物件不存在。" -#: relations.py:243 +#: relations.py:283 +#, python-brace-format msgid "Incorrect type. Expected URL string, received {data_type}." msgstr "" -#: relations.py:401 +#: relations.py:448 +#, python-brace-format msgid "Object with {slug_name}={value} does not exist." msgstr "" -#: relations.py:402 +#: relations.py:449 msgid "Invalid value." +msgstr "無效值。" + +#: schemas/utils.py:32 +msgid "unique integer value" msgstr "" -#: serializers.py:326 -msgid "Invalid data. Expected a dictionary, but got {datatype}." +#: schemas/utils.py:34 +msgid "UUID string" msgstr "" +#: schemas/utils.py:36 +msgid "unique value" +msgstr "" + +#: schemas/utils.py:38 +#, python-brace-format +msgid "A {value_type} identifying this {name}." +msgstr "" + +#: serializers.py:337 +#, python-brace-format +msgid "Invalid data. Expected a dictionary, but got {datatype}." +msgstr "無效的資料,應該為字典類型,並非 {datatype}。" + #: templates/rest_framework/admin.html:116 -#: templates/rest_framework/base.html:128 +#: templates/rest_framework/base.html:136 +msgid "Extra Actions" +msgstr "" + +#: templates/rest_framework/admin.html:130 +#: templates/rest_framework/base.html:150 msgid "Filters" +msgstr "篩選" + +#: templates/rest_framework/base.html:37 +msgid "navbar" msgstr "" -#: templates/rest_framework/filters/django_filter.html:2 -#: templates/rest_framework/filters/django_filter_crispyforms.html:4 -msgid "Field filters" +#: templates/rest_framework/base.html:75 +msgid "content" msgstr "" -#: templates/rest_framework/filters/ordering.html:3 -msgid "Ordering" +#: templates/rest_framework/base.html:78 +msgid "request form" msgstr "" -#: templates/rest_framework/filters/search.html:2 -msgid "Search" +#: templates/rest_framework/base.html:157 +msgid "main content" msgstr "" -#: templates/rest_framework/horizontal/radio.html:2 -#: templates/rest_framework/inline/radio.html:2 -#: templates/rest_framework/vertical/radio.html:2 -msgid "None" +#: templates/rest_framework/base.html:173 +msgid "request info" msgstr "" -#: templates/rest_framework/horizontal/select_multiple.html:2 -#: templates/rest_framework/inline/select_multiple.html:2 -#: templates/rest_framework/vertical/select_multiple.html:2 +#: templates/rest_framework/base.html:177 +msgid "response info" +msgstr "" + +#: templates/rest_framework/horizontal/radio.html:4 +#: templates/rest_framework/inline/radio.html:3 +#: templates/rest_framework/vertical/radio.html:3 +msgid "None" +msgstr "無" + +#: templates/rest_framework/horizontal/select_multiple.html:4 +#: templates/rest_framework/inline/select_multiple.html:3 +#: templates/rest_framework/vertical/select_multiple.html:3 msgid "No items to select." msgstr "" -#: validators.py:43 +#: validators.py:39 msgid "This field must be unique." -msgstr "" +msgstr "此欄位必須唯一。" -#: validators.py:97 +#: validators.py:89 +#, python-brace-format msgid "The fields {field_names} must make a unique set." msgstr "" -#: validators.py:245 +#: validators.py:171 +#, python-brace-format +msgid "Surrogate characters are not allowed: U+{code_point:X}." +msgstr "" + +#: validators.py:243 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" date." msgstr "" -#: validators.py:260 +#: validators.py:258 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" month." msgstr "" -#: validators.py:273 +#: validators.py:271 +#, python-brace-format msgid "This field must be unique for the \"{date_field}\" year." msgstr "" -#: versioning.py:42 +#: versioning.py:40 msgid "Invalid version in \"Accept\" header." msgstr "" -#: versioning.py:73 +#: versioning.py:71 msgid "Invalid version in URL path." msgstr "" -#: versioning.py:115 +#: versioning.py:116 msgid "Invalid version in URL path. Does not match any version namespace." msgstr "" -#: versioning.py:147 +#: versioning.py:148 msgid "Invalid version in hostname." msgstr "" -#: versioning.py:169 +#: versioning.py:170 msgid "Invalid version in query parameter." msgstr "" - -#: views.py:88 -msgid "Permission denied." -msgstr "" From 9c29f5013f2741b215d537d12f7a6ddecda5677e Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 1 Nov 2020 07:42:30 -0800 Subject: [PATCH 382/907] Use Python 3.9 release in Travis configuration --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c2724bf63..7a820766e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,8 +21,8 @@ matrix: - { python: "3.8", env: DJANGO=3.1 } - { python: "3.8", env: DJANGO=master } - - { python: "3.9-dev", env: DJANGO=3.1 } - - { python: "3.9-dev", env: DJANGO=master } + - { python: "3.9", env: DJANGO=3.1 } + - { python: "3.9", env: DJANGO=master } - { python: "3.8", env: TOXENV=base } - { python: "3.8", env: TOXENV=lint } From 56e45081235783812f90098916a6e0131b08bbaa Mon Sep 17 00:00:00 2001 From: "James S Blachly, MD" Date: Mon, 2 Nov 2020 03:45:43 -0500 Subject: [PATCH 383/907] Fix #7612 (#7622) --- docs/api-guide/viewsets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 22cc3d8aa9..d5815127b6 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -152,7 +152,7 @@ A more complete example of extra actions: user = self.get_object() serializer = PasswordSerializer(data=request.data) if serializer.is_valid(): - user.set_password(serializer.data['password']) + user.set_password(serializer.validated_data['password']) user.save() return Response({'status': 'password set'}) else: From 606df838856bd3fe2c2a76bead15a8fb7234479e Mon Sep 17 00:00:00 2001 From: Megan Gross <16373770+144mdgross@users.noreply.github.com> Date: Thu, 5 Nov 2020 01:43:45 -0700 Subject: [PATCH 384/907] Update throttling.md (#7606) There were recent updates to the `@action` decorator calling a little more attention to the kwargs it accepts. I thought it would be useful to also provide an example in the throttling section of how those kwargs can be used to define/override throttle_classes through the action decorator as well. --- docs/api-guide/throttling.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 215c735bf4..a3e42cacf9 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -59,7 +59,7 @@ using the `APIView` class-based views. } return Response(content) -Or, if you're using the `@api_view` decorator with function based views. +If you're using the `@api_view` decorator with function based views you can use the following decorator. @api_view(['GET']) @throttle_classes([UserRateThrottle]) @@ -69,6 +69,16 @@ Or, if you're using the `@api_view` decorator with function based views. } return Response(content) +It's also possible to set throttle classes for routes that are created using the `@action` decorator. +Throttle classes set in this way will override any viewset level class settings. + + @action(detail=True, methods=["post"], throttle_classes=[UserRateThrottle]) + def example_adhoc_method(request, pk=None): + content = { + 'status': 'request was permitted' + } + return Response(content) + ## How clients are identified The `X-Forwarded-For` HTTP header and `REMOTE_ADDR` WSGI variable are used to uniquely identify client IP addresses for throttling. If the `X-Forwarded-For` header is present then it will be used, otherwise the value of the `REMOTE_ADDR` variable from the WSGI environment will be used. From 80444a0afb1c05b4c85a34de2bc6c3f9614bdc86 Mon Sep 17 00:00:00 2001 From: bhealy-indeed <62403057+bhealy-indeed@users.noreply.github.com> Date: Thu, 5 Nov 2020 15:21:30 -0600 Subject: [PATCH 385/907] nit: Typo fix (#7629) --- docs/api-guide/exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index d7d73a2f2b..fbf3097e0c 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -38,7 +38,7 @@ Might receive an error response indicating that the `DELETE` method is not allow Validation errors are handled slightly differently, and will include the field names as the keys in the response. If the validation error was not specific to a particular field then it will use the "non_field_errors" key, or whatever string value has been set for the `NON_FIELD_ERRORS_KEY` setting. -Any example validation error might look like this: +An example validation error might look like this: HTTP/1.1 400 Bad Request Content-Type: application/json From 6da94e5700dd505f0fe2d9f376fbc212d02febc2 Mon Sep 17 00:00:00 2001 From: Georg Lukas Date: Thu, 12 Nov 2020 09:48:18 +0100 Subject: [PATCH 386/907] docs: outline the difference between JSON and form parsers. Fix #7633 --- docs/api-guide/parsers.md | 2 +- docs/api-guide/requests.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index e8f03de8bd..dde77c3e0e 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -73,7 +73,7 @@ Or, if you're using the `@api_view` decorator with function based views. ## JSONParser -Parses `JSON` request content. +Parses `JSON` request content. `request.data` will be populated with a dictionary of data. **.media_type**: `application/json` diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md index 1c336953ca..e877c868df 100644 --- a/docs/api-guide/requests.md +++ b/docs/api-guide/requests.md @@ -23,7 +23,7 @@ REST framework's Request objects provide flexible request parsing that allows yo * It includes all parsed content, including *file and non-file* inputs. * It supports parsing the content of HTTP methods other than `POST`, meaning that you can access the content of `PUT` and `PATCH` requests. -* It supports REST framework's flexible request parsing, rather than just supporting form data. For example you can handle incoming JSON data in the same way that you handle incoming form data. +* It supports REST framework's flexible request parsing, rather than just supporting form data. For example you can handle incoming [JSON data] similarly to how you handle incoming [form data]. For more details see the [parsers documentation]. @@ -136,5 +136,7 @@ Note that due to implementation reasons the `Request` class does not inherit fro [cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion [parsers documentation]: parsers.md +[JSON data]: parsers.md#jsonparser +[form data]: parsers.md#formparser [authentication documentation]: authentication.md [browser enhancements documentation]: ../topics/browser-enhancements.md From 96993d817a6af9c037ece7253cfae49efc814f49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karol=20Ony=C5=9Bko?= Date: Thu, 12 Nov 2020 18:42:42 +0100 Subject: [PATCH 387/907] Changed url to django docs so it points to the stable version (#7628) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Karol Onyśko --- docs/api-guide/relations.md | 2 +- docs/topics/internationalization.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index d3d8b30b8c..f444125cff 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -603,6 +603,6 @@ The [rest-framework-generic-relations][drf-nested-relations] library provides re [generic-relations]: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1 [drf-nested-routers]: https://github.com/alanjds/drf-nested-routers [drf-nested-relations]: https://github.com/Ian-Foote/rest-framework-generic-relations -[django-intermediary-manytomany]: https://docs.djangoproject.com/en/2.2/topics/db/models/#intermediary-manytomany +[django-intermediary-manytomany]: https://docs.djangoproject.com/en/stable/topics/db/models/#intermediary-manytomany [dealing-with-nested-objects]: https://www.django-rest-framework.org/api-guide/serializers/#dealing-with-nested-objects [to_internal_value]: https://www.django-rest-framework.org/api-guide/serializers/#to_internal_valueself-data diff --git a/docs/topics/internationalization.md b/docs/topics/internationalization.md index 7cfc6e247c..c20cf9e339 100644 --- a/docs/topics/internationalization.md +++ b/docs/topics/internationalization.md @@ -103,10 +103,10 @@ You can find more information on how the language preference is determined in th For API clients the most appropriate of these will typically be to use the `Accept-Language` header; Sessions and cookies will not be available unless using session authentication, and generally better practice to prefer an `Accept-Language` header for API clients rather than using language URL prefixes. [cite]: https://youtu.be/Wa0VfS2q94Y -[django-translation]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation +[django-translation]: https://docs.djangoproject.com/en/stable/topics/i18n/translation [custom-exception-handler]: ../api-guide/exceptions.md#custom-exception-handling [transifex-project]: https://www.transifex.com/projects/p/django-rest-framework/ [django-po-source]: https://raw.githubusercontent.com/encode/django-rest-framework/master/rest_framework/locale/en_US/LC_MESSAGES/django.po -[django-language-preference]: https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#how-django-discovers-language-preference -[django-locale-paths]: https://docs.djangoproject.com/en/1.7/ref/settings/#std:setting-LOCALE_PATHS -[django-locale-name]: https://docs.djangoproject.com/en/1.7/topics/i18n/#term-locale-name +[django-language-preference]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#how-django-discovers-language-preference +[django-locale-paths]: https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-LOCALE_PATHS +[django-locale-name]: https://docs.djangoproject.com/en/stable/topics/i18n/#term-locale-name From 3ab8d4706eb6c04b331ca4d57778a1989c668f2f Mon Sep 17 00:00:00 2001 From: babaroga Date: Sat, 21 Nov 2020 11:53:39 -0500 Subject: [PATCH 388/907] changed unicode to str --- docs/api-guide/authentication.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 5878040a48..da932a06c8 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -60,8 +60,8 @@ using the `APIView` class-based views. def get(self, request, format=None): content = { - 'user': unicode(request.user), # `django.contrib.auth.User` instance. - 'auth': unicode(request.auth), # None + 'user': str(request.user), # `django.contrib.auth.User` instance. + 'auth': str(request.auth), # None } return Response(content) @@ -72,8 +72,8 @@ Or, if you're using the `@api_view` decorator with function based views. @permission_classes([IsAuthenticated]) def example_view(request, format=None): content = { - 'user': unicode(request.user), # `django.contrib.auth.User` instance. - 'auth': unicode(request.auth), # None + 'user': str(request.user), # `django.contrib.auth.User` instance. + 'auth': str(request.auth), # None } return Response(content) From bb133522efaf6ce3ae8fdf1dec6cd79566cfd166 Mon Sep 17 00:00:00 2001 From: Erwin Junge Date: Sun, 22 Nov 2020 09:33:17 +0100 Subject: [PATCH 389/907] Small documentation fix --- docs/api-guide/serializers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index fd5dbb0e67..f05fe7e7e9 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -282,7 +282,7 @@ If a nested representation may optionally accept the `None` value you should pas content = serializers.CharField(max_length=200) created = serializers.DateTimeField() -Similarly if a nested representation should be a list of items, you should pass the `many=True` flag to the nested serialized. +Similarly if a nested representation should be a list of items, you should pass the `many=True` flag to the nested serializer. class CommentSerializer(serializers.Serializer): user = UserSerializer(required=False) From 8351747d98b97907e6bb096914bf287a22c5314b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 21 Dec 2020 16:41:12 +0000 Subject: [PATCH 390/907] Update index.md --- docs/index.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/index.md b/docs/index.md index 0273da9f14..0e6bb48f2e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -190,11 +190,6 @@ For support please see the [REST framework discussion group][group], try the `# For priority support please sign up for a [professional or premium sponsorship plan](https://fund.django-rest-framework.org/topics/funding/). -For updates on REST framework development, you may also want to follow [the author][twitter] on Twitter. - - - - ## Security If you believe you’ve found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**. From 3db88778893579e1d7609b584ef35409c8aa5a22 Mon Sep 17 00:00:00 2001 From: Adrian Coveney Date: Wed, 25 Jul 2018 10:53:43 +0100 Subject: [PATCH 391/907] Clarify documentation for TemplateHTMLRenderer Clarify that the response from a view may need to be modified to provide TemplateHTMLRenderer with a dict for it to use. --- docs/api-guide/renderers.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index ca3a29b82c..954fb3bb98 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -103,6 +103,16 @@ Unlike other renderers, the data passed to the `Response` does not need to be se The TemplateHTMLRenderer will create a `RequestContext`, using the `response.data` as the context dict, and determine a template name to use to render the context. +--- + +**Note:** When used with a view that makes use of a serializer the `Response` sent for rendering may not be a dictionay and will need to be wrapped in a dict before returning to allow the TemplateHTMLRenderer to render it. For example: + +``` +response.data = {'results': response.data} +``` + +--- + The template name is determined by (in order of preference): 1. An explicit `template_name` argument passed to the response. From 19655edbf782aa1fbdd7f8cd56ff9e0b7786ad3c Mon Sep 17 00:00:00 2001 From: Sebastian Jordan Date: Wed, 6 Jan 2021 14:13:34 +0100 Subject: [PATCH 392/907] Handle tuples same as lists in ValidationError detail context (#7647) --- rest_framework/exceptions.py | 6 ++++-- tests/test_validation_error.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 943dcc88c3..fee8f024f2 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -20,7 +20,7 @@ def _get_error_details(data, default_code=None): Descend into a nested data structure, forcing any lazy translation strings or strings into `ErrorDetail`. """ - if isinstance(data, list): + if isinstance(data, (list, tuple)): ret = [ _get_error_details(item, default_code) for item in data ] @@ -150,7 +150,9 @@ def __init__(self, detail=None, code=None): # For validation failures, we may collect many errors together, # so the details should always be coerced to a list if not already. - if not isinstance(detail, dict) and not isinstance(detail, list): + if isinstance(detail, tuple): + detail = list(detail) + elif not isinstance(detail, dict) and not isinstance(detail, list): detail = [detail] self.detail = _get_error_details(detail, code) diff --git a/tests/test_validation_error.py b/tests/test_validation_error.py index 562fe37e6b..341c4342a5 100644 --- a/tests/test_validation_error.py +++ b/tests/test_validation_error.py @@ -2,6 +2,7 @@ from rest_framework import serializers, status from rest_framework.decorators import api_view +from rest_framework.exceptions import ValidationError from rest_framework.response import Response from rest_framework.settings import api_settings from rest_framework.test import APIRequestFactory @@ -99,3 +100,12 @@ def test_function_based_view_exception_handler(self): response = view(request) assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.data == self.expected_response_data + + +class TestValidationErrorConvertsTuplesToLists(TestCase): + def test_validation_error_details(self): + error = ValidationError(detail=('message1', 'message2')) + assert isinstance(error.detail, list) + assert len(error.detail) == 2 + assert str(error.detail[0]) == 'message1' + assert str(error.detail[1]) == 'message2' From 3e956df6eb7e3b645d334fec372ad7f8a487d765 Mon Sep 17 00:00:00 2001 From: Tonye Jack Date: Tue, 2 Feb 2021 20:54:21 -0500 Subject: [PATCH 393/907] Fixed test --- requirements/requirements-testing.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index ad246e8570..99463560e2 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -2,3 +2,4 @@ pytest>=5.4.1,<5.5 pytest-django>=3.9.0,<3.10 pytest-cov>=2.7.1 +six>=1.14.0 From 1ec0f86b585cd87e4b413aeaad1ecc947bacfef2 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Tue, 16 Feb 2021 18:17:29 +0600 Subject: [PATCH 394/907] Dj32 (#7713) adds django 3.2 line to the build matrix --- .gitignore | 2 ++ .travis.yml | 7 +++++-- tox.ini | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 41768084c5..82e885edee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ *.db *~ .* +*.py.bak + /site/ /htmlcov/ diff --git a/.travis.yml b/.travis.yml index 7a820766e5..f9f22336fc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,18 +10,20 @@ matrix: - { python: "3.6", env: DJANGO=2.2 } - { python: "3.6", env: DJANGO=3.0 } - { python: "3.6", env: DJANGO=3.1 } - - { python: "3.6", env: DJANGO=master } + - { python: "3.6", env: DJANGO=3.2 } - { python: "3.7", env: DJANGO=2.2 } - { python: "3.7", env: DJANGO=3.0 } - { python: "3.7", env: DJANGO=3.1 } - - { python: "3.7", env: DJANGO=master } + - { python: "3.7", env: DJANGO=3.2 } - { python: "3.8", env: DJANGO=3.0 } - { python: "3.8", env: DJANGO=3.1 } + - { python: "3.8", env: DJANGO=3.2 } - { python: "3.8", env: DJANGO=master } - { python: "3.9", env: DJANGO=3.1 } + - { python: "3.9", env: DJANGO=3.2 } - { python: "3.9", env: DJANGO=master } - { python: "3.8", env: TOXENV=base } @@ -38,6 +40,7 @@ matrix: allow_failures: - env: DJANGO=master + - env: DJANGO=3.2 install: - pip install tox tox-travis diff --git a/tox.ini b/tox.ini index df6387d5e1..544bab163c 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,8 @@ envlist = {py35,py36,py37}-django22, {py36,py37,py38}-django30, {py36,py37,py38,py39}-django31, - {py36,py37,py38,py39}-djangomaster, + {py36,py37,py38,py39}-django32, + {py38,py39}-djangomaster, base,dist,lint,docs, [travis:env] @@ -11,6 +12,7 @@ DJANGO = 2.2: django22 3.0: django30 3.1: django31 + 3.2: django32 master: djangomaster [testenv] @@ -23,6 +25,7 @@ deps = django22: Django>=2.2,<3.0 django30: Django>=3.0,<3.1 django31: Django>=3.1,<3.2 + django32: Django>=3.2a1,<4.0 djangomaster: https://github.com/django/django/archive/master.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 8f6d2d2f9c7d9bb91a2b894533c0233620fa360f Mon Sep 17 00:00:00 2001 From: Usoof Mansoor Date: Tue, 2 Mar 2021 11:26:31 +0400 Subject: [PATCH 395/907] Update docs link to Django OAuth Toolkit. (#7737) --- docs/api-guide/authentication.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index da932a06c8..61687e6421 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -357,7 +357,7 @@ The following third party packages are also available. ## Django OAuth Toolkit -The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by [Evonove][evonove] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**. +The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by [jazzband][jazzband] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**. #### Installation & configuration @@ -448,7 +448,7 @@ There are currently two forks of this project. [djangorestframework-digestauth]: https://github.com/juanriaza/django-rest-framework-digestauth [oauth-1.0a]: https://oauth.net/core/1.0a/ [django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit -[evonove]: https://github.com/evonove/ +[jazzband]: https://github.com/jazzband/ [oauthlib]: https://github.com/idan/oauthlib [djangorestframework-simplejwt]: https://github.com/davesque/django-rest-framework-simplejwt [etoccalino]: https://github.com/etoccalino/ From de7468d0b4c48007aed734fee22db0b79b22e70b Mon Sep 17 00:00:00 2001 From: Jonathan Mortensen <56177725+jmo-qap@users.noreply.github.com> Date: Wed, 3 Mar 2021 03:15:39 -0800 Subject: [PATCH 396/907] support multi db atomic_requests (#7739) --- rest_framework/views.py | 8 ++++---- tests/conftest.py | 4 ++++ tests/test_atomic_requests.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/rest_framework/views.py b/rest_framework/views.py index d1b5e4ed90..5b06220691 100644 --- a/rest_framework/views.py +++ b/rest_framework/views.py @@ -3,7 +3,7 @@ """ from django.conf import settings from django.core.exceptions import PermissionDenied -from django.db import connection, models, transaction +from django.db import connections, models from django.http import Http404 from django.http.response import HttpResponseBase from django.utils.cache import cc_delim_re, patch_vary_headers @@ -63,9 +63,9 @@ def get_view_description(view, html=False): def set_rollback(): - atomic_requests = connection.settings_dict.get('ATOMIC_REQUESTS', False) - if atomic_requests and connection.in_atomic_block: - transaction.set_rollback(True) + for db in connections.all(): + if db.settings_dict['ATOMIC_REQUESTS'] and db.in_atomic_block: + db.set_rollback(True) def exception_handler(exc, context): diff --git a/tests/conftest.py b/tests/conftest.py index ac29e4a429..cc32cc6373 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,6 +24,10 @@ def pytest_configure(config): 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:' + }, + 'secondary': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:' } }, SITE_ID=1, diff --git a/tests/test_atomic_requests.py b/tests/test_atomic_requests.py index 15b41e02f4..beda5cba19 100644 --- a/tests/test_atomic_requests.py +++ b/tests/test_atomic_requests.py @@ -130,6 +130,41 @@ def test_api_exception_rollback_transaction(self): assert BasicModel.objects.count() == 0 +@unittest.skipUnless( + connection.features.uses_savepoints, + "'atomic' requires transactions and savepoints." +) +class MultiDBTransactionAPIExceptionTests(TestCase): + databases = '__all__' + + def setUp(self): + self.view = APIExceptionView.as_view() + connections.databases['default']['ATOMIC_REQUESTS'] = True + connections.databases['secondary']['ATOMIC_REQUESTS'] = True + + def tearDown(self): + connections.databases['default']['ATOMIC_REQUESTS'] = False + connections.databases['secondary']['ATOMIC_REQUESTS'] = False + + def test_api_exception_rollback_transaction(self): + """ + Transaction is rollbacked by our transaction atomic block. + """ + request = factory.post('/') + num_queries = 4 if connection.features.can_release_savepoints else 3 + with self.assertNumQueries(num_queries): + # 1 - begin savepoint + # 2 - insert + # 3 - rollback savepoint + # 4 - release savepoint + with transaction.atomic(), transaction.atomic(using='secondary'): + response = self.view(request) + assert transaction.get_rollback() + assert transaction.get_rollback(using='secondary') + assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + assert BasicModel.objects.count() == 0 + + @unittest.skipUnless( connection.features.uses_savepoints, "'atomic' requires transactions and savepoints." From 1deb8ae370df6c65b6ec3fadf71d9391236be06f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:06:42 +0000 Subject: [PATCH 397/907] Update FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index d7c23d6351..5a830ca53f 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +github: encode custom: https://fund.django-rest-framework.org/topics/funding/ From 344235ab371dcd80e2ff2546bf673d586cde4310 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:32:19 +0000 Subject: [PATCH 398/907] Create config.yml --- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..8bb8d8c210 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +contact_links: +- name: Discussions + url: https://github.com/encode/django-rest-framework/discussions + about: > + The "Discussions" forum is where you want to be headed too. Please only raise an issue if you've been advised to do so after discussion. Thank you! 🙏 From c9a00bdb2c0838f17e9a64f6ccab69d14d8ea6e7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:33:14 +0000 Subject: [PATCH 399/907] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 8bb8d8c210..fd0db4d66a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,4 +2,6 @@ contact_links: - name: Discussions url: https://github.com/encode/django-rest-framework/discussions about: > - The "Discussions" forum is where you want to be headed too. Please only raise an issue if you've been advised to do so after discussion. Thank you! 🙏 + The "Discussions" forum is where you want to be headed too. + Please only raise an issue if you've been advised to do so after discussion. + Thank you! 🙏 From db0bb5ef42879a69e7262aebf0f42ce173248a61 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:39:22 +0000 Subject: [PATCH 400/907] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index fd0db4d66a..bf0c054a63 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,6 +2,4 @@ contact_links: - name: Discussions url: https://github.com/encode/django-rest-framework/discussions about: > - The "Discussions" forum is where you want to be headed too. - Please only raise an issue if you've been advised to do so after discussion. - Thank you! 🙏 + The "Discussions" forum is where you want to start. 💖 From 37b8d2018d4dbe2efc012f23baff1cca4df15675 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:42:38 +0000 Subject: [PATCH 401/907] Create 1-issue.md --- .github/ISSUE_TEMPLATE/1-issue.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/1-issue.md diff --git a/.github/ISSUE_TEMPLATE/1-issue.md b/.github/ISSUE_TEMPLATE/1-issue.md new file mode 100644 index 0000000000..0da1549534 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1-issue.md @@ -0,0 +1,10 @@ +--- +name: Issue +about: Please only raise an issue if you've been advised to do so after discussion. Thanks! 🙏 +--- + +## Checklist + +- [ ] Raised initially as discussion #... +- [ ] 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. From ee51145574c9d868baab87a65ac18878dee4ee12 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:43:57 +0000 Subject: [PATCH 402/907] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index bf0c054a63..382fc521aa 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,3 +1,4 @@ +blank_issues_enabled: false contact_links: - name: Discussions url: https://github.com/encode/django-rest-framework/discussions From ec29ff8a8013dd3383344bb78eea479d025e2a87 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 3 Mar 2021 15:45:40 +0000 Subject: [PATCH 403/907] Delete ISSUE_TEMPLATE.md --- ISSUE_TEMPLATE.md | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 ISSUE_TEMPLATE.md diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 566bf95436..0000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,14 +0,0 @@ -## Checklist - -- [ ] 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/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.) - -## Steps to reproduce - -## Expected behavior - -## Actual behavior From ef112f5017bb6d3d6a331ff485dbf6a9209fb8b4 Mon Sep 17 00:00:00 2001 From: arcanemachine Date: Mon, 8 Mar 2021 04:46:35 -0700 Subject: [PATCH 404/907] Provide example for dict in ValidationError detail (#7788) Added a sentence describing the use of a dictionary as the `detail` argument of a ValidationError, and how it can be used to add field-level errors during object-level validation. --- docs/api-guide/exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index fbf3097e0c..e62a7e4f9d 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -222,7 +222,7 @@ By default this exception results in a response with the HTTP status code "429 T The `ValidationError` exception is slightly different from the other `APIException` classes: * The `detail` argument is mandatory, not optional. -* The `detail` argument may be a list or dictionary of error details, and may also be a nested data structure. +* The `detail` argument may be a list or dictionary of error details, and may also be a nested data structure. By using a dictionary, you can specify field-level errors while performing object-level validation in the `validate()` method of a serializer. For example. `raise serializers.ValidationError({'name': 'Please enter a valid name.'})` * By convention you should import the serializers module and use a fully qualified `ValidationError` style, in order to differentiate it from Django's built-in validation error. For example. `raise serializers.ValidationError('This field must be an integer value.')` The `ValidationError` class should be used for serializer and field validation, and by validator classes. It is also raised when calling `serializer.is_valid` with the `raise_exception` keyword argument: From 234527959d5ad6eef2bc0f8af1aa2e149fc8bc60 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Merx Date: Mon, 8 Mar 2021 13:08:26 +0100 Subject: [PATCH 405/907] Have options example in documenting-your-api.md to return a Response (#7639) It was returning data which is not correct. Closes #7638. Co-authored-by: Jean-Pierre Merx --- docs/topics/documenting-your-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md index cd7e5098fe..5eabeee7bb 100644 --- a/docs/topics/documenting-your-api.md +++ b/docs/topics/documenting-your-api.md @@ -202,7 +202,7 @@ You can modify the response behavior to `OPTIONS` requests by overriding the `op meta = self.metadata_class() data = meta.determine_metadata(request, self) data.pop('description') - return data + return Response(data=data, status=status.HTTP_200_OK) See [the Metadata docs][metadata-docs] for more details. From e32ebc41998ffd7f22f6e691badb86a709c89ba7 Mon Sep 17 00:00:00 2001 From: Alex Cotsarelis <57880995+alex-cots@users.noreply.github.com> Date: Mon, 8 Mar 2021 07:09:17 -0500 Subject: [PATCH 406/907] Docs: DjangoModelPermissions works on views with get_queryset() method. (#7693) Sentinel querysets not needed after v3.1.2 --- docs/api-guide/permissions.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index ade1462572..f694d6be50 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -169,7 +169,7 @@ This permission is suitable if you want to your API to allow read permissions to ## DjangoModelPermissions -This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. This permission must only be applied to views that have a `.queryset` property set. Authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned. +This permission class ties into Django's standard `django.contrib.auth` [model permissions][contribauth]. This permission must only be applied to views that have a `.queryset` property or `get_queryset()` method. Authorization will only be granted if the user *is authenticated* and has the *relevant model permissions* assigned. * `POST` requests require the user to have the `add` permission on the model. * `PUT` and `PATCH` requests require the user to have the `change` permission on the model. @@ -179,12 +179,6 @@ The default behaviour can also be overridden to support custom model permissions To use custom model permissions, override `DjangoModelPermissions` and set the `.perms_map` property. Refer to the source code for details. -#### Using with views that do not include a `queryset` attribute. - -If you're using this permission with a view that uses an overridden `get_queryset()` method there may not be a `queryset` attribute on the view. In this case we suggest also marking the view with a sentinel queryset, so that this class can determine the required permissions. For example: - - queryset = User.objects.none() # Required for DjangoModelPermissions - ## DjangoModelPermissionsOrAnonReadOnly Similar to `DjangoModelPermissions`, but also allows unauthenticated users to have read-only access to the API. From b463878132004d33182b2f61be8209bfad79af7f Mon Sep 17 00:00:00 2001 From: Igor Polyakov Date: Tue, 9 Mar 2021 17:16:19 +0700 Subject: [PATCH 407/907] Commas added in README (#7730) To make it more comfortable for users to copy and paste snippets --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8af1466f8a..305f923898 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ router.register(r'users', UserViewSet) # Additionally, we include login URLs for the browsable API. urlpatterns = [ path('', include(router.urls)), - path('api-auth/', include('rest_framework.urls', namespace='rest_framework')) + path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), ] ``` @@ -131,7 +131,7 @@ REST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'DEFAULT_PERMISSION_CLASSES': [ - 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' + 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly', ] } ``` From e9a54e38e1c864919c79a8b88d83b8d61d477053 Mon Sep 17 00:00:00 2001 From: kuter Date: Tue, 9 Mar 2021 11:17:30 +0100 Subject: [PATCH 408/907] add support for Yes/No literals with BooleanField (#7701) --- rest_framework/fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index fdfba13f26..b6c9ee5c52 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -704,7 +704,7 @@ class BooleanField(Field): initial = False TRUE_VALUES = { 't', 'T', - 'y', 'Y', 'yes', 'YES', + 'y', 'Y', 'yes', 'Yes', 'YES', 'true', 'True', 'TRUE', 'on', 'On', 'ON', '1', 1, @@ -712,7 +712,7 @@ class BooleanField(Field): } FALSE_VALUES = { 'f', 'F', - 'n', 'N', 'no', 'NO', + 'n', 'N', 'no', 'No', 'NO', 'false', 'False', 'FALSE', 'off', 'Off', 'OFF', '0', 0, 0.0, From 393f8679952b3e81b56db3e3c498aeb1f8849f52 Mon Sep 17 00:00:00 2001 From: Cas Ebbers <617080+CasEbbers@users.noreply.github.com> Date: Tue, 9 Mar 2021 11:21:11 +0100 Subject: [PATCH 409/907] Overlooked translation in search.html (#7551) --- rest_framework/templates/rest_framework/filters/search.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/templates/rest_framework/filters/search.html b/rest_framework/templates/rest_framework/filters/search.html index edb28d45d8..065c3889ac 100644 --- a/rest_framework/templates/rest_framework/filters/search.html +++ b/rest_framework/templates/rest_framework/filters/search.html @@ -5,7 +5,7 @@

    {% trans "Search" %}

    - +
    From a89a6427d3af7045c8c35693cc830c8b76b8a00d Mon Sep 17 00:00:00 2001 From: Nathan Glover <15344788+nathanglover@users.noreply.github.com> Date: Tue, 9 Mar 2021 05:22:37 -0500 Subject: [PATCH 410/907] #7703 adding deprecations to release notes (#7716) * #7703 adding deprecations to release notes * #7703 - update link for gh6687 --- docs/community/release-notes.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index c981b9ac92..49fb655b01 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -177,6 +177,8 @@ Date: 28th September 2020 * Don't strict disallow redundant `SerializerMethodField` field name arguments. * Don't render extra actions in browable API if not authenticated. * Strip null characters from search parameters. +* Deprecate the `detail_route` decorator in favor of `action`, which accepts a `detail` bool. Use `@action(detail=True)` instead. [gh6687] +* Deprecate the `list_route` decorator in favor of `action`, which accepts a `detail` bool. Use `@action(detail=False)` instead. [gh6687] ## 3.9.x series @@ -2270,6 +2272,7 @@ For older release notes, [please see the version 2.x documentation][old-release- [gh6680]: https://github.com/encode/django-rest-framework/issues/6680 [gh6317]: https://github.com/encode/django-rest-framework/issues/6317 +[gh6687]: https://github.com/encode/django-rest-framework/issues/6687 [gh6892]: https://github.com/encode/django-rest-framework/issues/6892 From 05512160abb4c2110afff9e82f8f523be68476cf Mon Sep 17 00:00:00 2001 From: David Kerkeslager Date: Tue, 9 Mar 2021 05:49:03 -0500 Subject: [PATCH 411/907] Respect allow_null=True on DecimalFields (#7718) * Handle None in to_representation() * Return None as '' in to_representation() when coerce_to_string=True * Handle '' as None in to_internal_value(), for symmetry with to_representation(), and because the empty concept doesn't make sense for Decimal. --- rest_framework/fields.py | 9 +++++++++ tests/test_fields.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index b6c9ee5c52..d91299484e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1063,6 +1063,9 @@ def to_internal_value(self, data): try: value = decimal.Decimal(data) except decimal.DecimalException: + if data == '' and self.allow_null: + return None + self.fail('invalid') if value.is_nan(): @@ -1112,6 +1115,12 @@ def validate_precision(self, value): def to_representation(self, value): coerce_to_string = getattr(self, 'coerce_to_string', api_settings.COERCE_DECIMAL_TO_STRING) + if value is None: + if coerce_to_string: + return '' + else: + return None + if not isinstance(value, decimal.Decimal): value = decimal.Decimal(str(value).strip()) diff --git a/tests/test_fields.py b/tests/test_fields.py index fdd570d8a6..5842553f02 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1090,6 +1090,9 @@ class TestDecimalField(FieldValues): '2E+1': Decimal('20'), } invalid_inputs = ( + (None, ["This field may not be null."]), + ('', ["A valid number is required."]), + (' ', ["A valid number is required."]), ('abc', ["A valid number is required."]), (Decimal('Nan'), ["A valid number is required."]), (Decimal('Snan'), ["A valid number is required."]), @@ -1115,6 +1118,32 @@ class TestDecimalField(FieldValues): field = serializers.DecimalField(max_digits=3, decimal_places=1) +class TestAllowNullDecimalField(FieldValues): + valid_inputs = { + None: None, + '': None, + ' ': None, + } + invalid_inputs = {} + outputs = { + None: '', + } + field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True) + + +class TestAllowNullNoStringCoercionDecimalField(FieldValues): + valid_inputs = { + None: None, + '': None, + ' ': None, + } + invalid_inputs = {} + outputs = { + None: None, + } + field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True, coerce_to_string=False) + + class TestMinMaxDecimalField(FieldValues): """ Valid and invalid values for `DecimalField` with min and max limits. From 95ae92ef23859b45d03bcc2facf04fab0acee09d Mon Sep 17 00:00:00 2001 From: Berkant Date: Tue, 9 Mar 2021 14:34:18 +0300 Subject: [PATCH 412/907] Fix #7706 (#7724) Handle non-dict values for NestedSerializer during BrowsableAPI rendering. --- rest_framework/utils/serializer_helpers.py | 4 ++-- tests/test_bound_fields.py | 27 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index b18fbe0df9..cd0373adcb 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from collections.abc import MutableMapping +from collections.abc import Mapping, MutableMapping from django.utils.encoding import force_str @@ -101,7 +101,7 @@ class NestedBoundField(BoundField): """ def __init__(self, field, value, errors, prefix=''): - if value is None or value == '': + if value is None or value == '' or not isinstance(value, Mapping): value = {} super().__init__(field, value, errors, prefix) diff --git a/tests/test_bound_fields.py b/tests/test_bound_fields.py index dc5ab542ff..dec8793c33 100644 --- a/tests/test_bound_fields.py +++ b/tests/test_bound_fields.py @@ -163,6 +163,33 @@ class ExampleSerializer(serializers.Serializer): rendered_packed = ''.join(rendered.split()) assert rendered_packed == expected_packed + def test_rendering_nested_fields_with_not_mappable_value(self): + from rest_framework.renderers import HTMLFormRenderer + + class Nested(serializers.Serializer): + text_field = serializers.CharField() + + class ExampleSerializer(serializers.Serializer): + nested = Nested() + + serializer = ExampleSerializer(data={'nested': 1}) + assert not serializer.is_valid() + renderer = HTMLFormRenderer() + for field in serializer: + rendered = renderer.render_field(field, {}) + expected_packed = ( + '
    ' + 'Nested' + '' + '' + '' + '' + '
    ' + ) + + rendered_packed = ''.join(rendered.split()) + assert rendered_packed == expected_packed + class TestJSONBoundField: def test_as_form_fields(self): From 82b8a64a02ccc4ff678ac9f9565f25463ecad871 Mon Sep 17 00:00:00 2001 From: Chris Guo <41265033+chrisguox@users.noreply.github.com> Date: Tue, 9 Mar 2021 19:49:19 +0800 Subject: [PATCH 413/907] docs: add example for caching (#7118) --- docs/api-guide/caching.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/api-guide/caching.md b/docs/api-guide/caching.md index 96517b15ee..ab4f82cd2f 100644 --- a/docs/api-guide/caching.md +++ b/docs/api-guide/caching.md @@ -13,13 +13,13 @@ provided in Django. Django provides a [`method_decorator`][decorator] to use decorators with class based views. This can be used with -other cache decorators such as [`cache_page`][page] and -[`vary_on_cookie`][cookie]. +other cache decorators such as [`cache_page`][page], +[`vary_on_cookie`][cookie] and [`vary_on_headers`][headers]. ```python from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page -from django.views.decorators.vary import vary_on_cookie +from django.views.decorators.vary import vary_on_cookie, vary_on_headers from rest_framework.response import Response from rest_framework.views import APIView @@ -27,8 +27,7 @@ from rest_framework import viewsets class UserViewSet(viewsets.ViewSet): - - # Cache requested url for each user for 2 hours + # With cookie: cache requested url for each user for 2 hours @method_decorator(cache_page(60*60*2)) @method_decorator(vary_on_cookie) def list(self, request, format=None): @@ -38,8 +37,18 @@ class UserViewSet(viewsets.ViewSet): return Response(content) -class PostView(APIView): +class ProfileView(APIView): + # With auth: cache requested url for each user for 2 hours + @method_decorator(cache_page(60*60*2)) + @method_decorator(vary_on_headers("Authorization",)) + def get(self, request, format=None): + content = { + 'user_feed': request.user.get_user_feed() + } + return Response(content) + +class PostView(APIView): # Cache page for the requested url @method_decorator(cache_page(60*60*2)) def get(self, request, format=None): @@ -55,4 +64,5 @@ class PostView(APIView): [page]: https://docs.djangoproject.com/en/dev/topics/cache/#the-per-view-cache [cookie]: https://docs.djangoproject.com/en/dev/topics/http/decorators/#django.views.decorators.vary.vary_on_cookie +[headers]: https://docs.djangoproject.com/en/dev/topics/http/decorators/#django.views.decorators.vary.vary_on_headers [decorator]: https://docs.djangoproject.com/en/dev/topics/class-based-views/intro/#decorating-the-class From 747fef6134539c8ce9042b38639459c59e0440a5 Mon Sep 17 00:00:00 2001 From: Celia Oakley Date: Tue, 9 Mar 2021 22:51:52 +1100 Subject: [PATCH 414/907] Add django-rest-authemail to Third Party Packages (#7679) * Add django-rest-authemail to Authentication * Add django-rest-authemail to Third Party Packages --- docs/api-guide/authentication.md | 5 +++++ docs/community/third-party-packages.md | 2 ++ 2 files changed, 7 insertions(+) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 61687e6421..d13c5a2f0d 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -432,6 +432,10 @@ There are currently two forks of this project. [drfpasswordless][drfpasswordless] adds (Medium, Square Cash inspired) passwordless support to Django REST Framework's own TokenAuthentication scheme. Users log in and sign up with a token sent to a contact point like an email address or a mobile number. +## django-rest-authemail + +[django-rest-authemail][django-rest-authemail] provides a RESTful API interface for user signup and authentication. Email addresses are used for authentication, rather than usernames. API endpoints are available for signup, signup email verification, login, logout, password reset, password reset verification, email change, email change verification, password change, and user detail. A fully-functional example project and detailed instructions are included. + [cite]: https://jacobian.org/writing/rest-worst-practices/ [http401]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 [http403]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4 @@ -466,3 +470,4 @@ There are currently two forks of this project. [django-rest-framework-social-oauth2]: https://github.com/PhilipGarnero/django-rest-framework-social-oauth2 [django-rest-knox]: https://github.com/James1345/django-rest-knox [drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless +[django-rest-authemail]: https://github.com/celiao/django-rest-authemail diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index d4359890dd..88836cfc61 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -190,6 +190,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [django-rest-auth][django-rest-auth] - Provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc. * [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF. * [drfpasswordless][drfpasswordless] - Adds (Medium, Square Cash inspired) passwordless logins and signups via email and mobile numbers. +* [django-rest-authemail][django-rest-authemail] - Provides a RESTful API for user signup and authentication using email addresses. ### Permissions @@ -362,3 +363,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-elasticsearch-dsl-drf]: https://github.com/barseghyanartur/django-elasticsearch-dsl-drf [django-api-client]: https://github.com/rhenter/django-api-client [drf-psq]: https://github.com/drf-psq/drf-psq +[django-rest-authemail]: https://github.com/celiao/django-rest-authemail From 4e0d6c411805743688bd25a2ad8021441a1ae1ac Mon Sep 17 00:00:00 2001 From: Vitor Hugo Date: Tue, 9 Mar 2021 08:54:58 -0300 Subject: [PATCH 415/907] Update default.css (#7643) When I apply a theme to the bootstrap used in the project, boolean inputs are out of line with the rest of the form. With this small payment, this no longer occurs. --- rest_framework/static/rest_framework/css/default.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/static/rest_framework/css/default.css b/rest_framework/static/rest_framework/css/default.css index 86fef17737..51ca3ba19e 100644 --- a/rest_framework/static/rest_framework/css/default.css +++ b/rest_framework/static/rest_framework/css/default.css @@ -40,7 +40,7 @@ td.nested > table { margin: 0; } -form select, form input, form textarea { +form select, form input:not([type=checkbox]), form textarea { width: 90%; } From 750bad0a58bdec85fa5a630a519277bac3b36535 Mon Sep 17 00:00:00 2001 From: Romain Rigaux Date: Tue, 9 Mar 2021 04:00:51 -0800 Subject: [PATCH 416/907] Actually use the loginUser arguments in the example (#7714) --- docs/topics/api-clients.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/api-clients.md b/docs/topics/api-clients.md index 9b61eaf427..b9f5e3ecd8 100644 --- a/docs/topics/api-clients.md +++ b/docs/topics/api-clients.md @@ -453,7 +453,7 @@ For example, using the "Django REST framework JWT" package function loginUser(username, password) { let action = ["api-token-auth", "obtain-token"]; - let params = {username: "example", email: "example@example.com"}; + let params = {username: username, password: password}; client.action(schema, action, params).then(function(result) { // On success, instantiate an authenticated client. let auth = window.coreapi.auth.TokenAuthentication({ From a3ae8ea77efa2fa9af69da5dfda9128ef94c0fde Mon Sep 17 00:00:00 2001 From: Dmitry Mugtasimov Date: Tue, 9 Mar 2021 15:06:12 +0300 Subject: [PATCH 417/907] Do not do `SELECT count(*) FROM ...` if pagination is not requested (#6098) * Do not do `SELECT count(*) FROM ...` if pagination is not requested * Update pagination.py Co-authored-by: Tom Christie --- rest_framework/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 60a57c8e4a..4db6461163 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -376,11 +376,11 @@ class LimitOffsetPagination(BasePagination): template = 'rest_framework/pagination/numbers.html' def paginate_queryset(self, queryset, request, view=None): - self.count = self.get_count(queryset) self.limit = self.get_limit(request) if self.limit is None: return None + self.count = self.get_count(queryset) self.offset = self.get_offset(request) self.request = request if self.count > self.limit and self.template is not None: From c69e2e4eaafd7270565f0ecab7635f8988bc0f6d Mon Sep 17 00:00:00 2001 From: PaulGilmartin Date: Wed, 10 Mar 2021 10:02:38 +0100 Subject: [PATCH 418/907] Add graphwrap to third-party-packages.md (#7819) * Add graphwrap to third-party-packages documentation * Fix typo in third party packages docs * Remove additional newline Co-authored-by: Paul Gilmartin Co-authored-by: Tom Christie --- docs/community/third-party-packages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 88836cfc61..93ed3e2ca7 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -215,6 +215,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [drf-action-serializer][drf-action-serializer] - Serializer providing per-action fields config for use with ViewSets to prevent having to write multiple serializers. * [djangorestframework-dataclasses][djangorestframework-dataclasses] - Serializer providing automatic field generation for Python dataclasses, like the built-in ModelSerializer does for models. * [django-restql][django-restql] - Turn your REST API into a GraphQL like API(It allows clients to control which fields will be sent in a response, uses GraphQL like syntax, supports read and write on both flat and nested fields). +* [graphwrap][graphwrap] - Transform your REST API into a fully compliant GraphQL API with just two lines of code. Leverages [Graphene-Django](https://docs.graphene-python.org/projects/django/en/latest/) to dynamically build, at runtime, a GraphQL ObjectType for each view in your API. ### Serializer fields @@ -364,3 +365,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-api-client]: https://github.com/rhenter/django-api-client [drf-psq]: https://github.com/drf-psq/drf-psq [django-rest-authemail]: https://github.com/celiao/django-rest-authemail +[graphwrap]: https://github.com/PaulGilmartin/graph_wrap From c603b98403e05070e01852f18fa5b11b11e366c8 Mon Sep 17 00:00:00 2001 From: Pierre Chiquet Date: Wed, 10 Mar 2021 13:03:15 +0100 Subject: [PATCH 419/907] Fix OpenAPISchema rendering for timedelta (#7641) * Add failing test when rendering to json a schema with timedelta * Fix JSONOpenAPIRenderer for fields with default=timedelta() * fix isort * fix test for python 3.5 Co-authored-by: Pierre Chiquet --- rest_framework/renderers.py | 3 ++- tests/schemas/test_openapi.py | 16 +++++++++++++++- tests/schemas/views.py | 2 ++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 3c4be8aeb0..5b7ba8a8c8 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -1063,7 +1063,8 @@ def ignore_aliases(self, data): class JSONOpenAPIRenderer(BaseRenderer): media_type = 'application/vnd.oai.openapi+json' charset = None + encoder_class = encoders.JSONEncoder format = 'openapi-json' def render(self, data, media_type=None, renderer_context=None): - return json.dumps(data, indent=2).encode('utf-8') + return json.dumps(data, cls=self.encoder_class, indent=2).encode('utf-8') diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 542c377b15..871eb1b302 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -11,7 +11,8 @@ from rest_framework.compat import uritemplate from rest_framework.parsers import JSONParser, MultiPartParser from rest_framework.renderers import ( - BaseRenderer, BrowsableAPIRenderer, JSONRenderer, OpenAPIRenderer + BaseRenderer, BrowsableAPIRenderer, JSONOpenAPIRenderer, JSONRenderer, + OpenAPIRenderer ) from rest_framework.request import Request from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator @@ -992,6 +993,19 @@ def test_schema_construction(self): assert 'openapi' in schema assert 'paths' in schema + def test_schema_rendering_to_json(self): + patterns = [ + path('example/', views.ExampleGenericAPIView.as_view()), + ] + generator = SchemaGenerator(patterns=patterns) + + request = create_request('/') + schema = generator.get_schema(request=request) + ret = JSONOpenAPIRenderer().render(schema) + + assert b'"openapi": "' in ret + assert b'"default": "0.0"' in ret + def test_schema_with_no_paths(self): patterns = [] generator = SchemaGenerator(patterns=patterns) diff --git a/tests/schemas/views.py b/tests/schemas/views.py index 18b3beae4e..f1ed0bd4e3 100644 --- a/tests/schemas/views.py +++ b/tests/schemas/views.py @@ -1,4 +1,5 @@ import uuid +from datetime import timedelta from django.core.validators import ( DecimalValidator, MaxLengthValidator, MaxValueValidator, @@ -59,6 +60,7 @@ def get(self, *args, **kwargs): class ExampleSerializer(serializers.Serializer): date = serializers.DateField() datetime = serializers.DateTimeField() + duration = serializers.DurationField(default=timedelta()) hstore = serializers.HStoreField() uuid_field = serializers.UUIDField(default=uuid.uuid4) From 6f6d402d043acb6400736f882c6a48e3c7e773f4 Mon Sep 17 00:00:00 2001 From: sarath ak Date: Wed, 10 Mar 2021 18:02:10 +0530 Subject: [PATCH 420/907] Allow 'get_page' method for overriding #7626 (#7652) --- rest_framework/pagination.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 4db6461163..0f0aa9ccf9 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -198,7 +198,7 @@ def paginate_queryset(self, queryset, request, view=None): return None paginator = self.django_paginator_class(queryset, page_size) - page_number = request.query_params.get(self.page_query_param, 1) + page_number = self.get_page_number(request) if page_number in self.last_page_strings: page_number = paginator.num_pages @@ -217,6 +217,9 @@ def paginate_queryset(self, queryset, request, view=None): self.request = request return list(self.page) + def get_page_number(self, request): + return request.query_params.get(self.page_query_param, 1) + def get_paginated_response(self, data): return Response(OrderedDict([ ('count', self.page.paginator.count), From cef74d1726a73991e6805c1bf31ec7464dc738d1 Mon Sep 17 00:00:00 2001 From: John Alexis Munera Date: Wed, 10 Mar 2021 07:37:06 -0500 Subject: [PATCH 421/907] Add rest-framework-actions to Third Party Packages (#7688) This pull request adds rest-framework-actions to Third Party Packages, under Views rest-framework-actions can be found on PyPi here: https://pypi.org/project/rest-framework-actions/ Co-authored-by: Tom Christie --- docs/community/third-party-packages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 93ed3e2ca7..838122cbe6 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -227,6 +227,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [django-rest-multiple-models][django-rest-multiple-models] - Provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request. * [drf-typed-views][drf-typed-views] - Use Python type annotations to validate/deserialize request parameters. Inspired by API Star, Hug and FastAPI. +* [rest-framework-actions][rest-framework-actions] - Provides control over each action in ViewSets. Serializers per action, method. ### Routers @@ -366,3 +367,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [drf-psq]: https://github.com/drf-psq/drf-psq [django-rest-authemail]: https://github.com/celiao/django-rest-authemail [graphwrap]: https://github.com/PaulGilmartin/graph_wrap +[rest-framework-actions]: https://github.com/AlexisMunera98/rest-framework-actions From 4f3cd8c7b0495276e7dc99cc06a1069082a37f9d Mon Sep 17 00:00:00 2001 From: Eshaan Bansal Date: Wed, 10 Mar 2021 18:10:45 +0530 Subject: [PATCH 422/907] add django-rest-durin to 3rd party auth libs [docs] (#7615) Co-authored-by: Tom Christie --- docs/api-guide/authentication.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index d13c5a2f0d..4497f73bd0 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -436,6 +436,12 @@ There are currently two forks of this project. [django-rest-authemail][django-rest-authemail] provides a RESTful API interface for user signup and authentication. Email addresses are used for authentication, rather than usernames. API endpoints are available for signup, signup email verification, login, logout, password reset, password reset verification, email change, email change verification, password change, and user detail. A fully-functional example project and detailed instructions are included. +## Django-Rest-Durin + +[Django-Rest-Durin][django-rest-durin] is built with the idea to have one library that does token auth for multiple Web/CLI/Mobile API clients via one interface but allows different token configuration for each API Client that consumes the API. It provides support for multiple tokens per user via custom models, views, permissions that work with Django-Rest-Framework. The token expiration time can be different per API client and is customizable via the Django Admin Interface. + +More information can be found in the [Documentation](https://django-rest-durin.readthedocs.io/en/latest/index.html). + [cite]: https://jacobian.org/writing/rest-worst-practices/ [http401]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 [http403]: https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4 @@ -471,3 +477,4 @@ There are currently two forks of this project. [django-rest-knox]: https://github.com/James1345/django-rest-knox [drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless [django-rest-authemail]: https://github.com/celiao/django-rest-authemail +[django-rest-durin]: https://github.com/eshaan7/django-rest-durin From c78f99217673fbcdca23627817909d6419be0175 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Wed, 10 Mar 2021 07:44:17 -0500 Subject: [PATCH 423/907] Make the doc on overriding the default permission classes more clear. (#7661) --- docs/api-guide/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index f694d6be50..08031bceb0 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -116,7 +116,7 @@ Or, if you're using the `@api_view` decorator with function based views. } return Response(content) -__Note:__ when you set new permission classes through class attribute or decorators you're telling the view to ignore the default list set over the __settings.py__ file. +__Note:__ when you set new permission classes via the class attribute or decorators you're telling the view to ignore the default list set in the __settings.py__ file. Provided they inherit from `rest_framework.permissions.BasePermission`, permissions can be composed using standard Python bitwise operators. For example, `IsAuthenticatedOrReadOnly` could be written: From ffe11d41bd357ab3acafebe9490bed89afc2039d Mon Sep 17 00:00:00 2001 From: Max Morlocke Date: Wed, 10 Mar 2021 07:45:47 -0500 Subject: [PATCH 424/907] upgrade pytest+pytest-django to eliminate dependencies on six (#7672) * upgrade to latest version of pytest+pytest-django to eliminate dependency on six * rollback pytest to 6.1 as py35 is dropped in 6.2 Co-authored-by: Tom Christie --- requirements/requirements-testing.txt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index 99463560e2..c5198dec54 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -1,5 +1,4 @@ # Pytest for running the tests. -pytest>=5.4.1,<5.5 -pytest-django>=3.9.0,<3.10 -pytest-cov>=2.7.1 -six>=1.14.0 +pytest>=6.1.1,<6.2 +pytest-django>=4.1.0,<4.2 +pytest-cov>=2.10.1 From c05cbe2da213ae6fef5ea66dbafb050b76923117 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 10 Mar 2021 12:50:59 +0000 Subject: [PATCH 425/907] Update pagination.py Include `last_page_strings` logic *inside* the `get_page_number method. --- rest_framework/pagination.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 0f0aa9ccf9..87ff7d3d69 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -199,8 +199,6 @@ def paginate_queryset(self, queryset, request, view=None): paginator = self.django_paginator_class(queryset, page_size) page_number = self.get_page_number(request) - if page_number in self.last_page_strings: - page_number = paginator.num_pages try: self.page = paginator.page(page_number) @@ -218,7 +216,10 @@ def paginate_queryset(self, queryset, request, view=None): return list(self.page) def get_page_number(self, request): - return request.query_params.get(self.page_query_param, 1) + page_number = request.query_params.get(self.page_query_param, 1) + if page_number in self.last_page_strings: + page_number = paginator.num_pages + return page_number def get_paginated_response(self, data): return Response(OrderedDict([ From 39a98c80a6cb0b21fb6adb5ee97bc64b8b404433 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 11 Mar 2021 09:21:22 +0000 Subject: [PATCH 426/907] Fix 'get_page_number' implementation --- rest_framework/pagination.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 87ff7d3d69..91da73de64 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -198,7 +198,7 @@ def paginate_queryset(self, queryset, request, view=None): return None paginator = self.django_paginator_class(queryset, page_size) - page_number = self.get_page_number(request) + page_number = self.get_page_number(request, paginator) try: self.page = paginator.page(page_number) @@ -215,7 +215,7 @@ def paginate_queryset(self, queryset, request, view=None): self.request = request return list(self.page) - def get_page_number(self, request): + def get_page_number(self, request, paginator): page_number = request.query_params.get(self.page_query_param, 1) if page_number in self.last_page_strings: page_number = paginator.num_pages From f0706190614541fd47aeb7576c2030b58907d68b Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 11 Mar 2021 11:26:11 +0100 Subject: [PATCH 427/907] Updated tox/travis to point to Django `main` branch. (#7827) --- .travis.yml | 6 +++--- tox.ini | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f9f22336fc..57a91e594a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,11 +20,11 @@ matrix: - { python: "3.8", env: DJANGO=3.0 } - { python: "3.8", env: DJANGO=3.1 } - { python: "3.8", env: DJANGO=3.2 } - - { python: "3.8", env: DJANGO=master } + - { python: "3.8", env: DJANGO=main } - { python: "3.9", env: DJANGO=3.1 } - { python: "3.9", env: DJANGO=3.2 } - - { python: "3.9", env: DJANGO=master } + - { python: "3.9", env: DJANGO=main } - { python: "3.8", env: TOXENV=base } - { python: "3.8", env: TOXENV=lint } @@ -39,7 +39,7 @@ matrix: - tox # test sdist allow_failures: - - env: DJANGO=master + - env: DJANGO=main - env: DJANGO=3.2 install: diff --git a/tox.ini b/tox.ini index 544bab163c..df16cf947f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ envlist = {py36,py37,py38}-django30, {py36,py37,py38,py39}-django31, {py36,py37,py38,py39}-django32, - {py38,py39}-djangomaster, + {py38,py39}-djangomain, base,dist,lint,docs, [travis:env] @@ -13,7 +13,7 @@ DJANGO = 3.0: django30 3.1: django31 3.2: django32 - master: djangomaster + main: djangomain [testenv] commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning runtests.py --fast --coverage {posargs} @@ -26,7 +26,7 @@ deps = django30: Django>=3.0,<3.1 django31: Django>=3.1,<3.2 django32: Django>=3.2a1,<4.0 - djangomaster: https://github.com/django/django/archive/master.tar.gz + djangomain: https://github.com/django/django/archive/main.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 883f6fe814acdbc35032143275ae78f6ff832d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Abac=C4=B1?= Date: Thu, 11 Mar 2021 14:39:06 +0300 Subject: [PATCH 428/907] Rename django-extra-fields to drf-extra-fields (#7833) --- docs/community/third-party-packages.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 838122cbe6..32fc8a0f5e 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -220,7 +220,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque ### Serializer fields * [drf-compound-fields][drf-compound-fields] - Provides "compound" serializer fields, such as lists of simple values. -* [django-extra-fields][django-extra-fields] - Provides extra serializer fields. +* [drf-extra-fields][drf-extra-fields] - Provides extra serializer fields. * [django-versatileimagefield][django-versatileimagefield] - Provides a drop-in replacement for Django's stock `ImageField` that makes it easy to serve images in multiple sizes/renditions from a single field. For DRF-specific implementation docs, [click here][django-versatileimagefield-drf-docs]. ### Views @@ -311,7 +311,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [djangorestframework-gis]: https://github.com/djangonauts/django-rest-framework-gis [djangorestframework-hstore]: https://github.com/djangonauts/django-rest-framework-hstore [drf-compound-fields]: https://github.com/estebistec/drf-compound-fields -[django-extra-fields]: https://github.com/Hipo/drf-extra-fields +[drf-extra-fields]: https://github.com/Hipo/drf-extra-fields [django-rest-multiple-models]: https://github.com/MattBroach/DjangoRestMultipleModels [drf-nested-routers]: https://github.com/alanjds/drf-nested-routers [wq.db.rest]: https://wq.io/docs/about-rest From ff625ecff5026e2f1e25014a0399afabb73753d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henryk=20Pl=C3=B6tz?= Date: Mon, 15 Mar 2021 11:28:45 +0100 Subject: [PATCH 429/907] Document object level permissions gotchas (#7446) * Document the limitation that object level permissions do not apply to object creation. See for example #6409. * Add overview of three different ways to restrict access --- docs/api-guide/permissions.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 08031bceb0..6912c375c2 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -70,6 +70,8 @@ For performance reasons the generic views will not automatically apply object le Often when you're using object level permissions you'll also want to [filter the queryset][filtering] appropriately, to ensure that users only have visibility onto instances that they are permitted to view. +Because the `get_object()` method is not called, object level permissions from the `has_object_permission()` method **are not applied** when creating objects. In order to restrict object creation you need to implement the permission check either in your Serializer class or override the `perform_create()` method of your ViewSet class. + ## Setting the permission policy The default permission policy may be set globally, using the `DEFAULT_PERMISSION_CLASSES` setting. For example. @@ -272,6 +274,30 @@ Note that the generic views will check the appropriate object level permissions, Also note that the generic views will only check the object-level permissions for views that retrieve a single model instance. If you require object-level filtering of list views, you'll need to filter the queryset separately. See the [filtering documentation][filtering] for more details. +# Overview of access restriction methods + +REST framework offers three different methods to customize access restrictions on a case-by-case basis. These apply in different scenarios and have different effects and limitations. + + * `queryset`/`get_queryset()`: Limits the general visibility of existing objects from the database. The queryset limits which objects will be listed and which objects can be modified or deleted. The `get_queryset()` method can apply different querysets based on the current action. + * `permission_classes`/`get_permissions()`: General permission checks based on the current action, request and targeted object. Object level permissions can only be applied to retrieve, modify and deletion actions. Permission checks for list and create will be applied to the entire object type. (In case of list: subject to restrictions in the queryset.) + * `serializer_class`/`get_serializer()`: Instance level restrictions that apply to all objects on input and output. The serializer may have access to the request context. The `get_serializer()` method can apply different serializers based on the current action. + +The following table lists the access restriction methods and the level of control they offer over which actions. + +| | `queryset` | `permission_classes` | `serializer_class` | +|------------------------------------|------------|----------------------|--------------------| +| Action: list | global | no | object-level* | +| Action: create | no | global | object-level | +| Action: retrieve | global | object-level | object-level | +| Action: update | global | object-level | object-level | +| Action: partial_update | global | object-level | object-level | +| Action: destroy | global | object-level | no | +| Can reference action in decision | no** | yes | no** | +| Can reference request in decision | no** | yes | yes | + + \* A Serializer class should not raise PermissionDenied in a list action, or the entire list would not be returned.
    + \** The `get_*()` methods have access to the current view and can return different Serializer or QuerySet instances based on the request or action. + --- # Third party packages From b256c46cb1470f818328941e0005134d38087220 Mon Sep 17 00:00:00 2001 From: Alex Hedlund Date: Mon, 15 Mar 2021 12:44:03 +0200 Subject: [PATCH 430/907] Render JSON fields with proper indentation in browsable API forms. (#6243) * Fix JSONBoundField usage on nested serializers (#6211) * Unify JSONBoundField as_form_field output between py2 and py3 When using json.dumps with indenting, in python2 the default formatting prints whitespace after commas (,) and python3 does not. This can be unified with the separators keyword argument. --- rest_framework/fields.py | 3 +++ rest_framework/utils/serializer_helpers.py | 9 ++++++++- tests/test_bound_fields.py | 23 +++++++++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index d91299484e..e4be54751d 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1764,6 +1764,9 @@ class JSONField(Field): 'invalid': _('Value must be valid JSON.') } + # Workaround for isinstance calls when importing the field isn't possible + _is_jsonfield = True + def __init__(self, *args, **kwargs): self.binary = kwargs.pop('binary', False) self.encoder = kwargs.pop('encoder', None) diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index cd0373adcb..4cd2ada314 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -87,7 +87,12 @@ def as_form_field(self): # value will be a JSONString, rather than a JSON primitive. if not getattr(value, 'is_json_string', False): try: - value = json.dumps(self.value, sort_keys=True, indent=4) + value = json.dumps( + self.value, + sort_keys=True, + indent=4, + separators=(',', ': '), + ) except (TypeError, ValueError): pass return self.__class__(self._field, value, self.errors, self._prefix) @@ -115,6 +120,8 @@ def __getitem__(self, key): error = self.errors.get(key) if isinstance(self.errors, dict) else None if hasattr(field, 'fields'): return NestedBoundField(field, value, error, prefix=self.name + '.') + elif getattr(field, '_is_jsonfield', False): + return JSONBoundField(field, value, error, prefix=self.name + '.') return BoundField(field, value, error, prefix=self.name + '.') def as_form_field(self): diff --git a/tests/test_bound_fields.py b/tests/test_bound_fields.py index dec8793c33..eee7d9b852 100644 --- a/tests/test_bound_fields.py +++ b/tests/test_bound_fields.py @@ -91,6 +91,10 @@ class ExampleSerializer(serializers.Serializer): assert rendered_packed == expected_packed +class CustomJSONField(serializers.JSONField): + pass + + class TestNestedBoundField: def test_nested_empty_bound_field(self): class Nested(serializers.Serializer): @@ -117,14 +121,31 @@ def test_as_form_fields(self): class Nested(serializers.Serializer): bool_field = serializers.BooleanField() null_field = serializers.IntegerField(allow_null=True) + json_field = serializers.JSONField() + custom_json_field = CustomJSONField() class ExampleSerializer(serializers.Serializer): nested = Nested() - serializer = ExampleSerializer(data={'nested': {'bool_field': False, 'null_field': None}}) + serializer = ExampleSerializer( + data={'nested': { + 'bool_field': False, 'null_field': None, + 'json_field': {'bool_item': True, 'number': 1, 'text_item': 'text'}, + 'custom_json_field': {'bool_item': True, 'number': 1, 'text_item': 'text'}, + }}) assert serializer.is_valid() assert serializer['nested']['bool_field'].as_form_field().value == '' assert serializer['nested']['null_field'].as_form_field().value == '' + assert serializer['nested']['json_field'].as_form_field().value == '''{ + "bool_item": true, + "number": 1, + "text_item": "text" +}''' + assert serializer['nested']['custom_json_field'].as_form_field().value == '''{ + "bool_item": true, + "number": 1, + "text_item": "text" +}''' def test_rendering_nested_fields_with_none_value(self): from rest_framework.renderers import HTMLFormRenderer From ce1568322af61a6b41ccc5dc2c631c6927ed5e71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Abac=C4=B1?= Date: Tue, 16 Mar 2021 15:53:39 +0300 Subject: [PATCH 431/907] Ordering filter bug with model property serializer field (#7609) * Add failing tests for ordering filter with model property * Fix get_default_valid_fields of OrderingFilter * Filter model properties in get_default_valid_fields of OrderingFilter --- rest_framework/filters.py | 12 ++++++++- tests/test_filters.py | 51 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 3665775195..1ffd9edc02 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -226,10 +226,20 @@ def get_default_valid_fields(self, queryset, view, context={}): ) raise ImproperlyConfigured(msg % self.__class__.__name__) + model_class = queryset.model + model_property_names = [ + # 'pk' is a property added in Django's Model class, however it is valid for ordering. + attr for attr in dir(model_class) if isinstance(getattr(model_class, attr), property) and attr != 'pk' + ] + return [ (field.source.replace('.', '__') or field_name, field.label) for field_name, field in serializer_class(context=context).fields.items() - if not getattr(field, 'write_only', False) and not field.source == '*' + if ( + not getattr(field, 'write_only', False) and + not field.source == '*' and + field.source not in model_property_names + ) ] def get_valid_fields(self, queryset, view, context={}): diff --git a/tests/test_filters.py b/tests/test_filters.py index 567e5f83fc..37ae4c7cf3 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -424,6 +424,10 @@ class OrderingFilterModel(models.Model): title = models.CharField(max_length=20, verbose_name='verbose title') text = models.CharField(max_length=100) + @property + def description(self): + return self.title + ": " + self.text + class OrderingFilterRelatedModel(models.Model): related_object = models.ForeignKey(OrderingFilterModel, related_name="relateds", on_delete=models.CASCADE) @@ -436,6 +440,17 @@ class Meta: fields = '__all__' +class OrderingFilterSerializerWithModelProperty(serializers.ModelSerializer): + class Meta: + model = OrderingFilterModel + fields = ( + "id", + "title", + "text", + "description" + ) + + class OrderingDottedRelatedSerializer(serializers.ModelSerializer): related_text = serializers.CharField(source='related_object.text') related_title = serializers.CharField(source='related_object.title') @@ -551,6 +566,42 @@ class OrderingListView(generics.ListAPIView): {'id': 1, 'title': 'zyx', 'text': 'abc'}, ] + def test_ordering_without_ordering_fields(self): + class OrderingListView(generics.ListAPIView): + queryset = OrderingFilterModel.objects.all() + serializer_class = OrderingFilterSerializerWithModelProperty + filter_backends = (filters.OrderingFilter,) + ordering = ('title',) + + view = OrderingListView.as_view() + + # Model field ordering works fine. + request = factory.get('/', {'ordering': 'text'}) + response = view(request) + assert response.data == [ + {'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, + {'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, + ] + + # `incorrectfield` ordering works fine. + request = factory.get('/', {'ordering': 'foobar'}) + response = view(request) + assert response.data == [ + {'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, + {'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, + ] + + # `description` is a Model property, which should be ignored. + request = factory.get('/', {'ordering': 'description'}) + response = view(request) + assert response.data == [ + {'id': 3, 'title': 'xwv', 'text': 'cde', 'description': 'xwv: cde'}, + {'id': 2, 'title': 'yxw', 'text': 'bcd', 'description': 'yxw: bcd'}, + {'id': 1, 'title': 'zyx', 'text': 'abc', 'description': 'zyx: abc'}, + ] + def test_default_ordering(self): class OrderingListView(generics.ListAPIView): queryset = OrderingFilterModel.objects.all() From b0ca248d88240c58ff04554bf5df07a6dd8d9e8f Mon Sep 17 00:00:00 2001 From: Jeff Baumes Date: Tue, 16 Mar 2021 08:57:04 -0400 Subject: [PATCH 432/907] Correct the use of "to" (#7696) --- docs/tutorial/5-relationships-and-hyperlinked-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index 4cd4e9bbd5..b0f3380859 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -143,7 +143,7 @@ We can change the default list style to use pagination, by modifying our `tutori Note that settings in REST framework are all namespaced into a single dictionary setting, named `REST_FRAMEWORK`, which helps keep them well separated from your other project settings. -We could also customize the pagination style if we needed too, but in this case we'll just stick with the default. +We could also customize the pagination style if we needed to, but in this case we'll just stick with the default. ## Browsing the API From 9c9ffb18f44062fd05f0b4e06b756c0a35230561 Mon Sep 17 00:00:00 2001 From: Jesse London Date: Tue, 16 Mar 2021 08:25:21 -0500 Subject: [PATCH 433/907] made Browsable API base template cachable: omit CSRF token when unnecessary (#7717) HTML responses generated by the Browsable API otherwise generate inconsistent ETAGs -- due to the presence of CSRF tokens in the response -- even when the API is read-only, (and as such when the response contains no resource-modifying forms, i.e. neither POST nor PUT forms, which might require the CSRF token). While the template was appropriately including CSRF tokens only within POST and PUT forms, its AJAX overlay included the CSRF token in *every* response, regardless of whether it would be needed. This change brings the logic of the `script` block into line with that of the rest of the template -- and such that read-only APIs (and really the Browsable API pages of *any* read-only resources) will not needlessly include the CSRF token, and will now be safely cachable -- by both back-end systems and by the user agent. --- .../templates/rest_framework/base.html | 2 +- tests/test_templates.py | 20 +++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index a88e1591c6..4d057b6322 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -290,7 +290,7 @@

    {{ name }}

    diff --git a/tests/test_templates.py b/tests/test_templates.py index 0dba78ea22..195296e161 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -3,15 +3,23 @@ from django.shortcuts import render -def test_base_template_with_context(): - context = {'request': True, 'csrf_token': 'TOKEN'} - result = render({}, 'rest_framework/base.html', context=context) - assert re.search(r'\bcsrfToken: "TOKEN"', result.content.decode()) - - def test_base_template_with_no_context(): # base.html should be renderable with no context, # so it can be easily extended. result = render({}, 'rest_framework/base.html') # note that this response will not include a valid CSRF token assert re.search(r'\bcsrfToken: ""', result.content.decode()) + + +def test_base_template_with_simple_context(): + context = {'request': True, 'csrf_token': 'TOKEN'} + result = render({}, 'rest_framework/base.html', context=context) + # note that response will STILL not include a CSRF token + assert re.search(r'\bcsrfToken: ""', result.content.decode()) + + +def test_base_template_with_editing_context(): + context = {'request': True, 'post_form': object(), 'csrf_token': 'TOKEN'} + result = render({}, 'rest_framework/base.html', context=context) + # response includes a CSRF token in support of the POST form + assert re.search(r'\bcsrfToken: "TOKEN"', result.content.decode()) From a40bce50cda95652afd822d69d91a67b78bc05b0 Mon Sep 17 00:00:00 2001 From: Yuekui Date: Tue, 16 Mar 2021 06:29:13 -0700 Subject: [PATCH 434/907] No need to explictitly set None as default (#7373) --- docs/api-guide/filtering.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index d305ede6ba..478e3bcf95 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -75,7 +75,7 @@ We can override `.get_queryset()` to deal with URLs such as `http://example.com/ by filtering against a `username` query parameter in the URL. """ queryset = Purchase.objects.all() - username = self.request.query_params.get('username', None) + username = self.request.query_params.get('username') if username is not None: queryset = queryset.filter(purchaser__username=username) return queryset From 3e274146fcd6baffa82ac6e146e2c3ca35d447cd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 17 Mar 2021 13:24:38 +0000 Subject: [PATCH 435/907] Fix WSGI signature for DjangoTestAdapter (#7846) Closes https://github.com/encode/django-rest-framework/issues/7132 --- rest_framework/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index f2581cacca..8ab0f2de19 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -79,7 +79,7 @@ def send(self, request, *args, **kwargs): """ raw_kwargs = {} - def start_response(wsgi_status, wsgi_headers): + def start_response(wsgi_status, wsgi_headers, exc_info=None): status, _, reason = wsgi_status.partition(' ') raw_kwargs['status'] = int(status) raw_kwargs['reason'] = reason From 7b53960c3bef7ffc8deb727639afd2ea118879b0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 17 Mar 2021 13:24:55 +0000 Subject: [PATCH 436/907] Revert "made Browsable API base template cachable: omit CSRF token when unnecessary (#7717)" (#7847) This reverts commit 9c9ffb18f44062fd05f0b4e06b756c0a35230561. --- .../templates/rest_framework/base.html | 2 +- tests/test_templates.py | 20 ++++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 4d057b6322..a88e1591c6 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -290,7 +290,7 @@

    {{ name }}

    diff --git a/tests/test_templates.py b/tests/test_templates.py index 195296e161..0dba78ea22 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -3,23 +3,15 @@ from django.shortcuts import render +def test_base_template_with_context(): + context = {'request': True, 'csrf_token': 'TOKEN'} + result = render({}, 'rest_framework/base.html', context=context) + assert re.search(r'\bcsrfToken: "TOKEN"', result.content.decode()) + + def test_base_template_with_no_context(): # base.html should be renderable with no context, # so it can be easily extended. result = render({}, 'rest_framework/base.html') # note that this response will not include a valid CSRF token assert re.search(r'\bcsrfToken: ""', result.content.decode()) - - -def test_base_template_with_simple_context(): - context = {'request': True, 'csrf_token': 'TOKEN'} - result = render({}, 'rest_framework/base.html', context=context) - # note that response will STILL not include a CSRF token - assert re.search(r'\bcsrfToken: ""', result.content.decode()) - - -def test_base_template_with_editing_context(): - context = {'request': True, 'post_form': object(), 'csrf_token': 'TOKEN'} - result = render({}, 'rest_framework/base.html', context=context) - # response includes a CSRF token in support of the POST form - assert re.search(r'\bcsrfToken: "TOKEN"', result.content.decode()) From 67ebdd32cdab0ec9078c281d24971ccd9d119173 Mon Sep 17 00:00:00 2001 From: Aristotelis Mikropoulos Date: Wed, 17 Mar 2021 15:28:38 +0200 Subject: [PATCH 437/907] Reject PrimaryKeyRelatedField bool lookup values (#7597) * Reject PrimaryKeyRelatedField bool lookup values * Test PrimaryKeyRelatedField bool lookup rejection * Fix indentation in test --- rest_framework/relations.py | 2 ++ tests/test_relations.py | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index eaf27e1d96..cbdf233698 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -259,6 +259,8 @@ def to_internal_value(self, data): data = self.pk_field.to_internal_value(data) queryset = self.get_queryset() try: + if isinstance(data, bool): + raise TypeError return queryset.get(pk=data) except ObjectDoesNotExist: self.fail('does_not_exist', pk_value=data) diff --git a/tests/test_relations.py b/tests/test_relations.py index 92aeecf6c4..bb719a65a9 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -107,6 +107,12 @@ def test_pk_related_lookup_invalid_type(self): msg = excinfo.value.detail[0] assert msg == 'Incorrect type. Expected pk value, received BadType.' + def test_pk_related_lookup_bool(self): + with pytest.raises(serializers.ValidationError) as excinfo: + self.field.to_internal_value(True) + msg = excinfo.value.detail[0] + assert msg == 'Incorrect type. Expected pk value, received bool.' + def test_pk_representation(self): representation = self.field.to_representation(self.instance) assert representation == self.instance.pk From b25ac6c5e36403f62b13163a0190eaa48b586c47 Mon Sep 17 00:00:00 2001 From: Anton Zaslavskiy Date: Fri, 19 Mar 2021 14:46:09 +0300 Subject: [PATCH 438/907] Don't hit db to access user_id in TokenProxy (#7852) --- rest_framework/authtoken/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/authtoken/models.py b/rest_framework/authtoken/models.py index 540049295d..5a143d936c 100644 --- a/rest_framework/authtoken/models.py +++ b/rest_framework/authtoken/models.py @@ -46,7 +46,7 @@ class TokenProxy(Token): """ @property def pk(self): - return self.user.pk + return self.user_id class Meta: proxy = 'rest_framework.authtoken' in settings.INSTALLED_APPS From 0cddf097ca50344355db79b048b12c1805bbe180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hasan=20Talha=20Yaz=C4=B1c=C4=B1?= Date: Sun, 21 Mar 2021 10:53:09 +0100 Subject: [PATCH 439/907] Fix typo in docs (#7853) --- docs/api-guide/viewsets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index d5815127b6..d4ab5a7317 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -185,7 +185,7 @@ The decorator allows you to override any viewset-level configuration such as `pe def set_password(self, request, pk=None): ... -The two new actions will then be available at the urls `^users/{pk}/set_password/$` and `^users/{pk}/unset_password/$`. Use the `url_path` and `url_name` parameters to change the URL segement and the reverse URL name of the action. +The two new actions will then be available at the urls `^users/{pk}/set_password/$` and `^users/{pk}/unset_password/$`. Use the `url_path` and `url_name` parameters to change the URL segment and the reverse URL name of the action. To view all extra actions, call the `.get_extra_actions()` method. From 7e3dd9cd1b7d9cd5d036b4b733937d93304b2dd8 Mon Sep 17 00:00:00 2001 From: Mohammad Ashraful Islam Date: Mon, 22 Mar 2021 18:07:48 +0600 Subject: [PATCH 440/907] Added fast-drf as a thirdparty package for making API development faster. (#7857) --- docs/community/third-party-packages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 32fc8a0f5e..046966594c 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -279,6 +279,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [djangorestframework-features][djangorestframework-features] - Advanced schema generation and more based on named features. * [django-elasticsearch-dsl-drf][django-elasticsearch-dsl-drf] - Integrate Elasticsearch DSL with Django REST framework. Package provides views, serializers, filter backends, pagination and other handy add-ons. * [django-api-client][django-api-client] - DRF client that groups the Endpoint response, for use in CBVs and FBV as if you were working with Django's Native Models.. +* [fast-drf] - A model based library for making API development faster and easier. [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework @@ -368,3 +369,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-rest-authemail]: https://github.com/celiao/django-rest-authemail [graphwrap]: https://github.com/PaulGilmartin/graph_wrap [rest-framework-actions]: https://github.com/AlexisMunera98/rest-framework-actions +[fast-drf]: https://github.com/iashraful/fast-drf From 71e6c30034a1dd35a39ca74f86c371713e762c79 Mon Sep 17 00:00:00 2001 From: Joe Michelini <66066937+afolksetapart@users.noreply.github.com> Date: Mon, 22 Mar 2021 08:08:19 -0400 Subject: [PATCH 441/907] update SerializerMethodField example in docs (#7858) * update SerializerMethodField example * fix formatting --- docs/api-guide/fields.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 0492af9aa9..04f9939425 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -583,6 +583,7 @@ The serializer method referred to by the `method_name` argument should accept a class Meta: model = User + fields = '__all__' def get_days_since_joined(self, obj): return (now() - obj.date_joined).days From ebcb8d53108f1ebe56b9a7aa78bbe09b1079953c Mon Sep 17 00:00:00 2001 From: Jack Date: Thu, 25 Mar 2021 18:47:44 +0800 Subject: [PATCH 442/907] pick deque instead of list (#7849) Co-authored-by: Jack Zhang --- rest_framework/throttling.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 0ba2ba66b1..1374d44925 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -2,6 +2,7 @@ Provides various throttling policies. """ import time +from collections import deque from django.core.cache import cache as default_cache from django.core.exceptions import ImproperlyConfigured @@ -120,7 +121,7 @@ def allow_request(self, request, view): if self.key is None: return True - self.history = self.cache.get(self.key, []) + self.history = self.cache.get(self.key, deque()) self.now = self.timer() # Drop any requests from the history which have now passed the From 83ad265e138106c26745a49dce0576573f0d202c Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 25 Mar 2021 12:23:23 +0000 Subject: [PATCH 443/907] Version 3.12.3 (#7866) --- docs/community/release-notes.md | 16 ++++++++++++++++ rest_framework/__init__.py | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 49fb655b01..72e6b466b5 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -38,6 +38,22 @@ You can determine your currently installed version using `pip show`: ### 3.12.2 +Date: 25th March 2021 + +* Properly handle ATOMIC_REQUESTS when multiple database configurations are used. [#7739] +* Bypass `COUNT` query when `LimitOffsetPagination` is configured but pagination params are not included on the request. [#6098] +* Respect `allow_null=True` on `DecimalField`. [#7718] +* Allow title cased `"Yes"`/`"No"` values with `BooleanField`. [#7739] +* Add `PageNumberPagination.get_page_number()` method for overriding behavior. [#7652] +* Fixed rendering of timedelta values in OpenAPI schemas, when present as default, min, or max fields. [#7641] +* Render JSONFields with indentation in browsable API forms. [#6243] +* Remove unnecessary database query in admin Token views. [#7852] +* Raise validation errors when bools are passed to `PrimaryKeyRelatedField` fields, instead of casting to ints. [#7597] +* Don't include model properties as automatically generated ordering fields with `OrderingFilter`. [#7609] +* Use `deque` instead of `list` for tracking throttling `.history`. [#7849] + +### 3.12.2 + Date: 13th October 2020 * Fix issue if `rest_framework.authtoken.models` is imported, but `rest_framework.authtoken` is not in INSTALLED_APPS. [#7571] diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 7ff188a5ad..eb5d605b9b 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -10,7 +10,7 @@ import django __title__ = 'Django REST framework' -__version__ = '3.12.2' +__version__ = '3.12.3' __author__ = 'Tom Christie' __license__ = 'BSD 3-Clause' __copyright__ = 'Copyright 2011-2019 Encode OSS Ltd' From dffa612134e89183bc081ddfd2528bd22108b558 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 25 Mar 2021 12:30:45 +0000 Subject: [PATCH 444/907] Fix release notes typo --- docs/community/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 72e6b466b5..3920830057 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -36,7 +36,7 @@ You can determine your currently installed version using `pip show`: ## 3.12.x series -### 3.12.2 +### 3.12.3 Date: 25th March 2021 From 72c155d8f4897c13d302340e43eacb48ebe321f6 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Mar 2021 09:17:47 +0000 Subject: [PATCH 445/907] Revert "pick deque instead of list (#7849)" (#7872) This reverts commit ebcb8d53108f1ebe56b9a7aa78bbe09b1079953c. --- rest_framework/throttling.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py index 1374d44925..0ba2ba66b1 100644 --- a/rest_framework/throttling.py +++ b/rest_framework/throttling.py @@ -2,7 +2,6 @@ Provides various throttling policies. """ import time -from collections import deque from django.core.cache import cache as default_cache from django.core.exceptions import ImproperlyConfigured @@ -121,7 +120,7 @@ def allow_request(self, request, view): if self.key is None: return True - self.history = self.cache.get(self.key, deque()) + self.history = self.cache.get(self.key, []) self.now = self.timer() # Drop any requests from the history which have now passed the From f83620dcc9e87f81ddc846c56f2ad87c2e548f8d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Mar 2021 09:27:01 +0000 Subject: [PATCH 446/907] Version 3.12.4 (#7873) * Version 3.12.4 * Tweak release notes --- docs/community/release-notes.md | 6 ++++++ rest_framework/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 3920830057..baeeaf8741 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -36,6 +36,12 @@ You can determine your currently installed version using `pip show`: ## 3.12.x series +### 3.12.4 + +Date: 26th March 2021 + +* Revert use of `deque` instead of `list` for tracking throttling `.history`. (Due to incompatibility with DjangoRedis cache backend. See #7870) [#7872] + ### 3.12.3 Date: 25th March 2021 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index eb5d605b9b..0c75d3617e 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -10,7 +10,7 @@ import django __title__ = 'Django REST framework' -__version__ = '3.12.3' +__version__ = '3.12.4' __author__ = 'Tom Christie' __license__ = 'BSD 3-Clause' __copyright__ = 'Copyright 2011-2019 Encode OSS Ltd' From 0323d6f8955f987771269506ca5da461e2e7a248 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Mar 2021 12:27:10 +0000 Subject: [PATCH 447/907] Linting fixes (#7874) * Fixed code quality issues - Added '.deepsource.toml' file for continuous analysis on bug risk - Remove `return` from `__init__()` method - Remove duplicate dictionary key(s) - Use `max` built-in to get the maximum of two values - Remove redundant `None` default - Remove unnecessary comprehension Signed-off-by: ankitdobhal * Delete .deepsource.toml * Delete test_fields.py * Reintroduce file from accidental deletion Co-authored-by: ankitdobhal --- rest_framework/pagination.py | 3 +-- rest_framework/relations.py | 8 ++++---- rest_framework/request.py | 2 +- rest_framework/routers.py | 2 +- rest_framework/templatetags/rest_framework.py | 4 ++-- rest_framework/test.py | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 91da73de64..dc120d8e86 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -488,8 +488,7 @@ def get_html_context(self): _divide_with_ceil(self.offset, self.limit) ) - if final < 1: - final = 1 + final = max(final, 1) else: current = 1 final = 1 diff --git a/rest_framework/relations.py b/rest_framework/relations.py index cbdf233698..c987007842 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -104,11 +104,11 @@ def __init__(self, **kwargs): self.html_cutoff_text or _(api_settings.HTML_SELECT_CUTOFF_TEXT) ) if not method_overridden('get_queryset', RelatedField, self): - assert self.queryset is not None or kwargs.get('read_only', None), ( + assert self.queryset is not None or kwargs.get('read_only'), ( 'Relational field must provide a `queryset` argument, ' 'override `get_queryset`, or set read_only=`True`.' ) - assert not (self.queryset is not None and kwargs.get('read_only', None)), ( + assert not (self.queryset is not None and kwargs.get('read_only')), ( 'Relational fields should not provide a `queryset` argument, ' 'when setting read_only=`True`.' ) @@ -339,7 +339,7 @@ def get_url(self, obj, view_name, request, format): return self.reverse(view_name, kwargs=kwargs, request=request, format=format) def to_internal_value(self, data): - request = self.context.get('request', None) + request = self.context.get('request') try: http_prefix = data.startswith(('http:', 'https:')) except AttributeError: @@ -382,7 +382,7 @@ def to_representation(self, value): ) request = self.context['request'] - format = self.context.get('format', None) + format = self.context.get('format') # By default use whatever format is given for the current context # unless the target is a different type to the source. diff --git a/rest_framework/request.py b/rest_framework/request.py index 2a007cd2bb..17ceadb08e 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -316,7 +316,7 @@ def _supports_form_parsing(self): 'application/x-www-form-urlencoded', 'multipart/form-data' ) - return any([parser.media_type in form_media for parser in self.parsers]) + return any(parser.media_type in form_media for parser in self.parsers) def _parse(self): """ diff --git a/rest_framework/routers.py b/rest_framework/routers.py index e2afa573fe..e0ae24b95c 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -290,7 +290,7 @@ def get(self, request, *args, **kwargs): args=args, kwargs=kwargs, request=request, - format=kwargs.get('format', None) + format=kwargs.get('format') ) except NoReverseMatch: # Don't bail out if eg. no list routes exist, only detail routes. diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index 7bfa8f5995..db0e9c95c3 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -206,7 +206,7 @@ def format_value(value): if value is None or isinstance(value, bool): return mark_safe('%s' % {True: 'true', False: 'false', None: 'null'}[value]) elif isinstance(value, list): - if any([isinstance(item, (list, dict)) for item in value]): + if any(isinstance(item, (list, dict)) for item in value): template = loader.get_template('rest_framework/admin/list_value.html') else: template = loader.get_template('rest_framework/admin/simple_list_value.html') @@ -285,7 +285,7 @@ def schema_links(section, sec_key=None): def add_nested_class(value): if isinstance(value, dict): return 'class=nested' - if isinstance(value, list) and any([isinstance(item, (list, dict)) for item in value]): + if isinstance(value, list) and any(isinstance(item, (list, dict)) for item in value): return 'class=nested' return '' diff --git a/rest_framework/test.py b/rest_framework/test.py index 8ab0f2de19..e934eff55d 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -124,7 +124,7 @@ class CoreAPIClient(coreapi.Client): def __init__(self, *args, **kwargs): self._session = RequestsClient() kwargs['transports'] = [coreapi.transports.HTTPTransport(session=self.session)] - return super().__init__(*args, **kwargs) + super().__init__(*args, **kwargs) @property def session(self): From 96885dd9a72e94df5e898bea65abba5345260f11 Mon Sep 17 00:00:00 2001 From: David Smith <39445562+smithdc1@users.noreply.github.com> Date: Thu, 1 Apr 2021 09:49:47 +0100 Subject: [PATCH 448/907] Fixed markdown test (#7892) The pygments rendering of invalid json changed in pygments>=2.7.3 --- tests/test_description.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/test_description.py b/tests/test_description.py index 9e7e4dc322..3b7d95e0a1 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -26,7 +26,7 @@ ``` json [{ "alpha": 1, - "beta: "this is a string" + "beta": "this is a string" }] ```""" @@ -48,20 +48,18 @@
    [{
    \ "alpha":\ 1,
    \ - "beta: "this\ - is a \ -string"
    }]\ -
    + "beta":\ + "this is a string"
    \ +}]


    """ MARKDOWN_lt_33 = """
    [{
    \ "alpha":\ 1,
    \ - "beta: "this\ - is a\ - string"
    }]\ -
    + "beta":\ + "this is a string"
    \ +}]


    """ @@ -112,7 +110,7 @@ class MockView(APIView): ``` json [{ "alpha": 1, - "beta: "this is a string" + "beta": "this is a string" }] ```""" From 406e6a2f352dd98623707fccc45fef7a7309eb59 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 1 Apr 2021 14:15:53 +0100 Subject: [PATCH 449/907] Update MANIFEST.in (#7893) --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 262e3dc917..5159eeddc7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ include README.md include LICENSE.md -recursive-include tests/* * +recursive-include tests/ * recursive-include rest_framework/static *.js *.css *.png *.ico *.eot *.svg *.ttf *.woff *.woff2 recursive-include rest_framework/templates *.html schema.js recursive-include rest_framework/locale *.mo From 78da1a824f1de338e1678a0182237d4c9b6d58e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Apr 2021 16:52:19 +0100 Subject: [PATCH 450/907] Bump pygments from 2.4.2 to 2.7.4 in /requirements (#7886) Bumps [pygments](https://github.com/pygments/pygments) from 2.4.2 to 2.7.4. - [Release notes](https://github.com/pygments/pygments/releases) - [Changelog](https://github.com/pygments/pygments/blob/master/CHANGES) - [Commits](https://github.com/pygments/pygments/compare/2.4.2...2.7.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/requirements-optionals.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index 739555667e..121de580e8 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -2,7 +2,7 @@ psycopg2-binary>=2.8.5, <2.9 markdown==3.3;python_version>="3.6" markdown==3.2.2;python_version=="3.5" -pygments==2.4.2 +pygments==2.7.4 django-guardian==2.2.0 django-filter>=2.2.0, <2.3 coreapi==2.3.1 From d82519bf8a0f0d4eb50d9ceadc52a01b1e06830e Mon Sep 17 00:00:00 2001 From: David Smith <39445562+smithdc1@users.noreply.github.com> Date: Mon, 5 Apr 2021 10:28:03 +0100 Subject: [PATCH 451/907] Updated dependencies (#7589) --- requirements/requirements-codestyle.txt | 7 +++---- requirements/requirements-documentation.txt | 2 +- requirements/requirements-optionals.txt | 14 +++++++------- requirements/requirements-packaging.txt | 6 +++--- requirements/requirements-testing.txt | 6 +++--- tests/test_status.py | 3 +-- tests/test_validators.py | 3 +-- 7 files changed, 19 insertions(+), 22 deletions(-) diff --git a/requirements/requirements-codestyle.txt b/requirements/requirements-codestyle.txt index 4f54d6e778..d9a93884c9 100644 --- a/requirements/requirements-codestyle.txt +++ b/requirements/requirements-codestyle.txt @@ -1,7 +1,6 @@ # PEP8 code linting, which we run on all commits. -flake8==3.8.3 -flake8-tidy-imports==4.1.0 -pycodestyle==2.6.0 +flake8>=3.8.4,<3.9 +flake8-tidy-imports>=4.1.0,<4.2 # Sort and lint imports -isort==5.4.2 +isort>=5.6.2,<6.0 diff --git a/requirements/requirements-documentation.txt b/requirements/requirements-documentation.txt index e969ff471b..ad49287304 100644 --- a/requirements/requirements-documentation.txt +++ b/requirements/requirements-documentation.txt @@ -1,2 +1,2 @@ # MkDocs to build our documentation. -mkdocs==1.1 +mkdocs>=1.1.2,<1.2 diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index 121de580e8..4cb0e54f4b 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -1,10 +1,10 @@ # Optional packages which may be used with REST framework. -psycopg2-binary>=2.8.5, <2.9 -markdown==3.3;python_version>="3.6" -markdown==3.2.2;python_version=="3.5" -pygments==2.7.4 -django-guardian==2.2.0 -django-filter>=2.2.0, <2.3 coreapi==2.3.1 coreschema==0.0.4 -pyyaml>=5.1 +django-filter>=2.4.0,<3.0 +django-guardian>=2.3.0,<2.4 +markdown==3.3;python_version>="3.6" +markdown==3.2.2;python_version=="3.5" +psycopg2-binary>=2.8.5,<2.9 +pygments>=2.7.1,<2.8 +pyyaml>=5.3.1,<5.4 diff --git a/requirements/requirements-packaging.txt b/requirements/requirements-packaging.txt index 091622fbeb..3489c76ec0 100644 --- a/requirements/requirements-packaging.txt +++ b/requirements/requirements-packaging.txt @@ -1,8 +1,8 @@ # Wheel for PyPI installs. -wheel==0.34.2 +wheel>=0.35.1,<0.36 # Twine for secured PyPI uploads. -twine==3.1.1 +twine>=3.2.0,<3.3 # Transifex client for managing translation resources. -transifex-client==0.13.9 +transifex-clien>=0.13.12,<0.14 diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index c5198dec54..313fdedc9b 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -1,4 +1,4 @@ # Pytest for running the tests. -pytest>=6.1.1,<6.2 -pytest-django>=4.1.0,<4.2 -pytest-cov>=2.10.1 +pytest>=6.1,<7.0 +pytest-cov>=2.10.1,<3.0 +pytest-django>=4.1.0,<5.0 diff --git a/tests/test_status.py b/tests/test_status.py index 07d893bee9..b10f7df994 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -1,8 +1,7 @@ from django.test import TestCase from rest_framework.status import ( - is_client_error, is_informational, is_redirect, is_server_error, - is_success + is_client_error, is_informational, is_redirect, is_server_error, is_success ) diff --git a/tests/test_validators.py b/tests/test_validators.py index 4962cf5816..bccbe1514b 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -7,8 +7,7 @@ from rest_framework import serializers from rest_framework.exceptions import ValidationError from rest_framework.validators import ( - BaseUniqueForValidator, UniqueTogetherValidator, UniqueValidator, - qs_exists + BaseUniqueForValidator, UniqueTogetherValidator, UniqueValidator, qs_exists ) From 846fe70cff1232da93f4868216d625de4b835967 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 5 Apr 2021 11:12:28 +0100 Subject: [PATCH 452/907] De-duplicate contributing guide (#7901) The contributing guide from `docs/community/contributing.md` was copy-pasted to `CONTRIBUTING.md` and the two have drifted apart over time. The docs page seems to have been updated a bit more so let's leave only that version. --- CONTRIBUTING.md | 206 +-------------------------------------- PULL_REQUEST_TEMPLATE.md | 2 +- 2 files changed, 2 insertions(+), 206 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2f1aad08f4..a7f17b1a35 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,207 +1,3 @@ # Contributing to REST framework -> The world can only really be changed one piece at a time. The art is picking that piece. -> -> — [Tim Berners-Lee][cite] - -There are many ways you can contribute to Django REST framework. We'd like it to be a community-led project, so please get involved and help shape the future of the project. - -## Community - -The most important thing you can do to help push the REST framework project forward is to be actively involved wherever possible. Code contributions are often overvalued as being the primary way to get involved in a project, we don't believe that needs to be the case. - -If you use REST framework, we'd love you to be vocal about your experiences with it - you might consider writing a blog post about using REST framework, or publishing a tutorial about building a project with a particular JavaScript framework. Experiences from beginners can be particularly helpful because you'll be in the best position to assess which bits of REST framework are more difficult to understand and work with. - -Other really great ways you can help move the community forward include helping to answer questions on the [discussion group][google-group], or setting up an [email alert on StackOverflow][so-filter] so that you get notified of any new questions with the `django-rest-framework` tag. - -When answering questions make sure to help future contributors find their way around by hyperlinking wherever possible to related threads and tickets, and include backlinks from those items if relevant. - -## Code of conduct - -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. (e.g. 'Hey folks,') - -The [Django code of conduct][code-of-conduct] gives a fuller set of guidelines for participating in community forums. - -# Issues - -It's really helpful if you can make sure to address issues on the correct channel. Usage questions should be directed to the [discussion group][google-group]. Feature requests, bug reports and other issues should be raised on the GitHub [issue tracker][issues]. - -Some tips on good issue reporting: - -* When describing issues try to phrase your ticket in terms of the *behavior* you think needs changing rather than the *code* you think need changing. -* Search the issue list first for related items, and make sure you're running the latest version of REST framework before reporting an issue. -* If reporting a bug, then try to include a pull request with a failing test case. This will help us quickly identify if there is a valid issue, and make sure that it gets fixed more quickly if there is one. -* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability, bug fixes, and great documentation. -* Closing an issue doesn't necessarily mean the end of a discussion. If you believe your issue has been closed incorrectly, explain why and we'll consider if it needs to be reopened. - -## Triaging issues - -Getting involved in triaging incoming issues is a good way to start contributing. Every single ticket that comes into the ticket tracker needs to be reviewed in order to determine what the next steps should be. Anyone can help out with this, you just need to be willing to: - -* Read through the ticket - does it make sense, is it missing any context that would help explain it better? -* Is the ticket reported in the correct place, would it be better suited as a discussion on the discussion group? -* If the ticket is a bug report, can you reproduce it? Are you able to write a failing test case that demonstrates the issue and that can be submitted as a pull request? -* If the ticket is a feature request, do you agree with it, and could the feature request instead be implemented as a third party package? -* If a ticket hasn't had much activity and it addresses something you need, then comment on the ticket and try to find out what's needed to get it moving again. - -# Development - -To start developing on Django REST framework, clone the repo: - - 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. - -## Testing - -To run the tests, clone the repository, and then: - - # Setup the virtual environment - python3 -m venv env - source env/bin/activate - pip install django - pip install -r requirements.txt - - # Run the tests - ./runtests.py - -### Test options - -Run using a more concise output style. - - ./runtests.py -q - -Run the tests using a more concise output style, no coverage, no flake8. - - ./runtests.py --fast - -Don't run the flake8 code linting. - - ./runtests.py --nolint - -Only run the flake8 code linting, don't run the tests. - - ./runtests.py --lintonly - -Run the tests for a given test case. - - ./runtests.py MyTestCase - -Run the tests for a given test method. - - ./runtests.py MyTestCase.test_this_method - -Shorter form to run the tests for a given test method. - - ./runtests.py test_this_method - -Note: The test case and test method matching is fuzzy and will sometimes run other tests that contain a partial string match to the given command line input. - -### Running against multiple environments - -You can also use the excellent [tox][tox] testing tool to run the tests against all supported versions of Python and Django. Install `tox` globally, and then simply run: - - tox - -## Pull requests - -It's a good idea to make pull requests early on. A pull request represents the start of a discussion, and doesn't necessarily need to be the final, finished submission. - -It's also always best to make a new branch before starting work on a pull request. This means that you'll be able to later switch back to working on another separate issue without interfering with an ongoing pull requests. - -It's also useful to remember that if you have an outstanding pull request then pushing new commits to your GitHub repo will also automatically update the pull requests. - -GitHub's documentation for working on pull requests is [available here][pull-requests]. - -Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible on all supported versions of Python and Django. - -Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect. - -## Managing compatibility issues - -Sometimes, in order to ensure your code works on various different versions of Django, Python or third party libraries, you'll need to run slightly different code depending on the environment. Any code that branches in this way should be isolated into the `compat.py` module, and should provide a single common interface that the rest of the codebase can use. - -# Documentation - -The documentation for REST framework is built from the [Markdown][markdown] source files in [the docs directory][docs]. - -There are many great Markdown editors that make working with the documentation really easy. The [Mou editor for Mac][mou] is one such editor that comes highly recommended. - -## Building the documentation - -To build the documentation, install MkDocs with `pip install mkdocs` and then run the following command. - - mkdocs build - -This will build the documentation into the `site` directory. - -You can build the documentation and open a preview in a browser window by using the `serve` command. - - mkdocs serve - -## Language style - -Documentation should be in American English. The tone of the documentation is very important - try to stick to a simple, plain, objective and well-balanced style where possible. - -Some other tips: - -* Keep paragraphs reasonably short. -* Don't use abbreviations such as 'e.g.' but instead use the long form, such as 'For example'. - -## Markdown style - -There are a couple of conventions you should follow when working on the documentation. - -##### 1. Headers - -Headers should use the hash style. For example: - - ### Some important topic - -The underline style should not be used. **Don't do this:** - - Some important topic - ==================== - -##### 2. Links - -Links should always use the reference style, with the referenced hyperlinks kept at the end of the document. - - Here is a link to [some other thing][other-thing]. - - More text... - - [other-thing]: http://example.com/other/thing - -This style helps keep the documentation source consistent and readable. - -If you are hyperlinking to another REST framework document, you should use a relative link, and link to the `.md` suffix. For example: - - [authentication]: ../api-guide/authentication.md - -Linking in this style means you'll be able to click the hyperlink in your Markdown editor to open the referenced document. When the documentation is built, these links will be converted into regular links to HTML pages. - -##### 3. Notes - -If you want to draw attention to a note or warning, use a pair of enclosing lines, like so: - - --- - - **Note:** A useful documentation note. - - --- - - -[cite]: https://www.w3.org/People/Berners-Lee/FAQ.html -[code-of-conduct]: https://www.djangoproject.com/conduct/ -[google-group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework -[so-filter]: https://stackexchange.com/filters/66475/rest-framework -[issues]: https://github.com/encode/django-rest-framework/issues?state=open -[pep-8]: https://www.python.org/dev/peps/pep-0008/ -[pull-requests]: https://help.github.com/articles/using-pull-requests -[tox]: https://tox.readthedocs.io/en/latest/ -[markdown]: https://daringfireball.net/projects/markdown/basics -[docs]: https://github.com/encode/django-rest-framework/tree/master/docs -[mou]: http://mouapp.com/ +See the [Contributing guide in the documentation](https://www.django-rest-framework.org/community/contributing/). diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 70673c6c16..e9230d5c99 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -1,4 +1,4 @@ -*Note*: Before submitting this pull request, please review our [contributing guidelines](https://github.com/encode/django-rest-framework/blob/master/CONTRIBUTING.md#pull-requests). +*Note*: Before submitting this pull request, please review our [contributing guidelines](https://www.django-rest-framework.org/community/contributing/#pull-requests). ## Description From aa12a5f967705f70b1dbe457bb2396d106e3570b Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 5 Apr 2021 12:08:52 +0100 Subject: [PATCH 453/907] Lint with pre-commit (#7900) Following [my comment here](https://github.com/encode/django-rest-framework/pull/7589#issuecomment-813301322) and [Django's own move to pre-commit](https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#pre-commit-checks). * Add pre-commit config file to run flake8 and isort. * Add extra "common sense" hooks. * Run pre-commit on GitHub actions using the [official action](https://github.com/pre-commit/action/). This is a good way to get up-and-running but it would be better if we activated [pre-commit.ci](https://pre-commit.ci/), which is faster and will auto-update the hooks for us going forwards. * Remove `runtests.py` code for running linting tools. * Remove `runtests.py --fast` flag, since that would now just run `pytest -q`, which can be done with `runtests.py -q` instead. * Remove tox configuration and requirements files for linting. * Update the contributing guide to mention setting up pre-commit. --- .github/workflows/pre-commit.yml | 24 +++++++++ .gitignore | 3 +- .pre-commit-config.yaml | 20 +++++++ .travis.yml | 1 - docs/community/contributing.md | 22 ++++---- requirements.txt | 1 - requirements/requirements-codestyle.txt | 6 --- runtests.py | 70 +------------------------ tox.ini | 12 ++--- 9 files changed, 59 insertions(+), 100 deletions(-) create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .pre-commit-config.yaml delete mode 100644 requirements/requirements-codestyle.txt diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000000..9c29ed0564 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,24 @@ +name: pre-commit + +on: + push: + branches: + - master + pull_request: + +jobs: + pre-commit: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - uses: pre-commit/action@v2.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 82e885edee..7cb1eb249a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ MANIFEST coverage.* +!.github !.gitignore +!.pre-commit-config.yaml !.travis.yml -!.isort.cfg diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..0fc181b10c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.4.0 + hooks: + - id: check-added-large-files + - id: check-case-conflict + - id: check-json + - id: check-merge-conflict + - id: check-symlinks + - id: check-toml +- repo: https://github.com/pycqa/isort + rev: 5.8.0 + hooks: + - id: isort +- repo: https://gitlab.com/pycqa/flake8 + rev: 3.9.0 + hooks: + - id: flake8 + additional_dependencies: + - flake8-tidy-imports diff --git a/.travis.yml b/.travis.yml index 57a91e594a..244ab77fa3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,7 +27,6 @@ matrix: - { python: "3.9", env: DJANGO=main } - { python: "3.8", env: TOXENV=base } - - { python: "3.8", env: TOXENV=lint } - { python: "3.8", env: TOXENV=docs } - python: "3.8" diff --git a/docs/community/contributing.md b/docs/community/contributing.md index cb67100d2b..e220f95fc4 100644 --- a/docs/community/contributing.md +++ b/docs/community/contributing.md @@ -54,11 +54,19 @@ To start developing on Django REST framework, first create a Fork from the Then clone your fork. The clone command will look like this, with your GitHub username instead of YOUR-USERNAME: - git clone https://github.com/YOUR-USERNAME/Spoon-Knife + git clone https://github.com/YOUR-USERNAME/django-rest-framework See GitHub's [_Fork a Repo_][how-to-fork] Guide for more help. 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. +You can check your contributions against these conventions each time you commit using the [pre-commit](https://pre-commit.com/) hooks, which we also run on CI. +To set them up, first ensure you have the pre-commit tool installed, for example: + + python -m pip install pre-commit + +Then run: + + pre-commit install ## Testing @@ -79,18 +87,6 @@ Run using a more concise output style. ./runtests.py -q -Run the tests using a more concise output style, no coverage, no flake8. - - ./runtests.py --fast - -Don't run the flake8 code linting. - - ./runtests.py --nolint - -Only run the flake8 code linting, don't run the tests. - - ./runtests.py --lintonly - Run the tests for a given test case. ./runtests.py MyTestCase diff --git a/requirements.txt b/requirements.txt index b4e5ff5797..395f3b7a86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,5 +9,4 @@ -r requirements/requirements-optionals.txt -r requirements/requirements-testing.txt -r requirements/requirements-documentation.txt --r requirements/requirements-codestyle.txt -r requirements/requirements-packaging.txt diff --git a/requirements/requirements-codestyle.txt b/requirements/requirements-codestyle.txt deleted file mode 100644 index d9a93884c9..0000000000 --- a/requirements/requirements-codestyle.txt +++ /dev/null @@ -1,6 +0,0 @@ -# PEP8 code linting, which we run on all commits. -flake8>=3.8.4,<3.9 -flake8-tidy-imports>=4.1.0,<4.2 - -# Sort and lint imports -isort>=5.6.2,<6.0 diff --git a/runtests.py b/runtests.py index 82028ea32c..c340b55d86 100755 --- a/runtests.py +++ b/runtests.py @@ -1,42 +1,8 @@ #! /usr/bin/env python3 -import subprocess import sys import pytest -PYTEST_ARGS = { - 'default': [], - 'fast': ['-q'], -} - -FLAKE8_ARGS = ['rest_framework', 'tests'] - -ISORT_ARGS = ['--check-only', '--diff', 'rest_framework', 'tests'] - - -def exit_on_failure(ret, message=None): - if ret: - sys.exit(ret) - - -def flake8_main(args): - print('Running flake8 code linting') - ret = subprocess.call(['flake8'] + args) - print('flake8 failed' if ret else 'flake8 passed') - return ret - - -def isort_main(args): - print('Running isort code checking') - ret = subprocess.call(['isort'] + args) - - if ret: - print('isort failed: Some modules have incorrectly ordered imports. Fix by running `isort --recursive .`') - else: - print('isort passed') - - return ret - def split_class_and_function(string): class_string, function_string = string.split('.', 1) @@ -54,31 +20,6 @@ def is_class(string): if __name__ == "__main__": - try: - sys.argv.remove('--nolint') - except ValueError: - run_flake8 = True - run_isort = True - else: - run_flake8 = False - run_isort = False - - try: - sys.argv.remove('--lintonly') - except ValueError: - run_tests = True - else: - run_tests = False - - try: - sys.argv.remove('--fast') - except ValueError: - style = 'default' - else: - style = 'fast' - run_flake8 = False - run_isort = False - if len(sys.argv) > 1: pytest_args = sys.argv[1:] first_arg = pytest_args[0] @@ -104,14 +45,5 @@ def is_class(string): # `runtests.py TestCase [flags]` # `runtests.py test_function [flags]` pytest_args = ['tests', '-k', pytest_args[0]] + pytest_args[1:] - else: - pytest_args = PYTEST_ARGS[style] - - if run_tests: - exit_on_failure(pytest.main(pytest_args)) - - if run_flake8: - exit_on_failure(flake8_main(FLAKE8_ARGS)) - if run_isort: - exit_on_failure(isort_main(ISORT_ARGS)) + sys.exit(pytest.main(pytest_args)) diff --git a/tox.ini b/tox.ini index df16cf947f..fc44b52d21 100644 --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ envlist = {py36,py37,py38,py39}-django31, {py36,py37,py38,py39}-django32, {py38,py39}-djangomain, - base,dist,lint,docs, + base,dist,docs, [travis:env] DJANGO = @@ -16,7 +16,7 @@ DJANGO = main: djangomain [testenv] -commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning runtests.py --fast --coverage {posargs} +commands = python -W error::DeprecationWarning -W error::PendingDeprecationWarning runtests.py --coverage {posargs} envdir = {toxworkdir}/venvs/{envname} setenv = PYTHONDONTWRITEBYTECODE=1 @@ -37,18 +37,12 @@ deps = -rrequirements/requirements-testing.txt [testenv:dist] -commands = ./runtests.py --fast --no-pkgroot --staticfiles {posargs} +commands = ./runtests.py --no-pkgroot --staticfiles {posargs} deps = django -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt -[testenv:lint] -commands = ./runtests.py --lintonly -deps = - -rrequirements/requirements-codestyle.txt - -rrequirements/requirements-testing.txt - [testenv:docs] skip_install = true commands = mkdocs build From 37ef62b0e650dfa4cb61416ec646fd67ebe1d565 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 5 Apr 2021 18:18:35 +0100 Subject: [PATCH 454/907] Remove link to third party cookiecutter template (#7902) The template has not been maintained for six years, so it's out of date on versions and various "best practices" (e.g. pre-commit). I also think any template should be documented on its own repo rather than here, especially if it's not an official maintained project. --- docs/community/third-party-packages.md | 137 +------------------------ 1 file changed, 2 insertions(+), 135 deletions(-) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 046966594c..63a5c4f5f4 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -14,142 +14,9 @@ We aim to make creating third party packages as easy as possible, whilst keeping If you have an idea for a new feature please consider how it may be packaged as a Third Party Package. We're always happy to discuss ideas on the [Mailing List][discussion-group]. -## How to create a Third Party Package +## Creating a Third Party Package -### Creating your package - -You can use [this cookiecutter template][cookiecutter] for creating reusable Django REST Framework packages quickly. Cookiecutter creates projects from project templates. While optional, this cookiecutter template includes best practices from Django REST framework and other packages, as well as a Travis CI configuration, Tox configuration, and a sane setup.py for easy PyPI registration/distribution. - -Note: Let us know if you have an alternate cookiecutter package so we can also link to it. - -#### Running the initial cookiecutter command - -To run the initial cookiecutter command, you'll first need to install the Python `cookiecutter` package. - - $ pip install cookiecutter - -Once `cookiecutter` is installed just run the following to create a new project. - - $ cookiecutter gh:jpadilla/cookiecutter-django-rest-framework - -You'll be prompted for some questions, answer them, then it'll create your Python package in the current working directory based on those values. - - full_name (default is "Your full name here")? Johnny Appleseed - email (default is "you@example.com")? jappleseed@example.com - github_username (default is "yourname")? jappleseed - pypi_project_name (default is "dj-package")? djangorestframework-custom-auth - repo_name (default is "dj-package")? django-rest-framework-custom-auth - app_name (default is "djpackage")? custom_auth - project_short_description (default is "Your project description goes here")? - year (default is "2014")? - version (default is "0.1.0")? - -#### Getting it onto GitHub - -To put your project up on GitHub, you'll need a repository for it to live in. You can create a new repository [here][new-repo]. If you need help, check out the [Create A Repo][create-a-repo] article on GitHub. - - -#### Adding to Travis CI - -We recommend using [Travis CI][travis-ci], a hosted continuous integration service which integrates well with GitHub and is free for public repositories. - -To get started with Travis CI, [sign in][travis-ci] with your GitHub account. Once you're signed in, go to your [profile page][travis-profile] and enable the service hook for the repository you want. - -If you use the cookiecutter template, your project will already contain a `.travis.yml` file which Travis CI will use to build your project and run tests. By default, builds are triggered every time you push to your repository or create Pull Request. - -#### Uploading to PyPI - -Once you've got at least a prototype working and tests running, you should publish it on PyPI to allow others to install it via `pip`. - -You must [register][pypi-register] an account before publishing to PyPI. - -To register your package on PyPI run the following command. - - $ python setup.py register - -If this is the first time publishing to PyPI, you'll be prompted to login. - -Note: Before publishing you'll need to make sure you have the latest pip that supports `wheel` as well as install the `wheel` package. - - $ pip install --upgrade pip - $ pip install wheel - -After this, every time you want to release a new version on PyPI just run the following command. - - $ python setup.py publish - You probably want to also tag the version now: - git tag -a {0} -m 'version 0.1.0' - git push --tags - -After releasing a new version to PyPI, it's always a good idea to tag the version and make available as a GitHub Release. - -We recommend to follow [Semantic Versioning][semver] for your package's versions. - -### Development - -#### Version requirements - -The cookiecutter template assumes a set of supported versions will be provided for Python and Django. Make sure you correctly update your requirements, docs, `tox.ini`, `.travis.yml`, and `setup.py` to match the set of versions you wish to support. - -#### Tests - -The cookiecutter template includes a `runtests.py` which uses the `pytest` package as a test runner. - -Before running, you'll need to install a couple test requirements. - - $ pip install -r requirements.txt - -Once requirements installed, you can run `runtests.py`. - - $ ./runtests.py - -Run using a more concise output style. - - $ ./runtests.py -q - -Run the tests using a more concise output style, no coverage, no flake8. - - $ ./runtests.py --fast - -Don't run the flake8 code linting. - - $ ./runtests.py --nolint - -Only run the flake8 code linting, don't run the tests. - - $ ./runtests.py --lintonly - -Run the tests for a given test case. - - $ ./runtests.py MyTestCase - -Run the tests for a given test method. - - $ ./runtests.py MyTestCase.test_this_method - -Shorter form to run the tests for a given test method. - - $ ./runtests.py test_this_method - -To run your tests against multiple versions of Python as different versions of requirements such as Django we recommend using `tox`. [Tox][tox-docs] is a generic virtualenv management and test command line tool. - -First, install `tox` globally. - - $ pip install tox - -To run `tox`, just simply run: - - $ tox - -To run a particular `tox` environment: - - $ tox -e envlist - -`envlist` is a comma-separated value to that specifies the environments to run tests against. To view a list of all possible test environments, run: - - $ tox -l - -#### Version compatibility +### Version compatibility Sometimes, in order to ensure your code works on various different versions of Django, Python or third party libraries, you'll need to run slightly different code depending on the environment. Any code that branches in this way should be isolated into a `compat.py` module, and should provide a single common interface that the rest of the codebase can use. From 90635c138f073f617516a5733946acb63254a1cf Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 6 Apr 2021 17:49:17 +0100 Subject: [PATCH 455/907] Update pre-commit for flake8 move (#7907) See: https://twitter.com/codewithanthony/status/1378746934928699396 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0fc181b10c..5a6e554b98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: rev: 5.8.0 hooks: - id: isort -- repo: https://gitlab.com/pycqa/flake8 +- repo: https://github.com/PyCQA/flake8 rev: 3.9.0 hooks: - id: flake8 From fd017d00f938c6629d2eeb1b4d81716dff6d006e Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Tue, 6 Apr 2021 18:34:18 +0100 Subject: [PATCH 456/907] Move CI to GitHub Actions (#7903) * Recreate all the jobs on GitHub Actions * Upgrade to Ubuntu 20.04 * Upgrade base/docs/dist to Python 3.9 --- .github/workflows/main.yml | 57 ++++++++++++++++++++++++++++++++++++++ .travis.yml | 55 ------------------------------------ tox.ini | 18 ++++++++++++ 3 files changed, 75 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..6686ce7593 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,57 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +jobs: + tests: + name: Python ${{ matrix.python-version }} + runs-on: ubuntu-20.04 + + strategy: + matrix: + python-version: + - '3.6' + - '3.7' + - '3.8' + - '3.9' + + steps: + - uses: actions/checkout@v2 + + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements/*.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + + - name: Upgrade packaging tools + run: python -m pip install --upgrade pip setuptools virtualenv wheel + + - name: Install dependencies + run: python -m pip install --upgrade codecov tox + + - name: Run tox targets for ${{ matrix.python-version }} + run: | + ENV_PREFIX=$(tr -C -d "0-9" <<< "${{ matrix.python-version }}") + TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') tox + + - name: Run extra tox targets + if: ${{ matrix.python-version == '3.9' }} + run: | + python setup.py bdist_wheel + rm -r djangorestframework.egg-info # see #6139 + tox -e base,dist,docs + tox -e dist --installpkg ./dist/djangorestframework-*.whl + + - name: Upload coverage + run: | + codecov -e TOXENV,DJANGO diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 244ab77fa3..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,55 +0,0 @@ -language: python -cache: pip -dist: bionic -matrix: - fast_finish: true - include: - - - { python: "3.5", env: DJANGO=2.2 } - - - { python: "3.6", env: DJANGO=2.2 } - - { python: "3.6", env: DJANGO=3.0 } - - { python: "3.6", env: DJANGO=3.1 } - - { python: "3.6", env: DJANGO=3.2 } - - - { python: "3.7", env: DJANGO=2.2 } - - { python: "3.7", env: DJANGO=3.0 } - - { python: "3.7", env: DJANGO=3.1 } - - { python: "3.7", env: DJANGO=3.2 } - - - { python: "3.8", env: DJANGO=3.0 } - - { python: "3.8", env: DJANGO=3.1 } - - { python: "3.8", env: DJANGO=3.2 } - - { python: "3.8", env: DJANGO=main } - - - { python: "3.9", env: DJANGO=3.1 } - - { python: "3.9", env: DJANGO=3.2 } - - { python: "3.9", env: DJANGO=main } - - - { python: "3.8", env: TOXENV=base } - - { python: "3.8", env: TOXENV=docs } - - - python: "3.8" - env: TOXENV=dist - script: - - python setup.py bdist_wheel - - rm -r djangorestframework.egg-info # see #6139 - - tox --installpkg ./dist/djangorestframework-*.whl - - tox # test sdist - - allow_failures: - - env: DJANGO=main - - env: DJANGO=3.2 - -install: - - pip install tox tox-travis - -script: - - tox - -after_success: - - pip install codecov - - codecov -e TOXENV,DJANGO - -notifications: - email: false diff --git a/tox.ini b/tox.ini index fc44b52d21..bf4de90d03 100644 --- a/tox.ini +++ b/tox.ini @@ -49,3 +49,21 @@ commands = mkdocs build deps = -rrequirements/requirements-testing.txt -rrequirements/requirements-documentation.txt + +[testenv:py36-django32] +ignore_outcome = true + +[testenv:py37-django32] +ignore_outcome = true + +[testenv:py38-django32] +ignore_outcome = true + +[testenv:py39-django32] +ignore_outcome = true + +[testenv:py38-djangomain] +ignore_outcome = true + +[testenv:py39-djangomain] +ignore_outcome = true From 9bdd6125a1fb1a0c429ce3ee0b68fe5d409fd1fe Mon Sep 17 00:00:00 2001 From: Lalit Suthar Date: Mon, 12 Apr 2021 16:29:58 +0530 Subject: [PATCH 457/907] fix broken article link (#7918) Co-authored-by: lalit97 --- docs/community/tutorials-and-resources.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/tutorials-and-resources.md b/docs/community/tutorials-and-resources.md index cfd3ba852e..dae292f50c 100644 --- a/docs/community/tutorials-and-resources.md +++ b/docs/community/tutorials-and-resources.md @@ -95,7 +95,7 @@ Want your Django REST Framework talk/tutorial/article to be added to our website [ember-and-django-part 1-video]: http://www.neckbeardrepublic.com/screencasts/ember-and-django-part-1 [django-rest-framework-part-1-video]: http://www.neckbeardrepublic.com/screencasts/django-rest-framework-part-1 [web-api-performance-profiling-django-rest-framework]: https://www.dabapps.com/blog/api-performance-profiling-django-rest-framework/ -[api-development-with-django-and-django-rest-framework]: https://bnotions.com/api-development-with-django-and-django-rest-framework/ +[api-development-with-django-and-django-rest-framework]: https://bnotions.com/news-and-insights/api-development-with-django-and-django-rest-framework/ [cdrf.co]:http://www.cdrf.co [medium-django-rest-framework]: https://medium.com/django-rest-framework [django-rest-framework-course]: https://teamtreehouse.com/library/django-rest-framework From 1c494e3d944796bef5ec27348a617afeaad792b9 Mon Sep 17 00:00:00 2001 From: Terence Honles Date: Mon, 12 Apr 2021 05:14:26 -0700 Subject: [PATCH 458/907] Update references to Travis CI after moving to Github Actions (#7909) x-ref: https://github.com/encode/django-rest-framework/pull/7903 --- .gitignore | 1 - README.md | 6 +++--- docs/community/contributing.md | 8 ++++---- docs/community/third-party-packages.md | 2 -- docs/img/build-status.png | Bin 0 -> 12443 bytes docs/img/travis-status.png | Bin 10023 -> 0 bytes docs/index.md | 4 ++-- docs_theme/css/default.css | 2 +- 8 files changed, 10 insertions(+), 13 deletions(-) create mode 100644 docs/img/build-status.png delete mode 100644 docs/img/travis-status.png diff --git a/.gitignore b/.gitignore index 7cb1eb249a..641714d163 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,3 @@ coverage.* !.github !.gitignore !.pre-commit-config.yaml -!.travis.yml diff --git a/README.md b/README.md index 305f923898..ff76a5525d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # [Django REST framework][docs] -[![build-status-image]][travis] +[![build-status-image]][build-status] [![coverage-status-image]][codecov] [![pypi-version]][pypi] @@ -176,8 +176,8 @@ You may also want to [follow the author on Twitter][twitter]. Please see the [security policy][security-policy]. -[build-status-image]: https://secure.travis-ci.org/encode/django-rest-framework.svg?branch=master -[travis]: https://travis-ci.org/encode/django-rest-framework?branch=master +[build-status-image]: https://github.com/encode/django-rest-framework/actions/workflows/main.yml/badge.svg +[build-status]: https://github.com/encode/django-rest-framework/actions/workflows/main.yml [coverage-status-image]: https://img.shields.io/codecov/c/github/encode/django-rest-framework/master.svg [codecov]: https://codecov.io/github/encode/django-rest-framework?branch=master [pypi-version]: https://img.shields.io/pypi/v/djangorestframework.svg diff --git a/docs/community/contributing.md b/docs/community/contributing.md index e220f95fc4..de1f8db0fb 100644 --- a/docs/community/contributing.md +++ b/docs/community/contributing.md @@ -119,11 +119,11 @@ GitHub's documentation for working on pull requests is [available here][pull-req Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible on all supported versions of Python and Django. -Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect. +Once you've made a pull request take a look at the build status in the GitHub interface and make sure the tests are running as you'd expect. -![Travis status][travis-status] +![Build status][build-status] -*Above: Travis build notifications* +*Above: build notifications* ## Managing compatibility issues @@ -206,7 +206,7 @@ If you want to draw attention to a note or warning, use a pair of enclosing line [so-filter]: https://stackexchange.com/filters/66475/rest-framework [issues]: https://github.com/encode/django-rest-framework/issues?state=open [pep-8]: https://www.python.org/dev/peps/pep-0008/ -[travis-status]: ../img/travis-status.png +[build-status]: ../img/build-status.png [pull-requests]: https://help.github.com/articles/using-pull-requests [tox]: https://tox.readthedocs.io/en/latest/ [markdown]: https://daringfireball.net/projects/markdown/basics diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 63a5c4f5f4..e53fc3d50c 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -152,8 +152,6 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework [new-repo]: https://github.com/new [create-a-repo]: https://help.github.com/articles/create-a-repo/ -[travis-ci]: https://travis-ci.org -[travis-profile]: https://travis-ci.org/profile [pypi-register]: https://pypi.org/account/register/ [semver]: https://semver.org/ [tox-docs]: https://tox.readthedocs.io/en/latest/ diff --git a/docs/img/build-status.png b/docs/img/build-status.png new file mode 100644 index 0000000000000000000000000000000000000000..bb043cb9e957085d8f693138b9625dc6095439fc GIT binary patch literal 12443 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 literal 0 HcmV?d00001 diff --git a/docs/img/travis-status.png b/docs/img/travis-status.png deleted file mode 100644 index fec98cf9b2ba728a532df7b50b89f505053e9012..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10023 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 - - + + diff --git a/docs_theme/css/default.css b/docs_theme/css/default.css index 992bc60a42..7006f2a668 100644 --- a/docs_theme/css/default.css +++ b/docs_theme/css/default.css @@ -37,7 +37,7 @@ body.index-page #main-content iframe.github-star-button { margin-right: -15px; } -/* Travis CI and PyPI badge */ +/* CI and PyPI badge */ body.index-page #main-content img.status-badge { float: right; margin-right: 8px; From f628db383a1cb47e7910ac2547d4dd53f0902211 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 16 Apr 2021 17:23:18 +0100 Subject: [PATCH 459/907] Fix GitHub Actions to run on 'master' branch (#7926) The config I copied in #7903 was from a repo with the new name 'main', so tests have not been running on master since. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 6686ce7593..1c9e49e348 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,7 @@ name: CI on: push: branches: - - main + - master pull_request: jobs: From a0a2c5cb370ff80a95deaa8d23f099acc4e5e0c5 Mon Sep 17 00:00:00 2001 From: Terence Honles Date: Fri, 16 Apr 2021 09:27:22 -0700 Subject: [PATCH 460/907] Fix tests with mock timezone (#7911) After django/django#13877, Django no longer checks for `hasattr(timezone, 'localize')` and instead does an inheritance check. --- tests/test_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 5842553f02..78a9effb8c 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1485,7 +1485,7 @@ class TestNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues): } outputs = {} - class MockTimezone: + class MockTimezone(pytz.BaseTzInfo): @staticmethod def localize(value, is_dst): raise pytz.InvalidTimeError() From 67b5093ca526d219b8f25abf427161e154c23c6e Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 16 Apr 2021 17:47:21 +0100 Subject: [PATCH 461/907] Fix pytest warnings (#7928) * Use `--strict-markers` instead of `--strict`, as per this warning: ``` /.../_pytest/config/__init__.py:1183: PytestDeprecationWarning: The --strict option is deprecated, use --strict-markers instead. ``` * Remove config option 'testspath' - pytest is logging a warning about this being unknown: ``` /.../_pytest/config/__init__.py:1233: PytestConfigWarning: Unknown config option: testspath ``` I can't find any reference to it in the pytest docs or changelog. --- setup.cfg | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index abb7cca908..46ffb13c52 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,8 +2,7 @@ license_file = LICENSE.md [tool:pytest] -addopts=--tb=short --strict -ra -testspath = tests +addopts=--tb=short --strict-markers -ra [flake8] ignore = E501,W504 From 010c8d4f084c1c3c5f712e731351604f301d6906 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 16 Apr 2021 17:59:27 +0100 Subject: [PATCH 462/907] Use tox-py in CI (#7925) --- .github/workflows/main.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1c9e49e348..fc166c434d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,12 +37,10 @@ jobs: run: python -m pip install --upgrade pip setuptools virtualenv wheel - name: Install dependencies - run: python -m pip install --upgrade codecov tox + run: python -m pip install --upgrade codecov tox tox-py - name: Run tox targets for ${{ matrix.python-version }} - run: | - ENV_PREFIX=$(tr -C -d "0-9" <<< "${{ matrix.python-version }}") - TOXENV=$(tox --listenvs | grep "^py$ENV_PREFIX" | tr '\n' ',') tox + run: tox --py current - name: Run extra tox targets if: ${{ matrix.python-version == '3.9' }} From 8812394ed83d7cce0ed5b2c5fcf093269d364b9b Mon Sep 17 00:00:00 2001 From: Denis Orehovsky Date: Tue, 20 Apr 2021 17:03:16 +0300 Subject: [PATCH 463/907] Add distinction between request and response serializers for OpenAPI (#7424) * Add distinction between request and response serializers * Add docs * document new functions in schemas.md * add a test case for different request vs response objects * Correct formatting for flake8 Co-authored-by: Shaun Gosse --- docs/api-guide/schemas.md | 14 +++++ rest_framework/schemas/openapi.py | 37 +++++++++++--- tests/schemas/test_openapi.py | 85 +++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 8 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index b4832b3690..acf2ecb932 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -375,6 +375,20 @@ operationIds. In order to work around this, you can override `get_operation_id_base()` to provide a different base for name part of the ID. +#### `get_serializer()` + +If the view has implemented `get_serializer()`, returns the result. + +#### `get_request_serializer()` + +By default returns `get_serializer()` but can be overridden to +differentiate between request and response objects. + +#### `get_response_serializer()` + +By default returns `get_serializer()` but can be overridden to +differentiate between request and response objects. + ### `AutoSchema.__init__()` kwargs `AutoSchema` provides a number of `__init__()` kwargs that can be used for diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 4ecb7a65f1..5e9d59f8bf 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -192,15 +192,22 @@ def get_components(self, path, method): if method.lower() == 'delete': return {} - serializer = self.get_serializer(path, method) + request_serializer = self.get_request_serializer(path, method) + response_serializer = self.get_response_serializer(path, method) - if not isinstance(serializer, serializers.Serializer): - return {} + components = {} + + if isinstance(request_serializer, serializers.Serializer): + component_name = self.get_component_name(request_serializer) + content = self.map_serializer(request_serializer) + components.setdefault(component_name, content) - component_name = self.get_component_name(serializer) + if isinstance(response_serializer, serializers.Serializer): + component_name = self.get_component_name(response_serializer) + content = self.map_serializer(response_serializer) + components.setdefault(component_name, content) - content = self.map_serializer(serializer) - return {component_name: content} + return components def _to_camel_case(self, snake_str): components = snake_str.split('_') @@ -615,6 +622,20 @@ def get_serializer(self, path, method): .format(view.__class__.__name__, method, path)) return None + def get_request_serializer(self, path, method): + """ + Override this method if your view uses a different serializer for + handling request body. + """ + return self.get_serializer(path, method) + + def get_response_serializer(self, path, method): + """ + Override this method if your view uses a different serializer for + populating response data. + """ + return self.get_serializer(path, method) + def _get_reference(self, serializer): return {'$ref': '#/components/schemas/{}'.format(self.get_component_name(serializer))} @@ -624,7 +645,7 @@ def get_request_body(self, path, method): self.request_media_types = self.map_parsers(path, method) - serializer = self.get_serializer(path, method) + serializer = self.get_request_serializer(path, method) if not isinstance(serializer, serializers.Serializer): item_schema = {} @@ -648,7 +669,7 @@ def get_responses(self, path, method): self.response_media_types = self.map_renderers(path, method) - serializer = self.get_serializer(path, method) + serializer = self.get_response_serializer(path, method) if not isinstance(serializer, serializers.Serializer): item_schema = {} diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 871eb1b302..aef20670e6 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -712,6 +712,91 @@ def get_operation_id_base(self, path, method, action): operationId = inspector.get_operation_id(path, method) assert operationId == 'listItem' + def test_different_request_response_objects(self): + class RequestSerializer(serializers.Serializer): + text = serializers.CharField() + + class ResponseSerializer(serializers.Serializer): + text = serializers.BooleanField() + + class CustomSchema(AutoSchema): + def get_request_serializer(self, path, method): + return RequestSerializer() + + def get_response_serializer(self, path, method): + return ResponseSerializer() + + path = '/' + method = 'POST' + view = create_view( + views.ExampleGenericAPIView, + method, + create_request(path), + ) + inspector = CustomSchema() + inspector.view = view + + components = inspector.get_components(path, method) + assert components == { + 'Request': { + 'properties': { + 'text': { + 'type': 'string' + } + }, + 'required': ['text'], + 'type': 'object' + }, + 'Response': { + 'properties': { + 'text': { + 'type': 'boolean' + } + }, + 'required': ['text'], + 'type': 'object' + } + } + + operation = inspector.get_operation(path, method) + assert operation == { + 'operationId': 'createExample', + 'description': '', + 'parameters': [], + 'requestBody': { + 'content': { + 'application/json': { + 'schema': { + '$ref': '#/components/schemas/Request' + } + }, + 'application/x-www-form-urlencoded': { + 'schema': { + '$ref': '#/components/schemas/Request' + } + }, + 'multipart/form-data': { + 'schema': { + '$ref': '#/components/schemas/Request' + } + } + } + }, + 'responses': { + '201': { + 'content': { + 'application/json': { + 'schema': { + '$ref': '#/components/schemas/Response' + } + } + }, + 'description': '' + } + }, + 'tags': [''] + } + def test_repeat_operation_ids(self): router = routers.SimpleRouter() router.register('account', views.ExampleGenericViewSet, basename="account") From 431f7dfa3dc108d449044a6c9eef715416d53059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Skar=C5=BCy=C5=84ski?= Date: Fri, 23 Apr 2021 10:34:58 +0200 Subject: [PATCH 464/907] fix typo in packaging requirements (#7949) --- requirements/requirements-packaging.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-packaging.txt b/requirements/requirements-packaging.txt index 3489c76ec0..fae03baab5 100644 --- a/requirements/requirements-packaging.txt +++ b/requirements/requirements-packaging.txt @@ -5,4 +5,4 @@ wheel>=0.35.1,<0.36 twine>=3.2.0,<3.3 # Transifex client for managing translation resources. -transifex-clien>=0.13.12,<0.14 +transifex-client>=0.13.12,<0.14 From a0083f7f9867113a37a5096a06ee69344781075a Mon Sep 17 00:00:00 2001 From: Julien Palard Date: Mon, 26 Apr 2021 10:30:41 +0200 Subject: [PATCH 465/907] FIX: Broken cite. (#7951) --- docs/api-guide/schemas.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index acf2ecb932..b9de6745fe 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -421,6 +421,7 @@ If your views have related customizations that are needed frequently, you can create a base `AutoSchema` subclass for your project that takes additional `__init__()` kwargs to save subclassing `AutoSchema` for each view. +[cite]: https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api [openapi]: https://github.com/OAI/OpenAPI-Specification [openapi-specification-extensions]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#specification-extensions [openapi-operation]: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#operationObject From 3875d3284e73ed4d8e36c07d9b70c1b22c9d5998 Mon Sep 17 00:00:00 2001 From: Ishu Kumar Date: Mon, 10 May 2021 16:56:26 +0530 Subject: [PATCH 466/907] Punctuations and missing "to" preposition (#7966) Changes made in lines 221, 222, 223, and 224 for better readability. --- docs/api-guide/pagination.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 8d9eb22881..632af6a823 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -218,10 +218,10 @@ To set these attributes you should override the `CursorPagination` class, and th # Custom pagination styles -To create a custom pagination serializer class you should subclass `pagination.BasePagination` and override the `paginate_queryset(self, queryset, request, view=None)` and `get_paginated_response(self, data)` methods: +To create a custom pagination serializer class, you should inherit the subclass `pagination.BasePagination`, override the `paginate_queryset(self, queryset, request, view=None)`, and `get_paginated_response(self, data)` methods: -* The `paginate_queryset` method is passed the initial queryset and should return an iterable object that contains only the data in the requested page. -* The `get_paginated_response` method is passed the serialized page data and should return a `Response` instance. +* The `paginate_queryset` method is passed to the initial queryset and should return an iterable object. That object contains only the data in the requested page. +* The `get_paginated_response` method is passed to the serialized page data and should return a `Response` instance. Note that the `paginate_queryset` method may set state on the pagination instance, that may later be used by the `get_paginated_response` method. From bc075212cb05a52a2b2b2b4c909cfbd03c7ebd8e Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 24 May 2021 09:47:44 +0200 Subject: [PATCH 467/907] Fix running runtests.py without arguments. (#7954) Regression in aa12a5f967705f70b1dbe457bb2396d106e3570b. --- runtests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/runtests.py b/runtests.py index c340b55d86..98f34c0673 100755 --- a/runtests.py +++ b/runtests.py @@ -45,5 +45,7 @@ def is_class(string): # `runtests.py TestCase [flags]` # `runtests.py test_function [flags]` pytest_args = ['tests', '-k', pytest_args[0]] + pytest_args[1:] + else: + pytest_args = [] sys.exit(pytest.main(pytest_args)) From 9d149f23177055b3b1ea12cf62de0d669739b544 Mon Sep 17 00:00:00 2001 From: Abduaziz <68025869+AbduazizZiyodov@users.noreply.github.com> Date: Wed, 2 Jun 2021 14:02:11 +0500 Subject: [PATCH 468/907] Fixed some punctuation marks & small typos (#8015) --- docs/api-guide/authentication.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 4497f73bd0..60544079f1 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -11,7 +11,7 @@ source: Authentication is the mechanism of associating an incoming request with a set of identifying credentials, such as the user the request came from, or the token that it was signed with. The [permission] and [throttling] policies can then use those credentials to determine if the request should be permitted. -REST framework provides a number of authentication schemes out of the box, and also allows you to implement custom schemes. +REST framework provides several authentication schemes out of the box, and also allows you to implement custom schemes. Authentication is always run at the very start of the view, before the permission and throttling checks occur, and before any other code is allowed to proceed. @@ -23,7 +23,7 @@ The `request.auth` property is used for any additional authentication informatio **Note:** Don't forget that **authentication by itself won't allow or disallow an incoming request**, it simply identifies the credentials that the request was made with. -For information on how to setup the permission polices for your API please see the [permissions documentation][permission]. +For information on how to set up the permission policies for your API please see the [permissions documentation][permission]. --- @@ -195,7 +195,7 @@ If you've already created some users, you can generate tokens for all existing u ##### By exposing an api endpoint -When using `TokenAuthentication`, you may want to provide a mechanism for clients to obtain a token given the username and password. REST framework provides a built-in view to provide this behavior. To use it, add the `obtain_auth_token` view to your URLconf: +When using `TokenAuthentication`, you may want to provide a mechanism for clients to obtain a token given the username and password. REST framework provides a built-in view to provide this behaviour. To use it, add the `obtain_auth_token` view to your URLconf: from rest_framework.authtoken import views urlpatterns += [ @@ -210,7 +210,7 @@ The `obtain_auth_token` view will return a JSON response when valid `username` a Note that the default `obtain_auth_token` view explicitly uses JSON requests and responses, rather than using default renderer and parser classes in your settings. -By default there are no permissions or throttling applied to the `obtain_auth_token` view. If you do wish to apply throttling you'll need to override the view class, +By default, there are no permissions or throttling applied to the `obtain_auth_token` view. If you do wish to apply to throttle you'll need to override the view class, and include them using the `throttle_classes` attribute. If you need a customized version of the `obtain_auth_token` view, you can do so by subclassing the `ObtainAuthToken` view class, and using that in your url conf instead. @@ -244,7 +244,7 @@ And in your `urls.py`: ##### With Django admin -It is also possible to create Tokens manually through admin interface. In case you are using a large user base, we recommend that you monkey patch the `TokenAdmin` class to customize it to your needs, more specifically by declaring the `user` field as `raw_field`. +It is also possible to create Tokens manually through the admin interface. In case you are using a large user base, we recommend that you monkey patch the `TokenAdmin` class customize it to your needs, more specifically by declaring the `user` field as `raw_field`. `your_app/admin.py`: @@ -279,11 +279,11 @@ If successfully authenticated, `SessionAuthentication` provides the following cr Unauthenticated responses that are denied permission will result in an `HTTP 403 Forbidden` response. -If you're using an AJAX style API with SessionAuthentication, you'll need to make sure you include a valid CSRF token for any "unsafe" HTTP method calls, such as `PUT`, `PATCH`, `POST` or `DELETE` requests. See the [Django CSRF documentation][csrf-ajax] for more details. +If you're using an AJAX-style API with SessionAuthentication, you'll need to make sure you include a valid CSRF token for any "unsafe" HTTP method calls, such as `PUT`, `PATCH`, `POST` or `DELETE` requests. See the [Django CSRF documentation][csrf-ajax] for more details. **Warning**: Always use Django's standard login view when creating login pages. This will ensure your login views are properly protected. -CSRF validation in REST framework works slightly differently to standard Django due to the need to support both session and non-session based authentication to the same views. This means that only authenticated requests require CSRF tokens, and anonymous requests may be sent without CSRF tokens. This behaviour is not suitable for login views, which should always have CSRF validation applied. +CSRF validation in REST framework works slightly differently from standard Django due to the need to support both session and non-session based authentication to the same views. This means that only authenticated requests require CSRF tokens, and anonymous requests may be sent without CSRF tokens. This behaviour is not suitable for login views, which should always have CSRF validation applied. ## RemoteUserAuthentication @@ -316,7 +316,7 @@ In some circumstances instead of returning `None`, you may want to raise an `Aut Typically the approach you should take is: * If authentication is not attempted, return `None`. Any other authentication schemes also in use will still be checked. -* If authentication is attempted but fails, raise a `AuthenticationFailed` exception. An error response will be returned immediately, regardless of any permissions checks, and without checking any other authentication schemes. +* If authentication is attempted but fails, raise an `AuthenticationFailed` exception. An error response will be returned immediately, regardless of any permissions checks, and without checking any other authentication schemes. You *may* also override the `.authenticate_header(self, request)` method. If implemented, it should return a string that will be used as the value of the `WWW-Authenticate` header in a `HTTP 401 Unauthorized` response. @@ -353,7 +353,7 @@ The following example will authenticate any incoming request as the user given b # Third party packages -The following third party packages are also available. +The following third-party packages are also available. ## Django OAuth Toolkit @@ -384,7 +384,7 @@ For more details see the [Django REST framework - Getting started][django-oauth- The [Django REST framework OAuth][django-rest-framework-oauth] package provides both OAuth1 and OAuth2 support for REST framework. -This package was previously included directly in REST framework but is now supported and maintained as a third party package. +This package was previously included directly in the REST framework but is now supported and maintained as a third-party package. #### Installation & configuration @@ -408,7 +408,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a ## Djoser -[Djoser][djoser] library provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. The package works with a custom user model and it uses token based authentication. This is a ready to use REST implementation of Django authentication system. +[Djoser][djoser] library provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. The package works with a custom user model and uses token-based authentication. This is ready to use REST implementation of the Django authentication system. ## django-rest-auth / dj-rest-auth @@ -426,15 +426,15 @@ There are currently two forks of this project. ## django-rest-knox -[Django-rest-knox][django-rest-knox] library provides models and views to handle token based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into). +[Django-rest-knox][django-rest-knox] library provides models and views to handle token-based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into). ## drfpasswordless -[drfpasswordless][drfpasswordless] adds (Medium, Square Cash inspired) passwordless support to Django REST Framework's own TokenAuthentication scheme. Users log in and sign up with a token sent to a contact point like an email address or a mobile number. +[drfpasswordless][drfpasswordless] adds (Medium, Square Cash inspired) passwordless support to Django REST Framework's TokenAuthentication scheme. Users log in and sign up with a token sent to a contact point like an email address or a mobile number. ## django-rest-authemail -[django-rest-authemail][django-rest-authemail] provides a RESTful API interface for user signup and authentication. Email addresses are used for authentication, rather than usernames. API endpoints are available for signup, signup email verification, login, logout, password reset, password reset verification, email change, email change verification, password change, and user detail. A fully-functional example project and detailed instructions are included. +[django-rest-authemail][django-rest-authemail] provides a RESTful API interface for user signup and authentication. Email addresses are used for authentication, rather than usernames. API endpoints are available for signup, signup email verification, login, logout, password reset, password reset verification, email change, email change verification, password change, and user detail. A fully functional example project and detailed instructions are included. ## Django-Rest-Durin From 61e7a993bd0702d30e3049179000bc7c5f284781 Mon Sep 17 00:00:00 2001 From: Ian De Bie Date: Mon, 7 Jun 2021 04:30:23 -0500 Subject: [PATCH 469/907] fix comments by using correct css comment syntax (#8019) these intended comments were causing errors in sonarqube scans due to using wrong css comment syntax --- rest_framework/static/rest_framework/docs/css/base.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest_framework/static/rest_framework/docs/css/base.css b/rest_framework/static/rest_framework/docs/css/base.css index 0be2bafa91..06b240c522 100644 --- a/rest_framework/static/rest_framework/docs/css/base.css +++ b/rest_framework/static/rest_framework/docs/css/base.css @@ -7,15 +7,15 @@ h1 { } pre.highlight code * { - white-space: nowrap; // this sets all children inside to nowrap + white-space: nowrap; /* this sets all children inside to nowrap */ } pre.highlight { - overflow-x: auto; // this sets the scrolling in x + overflow-x: auto; /* this sets the scrolling in x */ } pre.highlight code { - white-space: pre; // forces to respect
     formatting
    +  white-space: pre;       /* forces  to respect 
     formatting */
     }
     
     .main-container {
    
    From 24a938abaadd98b5482bec33defd285625842342 Mon Sep 17 00:00:00 2001
    From: Finn Gundlach 
    Date: Wed, 16 Jun 2021 15:53:29 +0200
    Subject: [PATCH 470/907] Update documentation to include Django 3.2 as
     supported version (#8037)
    
    ---
     README.md     | 2 +-
     docs/index.md | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/README.md b/README.md
    index ff76a5525d..fce275256e 100644
    --- a/README.md
    +++ b/README.md
    @@ -53,7 +53,7 @@ There is a live example API for testing purposes, [available here][sandbox].
     # Requirements
     
     * Python (3.5, 3.6, 3.7, 3.8, 3.9)
    -* Django (2.2, 3.0, 3.1)
    +* Django (2.2, 3.0, 3.1, 3.2)
     
     We **highly recommend** and only officially support the latest patch release of
     each Python and Django series.
    diff --git a/docs/index.md b/docs/index.md
    index 530813684e..28e3302501 100644
    --- a/docs/index.md
    +++ b/docs/index.md
    @@ -84,7 +84,7 @@ continued development by **[signing up for a paid plan][funding]**.
     REST framework requires the following:
     
     * Python (3.5, 3.6, 3.7, 3.8, 3.9)
    -* Django (2.2, 3.0, 3.1)
    +* Django (2.2, 3.0, 3.1, 3.2)
     
     We **highly recommend** and only officially support the latest patch release of
     each Python and Django series.
    
    From e92016ac2e926483e05e296558fc3d1ea3279625 Mon Sep 17 00:00:00 2001
    From: Adam Johnson 
    Date: Mon, 21 Jun 2021 11:33:43 +0100
    Subject: [PATCH 471/907] Stop ignoring test outcome for Django 3.2 (#7927)
    
    ---
     requirements/requirements-optionals.txt |  2 +-
     setup.py                                |  1 +
     tox.ini                                 | 12 ------------
     3 files changed, 2 insertions(+), 13 deletions(-)
    
    diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt
    index 4cb0e54f4b..75b9ab4d60 100644
    --- a/requirements/requirements-optionals.txt
    +++ b/requirements/requirements-optionals.txt
    @@ -2,7 +2,7 @@
     coreapi==2.3.1
     coreschema==0.0.4
     django-filter>=2.4.0,<3.0
    -django-guardian>=2.3.0,<2.4
    +django-guardian>=2.4.0,<2.5
     markdown==3.3;python_version>="3.6"
     markdown==3.2.2;python_version=="3.5"
     psycopg2-binary>=2.8.5,<2.9
    diff --git a/setup.py b/setup.py
    index e2a1c0222c..5fd4df20db 100755
    --- a/setup.py
    +++ b/setup.py
    @@ -92,6 +92,7 @@ def get_version(package):
             'Framework :: Django :: 2.2',
             'Framework :: Django :: 3.0',
             'Framework :: Django :: 3.1',
    +        'Framework :: Django :: 3.2',
             'Intended Audience :: Developers',
             'License :: OSI Approved :: BSD License',
             'Operating System :: OS Independent',
    diff --git a/tox.ini b/tox.ini
    index bf4de90d03..f23486a685 100644
    --- a/tox.ini
    +++ b/tox.ini
    @@ -50,18 +50,6 @@ deps =
            -rrequirements/requirements-testing.txt
            -rrequirements/requirements-documentation.txt
     
    -[testenv:py36-django32]
    -ignore_outcome = true
    -
    -[testenv:py37-django32]
    -ignore_outcome = true
    -
    -[testenv:py38-django32]
    -ignore_outcome = true
    -
    -[testenv:py39-django32]
    -ignore_outcome = true
    -
     [testenv:py38-djangomain]
     ignore_outcome = true
     
    
    From c8a9c856c25a1a360a91d2c7bc11e0dacfb9c3a4 Mon Sep 17 00:00:00 2001
    From: Burak Kadir Er 
    Date: Mon, 28 Jun 2021 14:51:21 +0300
    Subject: [PATCH 472/907] fix a small typo (#8060)
    
    ---
     docs/api-guide/renderers.md | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md
    index 954fb3bb98..7dbc5eee8f 100644
    --- a/docs/api-guide/renderers.md
    +++ b/docs/api-guide/renderers.md
    @@ -105,7 +105,7 @@ The TemplateHTMLRenderer will create a `RequestContext`, using the `response.dat
     
     ---
     
    -**Note:** When used with a view that makes use of a serializer the `Response` sent for rendering may not be a dictionay and will need to be wrapped in a dict before returning to allow the TemplateHTMLRenderer to render it. For example:
    +**Note:** When used with a view that makes use of a serializer the `Response` sent for rendering may not be a dictionary and will need to be wrapped in a dict before returning to allow the TemplateHTMLRenderer to render it. For example:
     
     ```
     response.data = {'results': response.data}
    
    From d2977cff989f9b14f402ecf1e9235ee3d110977b Mon Sep 17 00:00:00 2001
    From: Nikita Sobolev 
    Date: Mon, 28 Jun 2021 15:07:41 +0300
    Subject: [PATCH 473/907] Fixes inconsistent headers in `serializer` docs
     (#8056)
    
    Some headers were using `.`, some - were not.
    Now, all of them are the same with `.`, because it was easier to fix.
    ---
     docs/api-guide/serializers.md | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
    index f05fe7e7e9..13c0c87104 100644
    --- a/docs/api-guide/serializers.md
    +++ b/docs/api-guide/serializers.md
    @@ -605,13 +605,13 @@ For `ModelSerializer` this defaults to `PrimaryKeyRelatedField`.
     
     For `HyperlinkedModelSerializer` this defaults to `serializers.HyperlinkedRelatedField`.
     
    -### `serializer_url_field`
    +### `.serializer_url_field`
     
     The serializer field class that should be used for any `url` field on the serializer.
     
     Defaults to `serializers.HyperlinkedIdentityField`
     
    -### `serializer_choice_field`
    +### `.serializer_choice_field`
     
     The serializer field class that should be used for any choice fields on the serializer.
     
    
    From 98e56e0327596db352b35fa3b3dc8355dc9bd030 Mon Sep 17 00:00:00 2001
    From: Evgeny Panfilov 
    Date: Thu, 1 Jul 2021 17:04:44 +0300
    Subject: [PATCH 474/907] fix empty string as a value for a validated
     DecimalField (#8064) (#8067)
    
    ---
     rest_framework/fields.py |  8 +++++---
     tests/test_fields.py     | 24 ++++++++++++++++++++++++
     2 files changed, 29 insertions(+), 3 deletions(-)
    
    diff --git a/rest_framework/fields.py b/rest_framework/fields.py
    index e4be54751d..bedc02b94d 100644
    --- a/rest_framework/fields.py
    +++ b/rest_framework/fields.py
    @@ -1046,6 +1046,11 @@ def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value=
                     'Invalid rounding option %s. Valid values for rounding are: %s' % (rounding, valid_roundings))
             self.rounding = rounding
     
    +    def validate_empty_values(self, data):
    +        if smart_str(data).strip() == '' and self.allow_null:
    +            return (True, None)
    +        return super().validate_empty_values(data)
    +
         def to_internal_value(self, data):
             """
             Validate that the input is a decimal number and return a Decimal
    @@ -1063,9 +1068,6 @@ def to_internal_value(self, data):
             try:
                 value = decimal.Decimal(data)
             except decimal.DecimalException:
    -            if data == '' and self.allow_null:
    -                return None
    -
                 self.fail('invalid')
     
             if value.is_nan():
    diff --git a/tests/test_fields.py b/tests/test_fields.py
    index 78a9effb8c..d99ca9c40d 100644
    --- a/tests/test_fields.py
    +++ b/tests/test_fields.py
    @@ -1163,6 +1163,30 @@ class TestMinMaxDecimalField(FieldValues):
         )
     
     
    +class TestAllowEmptyStrDecimalFieldWithValidators(FieldValues):
    +    """
    +    Check that empty string ('', ' ') is acceptable value for the DecimalField
    +    if allow_null=True and there are max/min validators
    +    """
    +    valid_inputs = {
    +        None: None,
    +        '': None,
    +        ' ': None,
    +        '  ': None,
    +        5: Decimal('5'),
    +        '0': Decimal('0'),
    +        '10': Decimal('10'),
    +    }
    +    invalid_inputs = {
    +        -1: ['Ensure this value is greater than or equal to 0.'],
    +        11: ['Ensure this value is less than or equal to 10.'],
    +    }
    +    outputs = {
    +        None: '',
    +    }
    +    field = serializers.DecimalField(max_digits=3, decimal_places=1, allow_null=True, min_value=0, max_value=10)
    +
    +
     class TestNoMaxDigitsDecimalField(FieldValues):
         field = serializers.DecimalField(
             max_value=100, min_value=0,
    
    From b215375125980114482779b36dd825775ef7e482 Mon Sep 17 00:00:00 2001
    From: Nikhil Benesch 
    Date: Fri, 6 Aug 2021 05:10:58 -0400
    Subject: [PATCH 475/907] Propagate nullability in ModelSerializer (#8116)
    
    Propagate the nullability of underlying model fields in ModelSerializer
    when those fields are marked as read only. This ensures the correct
    generation of OpenAPI schemas.
    
    Fix #8041.
    ---
     rest_framework/serializers.py |  5 ++---
     tests/schemas/test_openapi.py | 19 +++++++++++++++++++
     2 files changed, 21 insertions(+), 3 deletions(-)
    
    diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py
    index 49eec82591..9ea57f1aff 100644
    --- a/rest_framework/serializers.py
    +++ b/rest_framework/serializers.py
    @@ -1326,9 +1326,8 @@ def include_extra_kwargs(self, kwargs, extra_kwargs):
             """
             if extra_kwargs.get('read_only', False):
                 for attr in [
    -                'required', 'default', 'allow_blank', 'allow_null',
    -                'min_length', 'max_length', 'min_value', 'max_value',
    -                'validators', 'queryset'
    +                'required', 'default', 'allow_blank', 'min_length',
    +                'max_length', 'min_value', 'max_value', 'validators', 'queryset'
                 ]:
                     kwargs.pop(attr, None)
     
    diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py
    index aef20670e6..daa035a3f3 100644
    --- a/tests/schemas/test_openapi.py
    +++ b/tests/schemas/test_openapi.py
    @@ -2,6 +2,7 @@
     import warnings
     
     import pytest
    +from django.db import models
     from django.test import RequestFactory, TestCase, override_settings
     from django.urls import path
     from django.utils.translation import gettext_lazy as _
    @@ -110,6 +111,24 @@ class Serializer(serializers.Serializer):
             assert data['properties']['default_false']['default'] is False, "default must be false"
             assert 'default' not in data['properties']['without_default'], "default must not be defined"
     
    +    def test_nullable_fields(self):
    +        class Model(models.Model):
    +            rw_field = models.CharField(null=True)
    +            ro_field = models.CharField(null=True)
    +
    +        class Serializer(serializers.ModelSerializer):
    +            class Meta:
    +                model = Model
    +                fields = ["rw_field", "ro_field"]
    +                read_only_fields = ["ro_field"]
    +
    +        inspector = AutoSchema()
    +
    +        data = inspector.map_serializer(Serializer())
    +        assert data['properties']['rw_field']['nullable'], "rw_field nullable must be true"
    +        assert data['properties']['ro_field']['nullable'], "ro_field nullable must be true"
    +        assert data['properties']['ro_field']['readOnly'], "ro_field read_only must be true"
    +
     
     @pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.')
     class TestOperationIntrospection(TestCase):
    
    From fdb49314754ff13d91c6eec7ccdb8ece52bea9eb Mon Sep 17 00:00:00 2001
    From: Aarni Koskela 
    Date: Fri, 6 Aug 2021 12:14:52 +0300
    Subject: [PATCH 476/907] Make Field constructors keyword-only (#7632)
    
    ---
     rest_framework/fields.py | 46 ++++++++++++++++++++--------------------
     tests/test_fields.py     |  5 +++++
     2 files changed, 28 insertions(+), 23 deletions(-)
    
    diff --git a/rest_framework/fields.py b/rest_framework/fields.py
    index bedc02b94d..5cafed5556 100644
    --- a/rest_framework/fields.py
    +++ b/rest_framework/fields.py
    @@ -320,7 +320,7 @@ class Field:
         default_empty_html = empty
         initial = None
     
    -    def __init__(self, read_only=False, write_only=False,
    +    def __init__(self, *, read_only=False, write_only=False,
                      required=None, default=empty, initial=empty, source=None,
                      label=None, help_text=None, style=None,
                      error_messages=None, validators=None, allow_null=False):
    @@ -1163,14 +1163,14 @@ class DateTimeField(Field):
         }
         datetime_parser = datetime.datetime.strptime
     
    -    def __init__(self, format=empty, input_formats=None, default_timezone=None, *args, **kwargs):
    +    def __init__(self, format=empty, input_formats=None, default_timezone=None, **kwargs):
             if format is not empty:
                 self.format = format
             if input_formats is not None:
                 self.input_formats = input_formats
             if default_timezone is not None:
                 self.timezone = default_timezone
    -        super().__init__(*args, **kwargs)
    +        super().__init__(**kwargs)
     
         def enforce_timezone(self, value):
             """
    @@ -1249,12 +1249,12 @@ class DateField(Field):
         }
         datetime_parser = datetime.datetime.strptime
     
    -    def __init__(self, format=empty, input_formats=None, *args, **kwargs):
    +    def __init__(self, format=empty, input_formats=None, **kwargs):
             if format is not empty:
                 self.format = format
             if input_formats is not None:
                 self.input_formats = input_formats
    -        super().__init__(*args, **kwargs)
    +        super().__init__(**kwargs)
     
         def to_internal_value(self, value):
             input_formats = getattr(self, 'input_formats', api_settings.DATE_INPUT_FORMATS)
    @@ -1315,12 +1315,12 @@ class TimeField(Field):
         }
         datetime_parser = datetime.datetime.strptime
     
    -    def __init__(self, format=empty, input_formats=None, *args, **kwargs):
    +    def __init__(self, format=empty, input_formats=None, **kwargs):
             if format is not empty:
                 self.format = format
             if input_formats is not None:
                 self.input_formats = input_formats
    -        super().__init__(*args, **kwargs)
    +        super().__init__(**kwargs)
     
         def to_internal_value(self, value):
             input_formats = getattr(self, 'input_formats', api_settings.TIME_INPUT_FORMATS)
    @@ -1470,9 +1470,9 @@ class MultipleChoiceField(ChoiceField):
         }
         default_empty_html = []
     
    -    def __init__(self, *args, **kwargs):
    +    def __init__(self, **kwargs):
             self.allow_empty = kwargs.pop('allow_empty', True)
    -        super().__init__(*args, **kwargs)
    +        super().__init__(**kwargs)
     
         def get_value(self, dictionary):
             if self.field_name not in dictionary:
    @@ -1529,12 +1529,12 @@ class FileField(Field):
             'max_length': _('Ensure this filename has at most {max_length} characters (it has {length}).'),
         }
     
    -    def __init__(self, *args, **kwargs):
    +    def __init__(self, **kwargs):
             self.max_length = kwargs.pop('max_length', None)
             self.allow_empty_file = kwargs.pop('allow_empty_file', False)
             if 'use_url' in kwargs:
                 self.use_url = kwargs.pop('use_url')
    -        super().__init__(*args, **kwargs)
    +        super().__init__(**kwargs)
     
         def to_internal_value(self, data):
             try:
    @@ -1578,9 +1578,9 @@ class ImageField(FileField):
             ),
         }
     
    -    def __init__(self, *args, **kwargs):
    +    def __init__(self, **kwargs):
             self._DjangoImageField = kwargs.pop('_DjangoImageField', DjangoImageField)
    -        super().__init__(*args, **kwargs)
    +        super().__init__(**kwargs)
     
         def to_internal_value(self, data):
             # Image validation is a bit grungy, so we'll just outright
    @@ -1595,8 +1595,8 @@ def to_internal_value(self, data):
     # Composite field types...
     
     class _UnvalidatedField(Field):
    -    def __init__(self, *args, **kwargs):
    -        super().__init__(*args, **kwargs)
    +    def __init__(self, **kwargs):
    +        super().__init__(**kwargs)
             self.allow_blank = True
             self.allow_null = True
     
    @@ -1617,7 +1617,7 @@ class ListField(Field):
             'max_length': _('Ensure this field has no more than {max_length} elements.')
         }
     
    -    def __init__(self, *args, **kwargs):
    +    def __init__(self, **kwargs):
             self.child = kwargs.pop('child', copy.deepcopy(self.child))
             self.allow_empty = kwargs.pop('allow_empty', True)
             self.max_length = kwargs.pop('max_length', None)
    @@ -1629,7 +1629,7 @@ def __init__(self, *args, **kwargs):
                 "Remove `source=` from the field declaration."
             )
     
    -        super().__init__(*args, **kwargs)
    +        super().__init__(**kwargs)
             self.child.bind(field_name='', parent=self)
             if self.max_length is not None:
                 message = lazy_format(self.error_messages['max_length'], max_length=self.max_length)
    @@ -1694,7 +1694,7 @@ class DictField(Field):
             'empty': _('This dictionary may not be empty.'),
         }
     
    -    def __init__(self, *args, **kwargs):
    +    def __init__(self, **kwargs):
             self.child = kwargs.pop('child', copy.deepcopy(self.child))
             self.allow_empty = kwargs.pop('allow_empty', True)
     
    @@ -1704,7 +1704,7 @@ def __init__(self, *args, **kwargs):
                 "Remove `source=` from the field declaration."
             )
     
    -        super().__init__(*args, **kwargs)
    +        super().__init__(**kwargs)
             self.child.bind(field_name='', parent=self)
     
         def get_value(self, dictionary):
    @@ -1753,8 +1753,8 @@ def run_child_validation(self, data):
     class HStoreField(DictField):
         child = CharField(allow_blank=True, allow_null=True)
     
    -    def __init__(self, *args, **kwargs):
    -        super().__init__(*args, **kwargs)
    +    def __init__(self, **kwargs):
    +        super().__init__(**kwargs)
             assert isinstance(self.child, CharField), (
                 "The `child` argument must be an instance of `CharField`, "
                 "as the hstore extension stores values as strings."
    @@ -1769,11 +1769,11 @@ class JSONField(Field):
         # Workaround for isinstance calls when importing the field isn't possible
         _is_jsonfield = True
     
    -    def __init__(self, *args, **kwargs):
    +    def __init__(self, **kwargs):
             self.binary = kwargs.pop('binary', False)
             self.encoder = kwargs.pop('encoder', None)
             self.decoder = kwargs.pop('decoder', None)
    -        super().__init__(*args, **kwargs)
    +        super().__init__(**kwargs)
     
         def get_value(self, dictionary):
             if html.is_html_input(dictionary) and self.field_name in dictionary:
    diff --git a/tests/test_fields.py b/tests/test_fields.py
    index d99ca9c40d..2d4cc44ae0 100644
    --- a/tests/test_fields.py
    +++ b/tests/test_fields.py
    @@ -2010,6 +2010,11 @@ def test_collection_types_are_invalid_input(self):
                 field.to_internal_value(input_value)
             assert exc_info.value.detail == ['Expected a list of items but got type "dict".']
     
    +    def test_constructor_misuse_raises(self):
    +        # Test that `ListField` can only be instantiated with keyword arguments
    +        with pytest.raises(TypeError):
    +            serializers.ListField(serializers.CharField())
    +
     
     class TestNestedListField(FieldValues):
         """
    
    From 2942590ee3d3596683405dcdb1017e1833f5cb02 Mon Sep 17 00:00:00 2001
    From: Ma77heus <58952630+MattheusHenrique@users.noreply.github.com>
    Date: Fri, 6 Aug 2021 12:39:58 -0300
    Subject: [PATCH 477/907] fix: broken cite (#8086)
    
    Co-authored-by: MattheusHenrique 
    ---
     docs/api-guide/renderers.md | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md
    index 7dbc5eee8f..f13b7ba946 100644
    --- a/docs/api-guide/renderers.md
    +++ b/docs/api-guide/renderers.md
    @@ -528,7 +528,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
     [Rest Framework Latex] provides a renderer that outputs PDFs using Laulatex. It is maintained by [Pebble (S/F Software)][mypebble].
     
     
    -[cite]: https://docs.djangoproject.com/en/stable/stable/template-response/#the-rendering-process
    +[cite]: https://docs.djangoproject.com/en/stable/ref/template-response/#the-rendering-process
     [conneg]: content-negotiation.md
     [html-and-forms]: ../topics/html-and-forms.md
     [browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers
    
    From cba24464e8f63377627f3016df88ee74d11a817d Mon Sep 17 00:00:00 2001
    From: Paul Wayper 
    Date: Sat, 7 Aug 2021 01:45:15 +1000
    Subject: [PATCH 478/907] Botbot has been acquired, all paths now point to
     startupresources (#8050)
    
    Signed-off-by: Paul Wayper 
    ---
     docs/index.md | 3 +--
     1 file changed, 1 insertion(+), 2 deletions(-)
    
    diff --git a/docs/index.md b/docs/index.md
    index 28e3302501..ccbaf73731 100644
    --- a/docs/index.md
    +++ b/docs/index.md
    @@ -186,7 +186,7 @@ Framework.
     
     ## Support
     
    -For support please see the [REST framework discussion group][group], try the  `#restframework` channel on `irc.freenode.net`, search [the IRC archives][botbot], or raise a  question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
    +For support please see the [REST framework discussion group][group], try the  `#restframework` channel on `irc.freenode.net`, or raise a  question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
     
     For priority support please sign up for a [professional or premium sponsorship plan](https://fund.django-rest-framework.org/topics/funding/).
     
    @@ -257,7 +257,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     [funding]: community/funding.md
     
     [group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
    -[botbot]: https://botbot.me/freenode/restframework/
     [stack-overflow]: https://stackoverflow.com/
     [django-rest-framework-tag]: https://stackoverflow.com/questions/tagged/django-rest-framework
     [security-mail]: mailto:rest-framework-security@googlegroups.com
    
    From c4404f3d5d2df2a5c7450517b48c4e6dfeb3c89e Mon Sep 17 00:00:00 2001
    From: Paul Wayper 
    Date: Sat, 7 Aug 2021 01:46:26 +1000
    Subject: [PATCH 479/907] We now use Libera.chat rather than Freenode for IRC
     (#8049)
    
    Signed-off-by: Paul Wayper 
    
    Co-authored-by: Tom Christie 
    ---
     README.md     | 2 +-
     docs/index.md | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/README.md b/README.md
    index fce275256e..b8d8ca61b1 100644
    --- a/README.md
    +++ b/README.md
    @@ -168,7 +168,7 @@ Or to create a new user:
     
     Full documentation for the project is available at [https://www.django-rest-framework.org/][docs].
     
    -For questions and support, use the [REST framework discussion group][group], or `#restframework` on freenode IRC.
    +For questions and support, use the [REST framework discussion group][group], or `#restframework` on libera.chat IRC.
     
     You may also want to [follow the author on Twitter][twitter].
     
    diff --git a/docs/index.md b/docs/index.md
    index ccbaf73731..641800b93c 100644
    --- a/docs/index.md
    +++ b/docs/index.md
    @@ -186,7 +186,7 @@ Framework.
     
     ## Support
     
    -For support please see the [REST framework discussion group][group], try the  `#restframework` channel on `irc.freenode.net`, or raise a  question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
    +For support please see the [REST framework discussion group][group], try the  `#restframework` channel on `irc.libera.chat`, or raise a  question on [Stack Overflow][stack-overflow], making sure to include the ['django-rest-framework'][django-rest-framework-tag] tag.
     
     For priority support please sign up for a [professional or premium sponsorship plan](https://fund.django-rest-framework.org/topics/funding/).
     
    
    From b824b33dc3a3facad3ef2b41fbe02ab2a7578bc3 Mon Sep 17 00:00:00 2001
    From: Thomas Grainger 
    Date: Fri, 6 Aug 2021 16:46:57 +0100
    Subject: [PATCH 480/907] add changelog project_url (#8085)
    
    ---
     setup.py | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/setup.py b/setup.py
    index 5fd4df20db..d755a00fe2 100755
    --- a/setup.py
    +++ b/setup.py
    @@ -109,6 +109,7 @@ def get_version(package):
         project_urls={
             'Funding': 'https://fund.django-rest-framework.org/topics/funding/',
             'Source': 'https://github.com/encode/django-rest-framework',
    +        'Changelog': 'https://www.django-rest-framework.org/community/release-notes/',
         },
     )
     
    
    From e95e91ccf2065cbf474892b73ebd5790e5a4ae14 Mon Sep 17 00:00:00 2001
    From: Ben Hampson <77866043+Ben-Hampson@users.noreply.github.com>
    Date: Fri, 6 Aug 2021 17:49:41 +0200
    Subject: [PATCH 481/907] Use correct link for httpie (#8005)
    
    Before it was linking to a fork of a fork of httpie. I've changed it to the right URL.
    ---
     docs/tutorial/1-serialization.md | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
    index 85d8676b1d..908b7474a0 100644
    --- a/docs/tutorial/1-serialization.md
    +++ b/docs/tutorial/1-serialization.md
    @@ -374,5 +374,5 @@ We'll see how we can start to improve things in [part 2 of the tutorial][tut-2].
     [sandbox]: https://restframework.herokuapp.com/
     [venv]: https://docs.python.org/3/library/venv.html
     [tut-2]: 2-requests-and-responses.md
    -[httpie]: https://github.com/jakubroztocil/httpie#installation
    +[httpie]: https://github.com/httpie/httpie#installation
     [curl]: https://curl.haxx.se/
    
    From cdd53c7de912d5868c96f4e3883df248a3e6341d Mon Sep 17 00:00:00 2001
    From: juliangeissler <81534590+juliangeissler@users.noreply.github.com>
    Date: Sun, 8 Aug 2021 15:45:00 +0200
    Subject: [PATCH 482/907] Update Tutorial - Relationships & Hyperlinked APIs
     (#7950)
    
    unnecessary import, because it is already added in the previous section
    ---
     docs/tutorial/5-relationships-and-hyperlinked-apis.md | 1 -
     1 file changed, 1 deletion(-)
    
    diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
    index b0f3380859..f999fdf507 100644
    --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md
    +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
    @@ -31,7 +31,6 @@ The other thing we need to consider when creating the code highlight view is tha
     Instead of using a concrete generic view, we'll use the base class for representing instances, and create our own `.get()` method.  In your `snippets/views.py` add:
     
         from rest_framework import renderers
    -    from rest_framework.response import Response
     
         class SnippetHighlight(generics.GenericAPIView):
             queryset = Snippet.objects.all()
    
    From c5d9144aef1144825942ddffe0a6af23102ef44a Mon Sep 17 00:00:00 2001
    From: Mark <33526445+mark-gold@users.noreply.github.com>
    Date: Wed, 11 Aug 2021 13:30:09 +0300
    Subject: [PATCH 483/907] fix typo (#8122)
    
    Co-authored-by: mgold 
    ---
     docs/api-guide/validators.md | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md
    index 4451489d4d..76dcb0d541 100644
    --- a/docs/api-guide/validators.md
    +++ b/docs/api-guide/validators.md
    @@ -238,7 +238,7 @@ In the case of update operations on *nested* serializers there's no way of
     applying this exclusion, because the instance is not available.
     
     Again, you'll probably want to explicitly remove the validator from the
    -serializer class, and write the code the for the validation constraint
    +serializer class, and write the code for the validation constraint
     explicitly, in a `.validate()` method, or in the view.
     
     ## Debugging complex cases
    
    From c927053d4b99ada6b3fd5d70c6536554ff5fe8c0 Mon Sep 17 00:00:00 2001
    From: jefcolbi 
    Date: Tue, 31 Aug 2021 12:51:47 +0100
    Subject: [PATCH 484/907] Replacing django-rest-auth with dj-rest-auth (#8146)
    
    ---
     docs/community/third-party-packages.md | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md
    index e53fc3d50c..933244a6a9 100644
    --- a/docs/community/third-party-packages.md
    +++ b/docs/community/third-party-packages.md
    @@ -54,7 +54,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
     * [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.
    -* [django-rest-auth][django-rest-auth] - Provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc.
    +* [dj-rest-auth][dj-rest-auth] - Provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc.
     * [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF.
     * [drfpasswordless][drfpasswordless] - Adds (Medium, Square Cash inspired) passwordless logins and signups via email and mobile numbers.
     * [django-rest-authemail][django-rest-authemail] - Provides a RESTful API for user signup and authentication using email addresses.
    @@ -193,7 +193,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
     [gaiarestframework]: https://github.com/AppsFuel/gaiarestframework
     [drf-extensions]: https://github.com/chibisov/drf-extensions
     [ember-django-adapter]: https://github.com/dustinfarris/ember-django-adapter
    -[django-rest-auth]: https://github.com/Tivix/django-rest-auth/
    +[dj-rest-auth]: https://github.com/iMerica/dj-rest-auth
     [django-versatileimagefield]: https://github.com/WGBH/django-versatileimagefield
     [django-versatileimagefield-drf-docs]:https://django-versatileimagefield.readthedocs.io/en/latest/drf_integration.html
     [drf-tracking]: https://github.com/aschn/drf-tracking
    
    From 88666629a70f5c3fbe31e11aecd9817338de9c92 Mon Sep 17 00:00:00 2001
    From: Asif Saif Uddin 
    Date: Tue, 31 Aug 2021 18:56:08 +0600
    Subject: [PATCH 485/907] stop testing django 3.0 as its EOL (#8136)
    
    ---
     tox.ini | 5 +----
     1 file changed, 1 insertion(+), 4 deletions(-)
    
    diff --git a/tox.ini b/tox.ini
    index f23486a685..25f8418219 100644
    --- a/tox.ini
    +++ b/tox.ini
    @@ -1,7 +1,6 @@
     [tox]
     envlist =
            {py35,py36,py37}-django22,
    -       {py36,py37,py38}-django30,
            {py36,py37,py38,py39}-django31,
            {py36,py37,py38,py39}-django32,
            {py38,py39}-djangomain,
    @@ -10,7 +9,6 @@ envlist =
     [travis:env]
     DJANGO =
         2.2: django22
    -    3.0: django30
         3.1: django31
         3.2: django32
         main: djangomain
    @@ -23,9 +21,8 @@ setenv =
            PYTHONWARNINGS=once
     deps =
             django22: Django>=2.2,<3.0
    -        django30: Django>=3.0,<3.1
             django31: Django>=3.1,<3.2
    -        django32: Django>=3.2a1,<4.0
    +        django32: Django>=3.2,<4.0
             djangomain: https://github.com/django/django/archive/main.tar.gz
             -rrequirements/requirements-testing.txt
             -rrequirements/requirements-optionals.txt
    
    From 6b392a46ea025148a24ce665e9c18e4386dde8fa Mon Sep 17 00:00:00 2001
    From: Aditya Mitra <55396651+aditya-mitra@users.noreply.github.com>
    Date: Tue, 31 Aug 2021 18:27:02 +0530
    Subject: [PATCH 486/907] [FIX] Typo in api-guide/authentication (#8144)
    
    ---
     docs/api-guide/authentication.md | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md
    index 60544079f1..57bbaeb679 100644
    --- a/docs/api-guide/authentication.md
    +++ b/docs/api-guide/authentication.md
    @@ -13,7 +13,7 @@ Authentication is the mechanism of associating an incoming request with a set of
     
     REST framework provides several authentication schemes out of the box, and also allows you to implement custom schemes.
     
    -Authentication is always run at the very start of the view, before the permission and throttling checks occur, and before any other code is allowed to proceed.
    +Authentication always runs at the very start of the view, before the permission and throttling checks occur, and before any other code is allowed to proceed.
     
     The `request.user` property will typically be set to an instance of the `contrib.auth` package's `User` class.
     
    
    From 4632b5daaed5a71a1be3e7d412a7f9a2e5520b90 Mon Sep 17 00:00:00 2001
    From: Ryan Nowakowski 
    Date: Tue, 31 Aug 2021 08:18:49 -0500
    Subject: [PATCH 487/907] Fix subtitle of schemas for filtering (#8145)
    
    Fix a likely copy/paste error
    ---
     docs/api-guide/filtering.md | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md
    index 478e3bcf95..3541388ca2 100644
    --- a/docs/api-guide/filtering.md
    +++ b/docs/api-guide/filtering.md
    @@ -335,7 +335,7 @@ Generic filters may also present an interface in the browsable API. To do so you
     
     The method should return a rendered HTML string.
     
    -## Pagination & schemas
    +## Filtering & schemas
     
     You can also make the filter controls available to the schema autogeneration
     that REST framework provides, by implementing a `get_schema_fields()` method. This method should have the following signature:
    
    From cb206e4701dd67f859c015bea111d0e77e364c4a Mon Sep 17 00:00:00 2001
    From: Juan Benitez 
    Date: Fri, 3 Sep 2021 07:00:23 -0500
    Subject: [PATCH 488/907] fix: change View class to Throttle class on
     SimpleRateThrottle Docstring (#8147)
    
    ---
     rest_framework/throttling.py | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/rest_framework/throttling.py b/rest_framework/throttling.py
    index 0ba2ba66b1..e262b886bc 100644
    --- a/rest_framework/throttling.py
    +++ b/rest_framework/throttling.py
    @@ -52,7 +52,7 @@ class SimpleRateThrottle(BaseThrottle):
         A simple cache implementation, that only requires `.get_cache_key()`
         to be overridden.
     
    -    The rate (requests / seconds) is set by a `rate` attribute on the View
    +    The rate (requests / seconds) is set by a `rate` attribute on the Throttle
         class.  The attribute is a string of the form 'number_of_requests/period'.
     
         Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')
    
    From 96001c5de61b5fe7c083bdd8e5810105e3575014 Mon Sep 17 00:00:00 2001
    From: Anthony Randall 
    Date: Fri, 3 Sep 2021 06:23:19 -0600
    Subject: [PATCH 489/907] Added an article - implementing rest apis with
     embedded privacy from doordash engineering blog (#7956)
    
    * Update tutorials-and-resources.md
    
    * Update tutorials-and-resources.md
    ---
     docs/community/tutorials-and-resources.md | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/docs/community/tutorials-and-resources.md b/docs/community/tutorials-and-resources.md
    index dae292f50c..23faf79128 100644
    --- a/docs/community/tutorials-and-resources.md
    +++ b/docs/community/tutorials-and-resources.md
    @@ -76,6 +76,7 @@ There are a wide range of resources available for learning and using Django REST
     * [Chatbot Using Django REST Framework + api.ai + Slack — Part 1/3][chatbot-using-drf-part1]
     * [New Django Admin with DRF and EmberJS... What are the News?][new-django-admin-with-drf-and-emberjs]
     * [Blog posts about Django REST Framework][medium-django-rest-framework]
    +* [Implementing Rest APIs With Embedded Privacy][doordash-implementing-rest-apis]
     
     ### Documentations
     * [Classy Django REST Framework][cdrf.co]
    @@ -128,3 +129,4 @@ Want your Django REST Framework talk/tutorial/article to be added to our website
     [anna-email]: mailto:anna@django-rest-framework.org
     [pycon-us-2017]: https://www.youtube.com/watch?v=Rk6MHZdust4
     [django-rest-react-valentinog]: https://www.valentinog.com/blog/tutorial-api-django-rest-react/
    +[doordash-implementing-rest-apis]: https://doordash.engineering/2013/10/07/implementing-rest-apis-with-embedded-privacy/
    
    From 655e803adfb19b8cb5b94a4895f1baffed55a958 Mon Sep 17 00:00:00 2001
    From: Peter Uittenbroek 
    Date: Fri, 3 Sep 2021 15:37:03 +0200
    Subject: [PATCH 490/907] #7157: Fix RemoteUserAuthentication calling django
     authenticate with request argument (#7158)
    
    ---
     rest_framework/authentication.py | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/rest_framework/authentication.py b/rest_framework/authentication.py
    index 9111007c09..382abf1580 100644
    --- a/rest_framework/authentication.py
    +++ b/rest_framework/authentication.py
    @@ -227,6 +227,6 @@ class RemoteUserAuthentication(BaseAuthentication):
         header = "REMOTE_USER"
     
         def authenticate(self, request):
    -        user = authenticate(remote_user=request.META.get(self.header))
    +        user = authenticate(request=request, remote_user=request.META.get(self.header))
             if user and user.is_active:
                 return (user, None)
    
    From 9716b1b6b7779543c134856e59f1c1393963e46f Mon Sep 17 00:00:00 2001
    From: Ivan Trushin <33528037+WannaFight@users.noreply.github.com>
    Date: Mon, 6 Sep 2021 14:18:13 +0300
    Subject: [PATCH 491/907] Fix arguments (#7995)
    
    `path()` has no argument `namespace`, it has `name` argument
    ---
     docs/tutorial/quickstart.md | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
    index ee839790f1..e19577f617 100644
    --- a/docs/tutorial/quickstart.md
    +++ b/docs/tutorial/quickstart.md
    @@ -126,7 +126,7 @@ Okay, now let's wire up the API URLs.  On to `tutorial/urls.py`...
         # Additionally, we include login URLs for the browsable API.
         urlpatterns = [
             path('', include(router.urls)),
    -        path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
    +        path('api-auth/', include('rest_framework.urls', name='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.
    
    From 9ce541e90990307e06da1b7f5a2576406366a5e5 Mon Sep 17 00:00:00 2001
    From: Tom Christie 
    Date: Mon, 6 Sep 2021 12:19:20 +0100
    Subject: [PATCH 492/907] Revert "Fix arguments (#7995)" (#8156)
    
    This reverts commit 9716b1b6b7779543c134856e59f1c1393963e46f.
    ---
     docs/tutorial/quickstart.md | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
    index e19577f617..ee839790f1 100644
    --- a/docs/tutorial/quickstart.md
    +++ b/docs/tutorial/quickstart.md
    @@ -126,7 +126,7 @@ Okay, now let's wire up the API URLs.  On to `tutorial/urls.py`...
         # Additionally, we include login URLs for the browsable API.
         urlpatterns = [
             path('', include(router.urls)),
    -        path('api-auth/', include('rest_framework.urls', name='rest_framework'))
    +        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.
    
    From 73f3325f80a381d1d62ab1b84956295963f445ed Mon Sep 17 00:00:00 2001
    From: Tom Christie 
    Date: Fri, 10 Sep 2021 11:32:27 +0100
    Subject: [PATCH 493/907] Update stream.io link (#8161)
    
    ---
     README.md     | 2 +-
     docs/index.md | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/README.md b/README.md
    index b8d8ca61b1..e3bcc2a1c2 100644
    --- a/README.md
    +++ b/README.md
    @@ -197,7 +197,7 @@ Please see the [security policy][security-policy].
     [bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png
     
     [sentry-url]: https://getsentry.com/welcome/
    -[stream-url]: https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf
    +[stream-url]: https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer
     [rollbar-url]: https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial
     [esg-url]: https://software.esg-usa.com/
     [retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship
    diff --git a/docs/index.md b/docs/index.md
    index 641800b93c..9b667c6691 100644
    --- a/docs/index.md
    +++ b/docs/index.md
    @@ -75,7 +75,7 @@ continued development by **[signing up for a paid plan][funding]**.
     
     
    -*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), and [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship).* +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), and [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship).* --- From 761f56ef4025543e9cf39346d25641305e7d957d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 10 Sep 2021 14:45:06 +0100 Subject: [PATCH 494/907] Update stream.io link --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 9b667c6691..f000d2e093 100644 --- a/docs/index.md +++ b/docs/index.md @@ -67,7 +67,7 @@ continued development by **[signing up for a paid plan][funding]**.
    • Sentry
    • -
    • Stream
    • +
    • Stream
    • ESG
    • Rollbar
    • Retool
    • From f0a5b958a134e8cd94e3ef3263e8fa623ac9b82f Mon Sep 17 00:00:00 2001 From: Dan Lousqui Date: Tue, 14 Sep 2021 14:45:55 +0200 Subject: [PATCH 495/907] Add max_length and min_length options to ListSerializer (#8165) --- docs/api-guide/serializers.md | 8 ++++ rest_framework/serializers.py | 27 +++++++++++++- tests/test_serializer_lists.py | 67 ++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 13c0c87104..cf8525748c 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -755,6 +755,14 @@ The following argument can also be passed to a `ListSerializer` field or a seria This is `True` by default, but can be set to `False` if you want to disallow empty lists as valid input. +### `max_length` + +This is `None` by default, but can be set to a positive integer if you want to validates that the list contains no more than this number of elements. + +### `min_length` + +This is `None` by default, but can be set to a positive integer if you want to validates that the list contains no fewer than this number of elements. + ### Customizing `ListSerializer` behavior There *are* a few use cases when you might want to customize the `ListSerializer` behavior. For example: diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 9ea57f1aff..3896805177 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -71,7 +71,8 @@ LIST_SERIALIZER_KWARGS = ( 'read_only', 'write_only', 'required', 'default', 'initial', 'source', 'label', 'help_text', 'style', 'error_messages', 'allow_empty', - 'instance', 'data', 'partial', 'context', 'allow_null' + 'instance', 'data', 'partial', 'context', 'allow_null', + 'max_length', 'min_length' ) ALL_FIELDS = '__all__' @@ -143,12 +144,18 @@ def many_init(cls, *args, **kwargs): return CustomListSerializer(*args, **kwargs) """ allow_empty = kwargs.pop('allow_empty', None) + max_length = kwargs.pop('max_length', None) + min_length = kwargs.pop('min_length', None) child_serializer = cls(*args, **kwargs) list_kwargs = { 'child': child_serializer, } if allow_empty is not None: list_kwargs['allow_empty'] = allow_empty + if max_length is not None: + list_kwargs['max_length'] = max_length + if min_length is not None: + list_kwargs['min_length'] = min_length list_kwargs.update({ key: value for key, value in kwargs.items() if key in LIST_SERIALIZER_KWARGS @@ -568,12 +575,16 @@ class ListSerializer(BaseSerializer): default_error_messages = { 'not_a_list': _('Expected a list of items but got type "{input_type}".'), - 'empty': _('This list may not be empty.') + 'empty': _('This list may not be empty.'), + 'max_length': _('Ensure this field has no more than {max_length} elements.'), + 'min_length': _('Ensure this field has at least {min_length} elements.') } def __init__(self, *args, **kwargs): self.child = kwargs.pop('child', copy.deepcopy(self.child)) self.allow_empty = kwargs.pop('allow_empty', True) + self.max_length = kwargs.pop('max_length', None) + self.min_length = kwargs.pop('min_length', None) assert self.child is not None, '`child` is a required argument.' assert not inspect.isclass(self.child), '`child` has not been instantiated.' super().__init__(*args, **kwargs) @@ -635,6 +646,18 @@ def to_internal_value(self, data): api_settings.NON_FIELD_ERRORS_KEY: [message] }, code='empty') + if self.max_length is not None and len(data) > self.max_length: + message = self.error_messages['max_length'].format(max_length=self.max_length) + raise ValidationError({ + api_settings.NON_FIELD_ERRORS_KEY: [message] + }, code='max_length') + + if self.min_length is not None and len(data) < self.min_length: + message = self.error_messages['min_length'].format(min_length=self.min_length) + raise ValidationError({ + api_settings.NON_FIELD_ERRORS_KEY: [message] + }, code='min_length') + ret = [] errors = [] diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index f35c4fcc9e..551f626662 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -616,3 +616,70 @@ def test_nested_serializer_with_list_multipart(self): assert serializer.is_valid() assert serializer.validated_data == [] + + +class TestMaxMinLengthListSerializer: + """ + Tests the behaviour of ListSerializers when max_length and min_length are used + """ + + def setup(self): + class IntegerSerializer(serializers.Serializer): + some_int = serializers.IntegerField() + + class MaxLengthSerializer(serializers.Serializer): + many_int = IntegerSerializer(many=True, max_length=5) + + class MinLengthSerializer(serializers.Serializer): + many_int = IntegerSerializer(many=True, min_length=3) + + class MaxMinLengthSerializer(serializers.Serializer): + many_int = IntegerSerializer(many=True, min_length=3, max_length=5) + + self.MaxLengthSerializer = MaxLengthSerializer + self.MinLengthSerializer = MinLengthSerializer + self.MaxMinLengthSerializer = MaxMinLengthSerializer + + def test_min_max_length_two_items(self): + input_data = {'many_int': [{'some_int': i} for i in range(2)]} + + max_serializer = self.MaxLengthSerializer(data=input_data) + min_serializer = self.MinLengthSerializer(data=input_data) + max_min_serializer = self.MaxMinLengthSerializer(data=input_data) + + assert max_serializer.is_valid() + assert max_serializer.validated_data == input_data + + assert not min_serializer.is_valid() + + assert not max_min_serializer.is_valid() + + def test_min_max_length_four_items(self): + input_data = {'many_int': [{'some_int': i} for i in range(4)]} + + max_serializer = self.MaxLengthSerializer(data=input_data) + min_serializer = self.MinLengthSerializer(data=input_data) + max_min_serializer = self.MaxMinLengthSerializer(data=input_data) + + assert max_serializer.is_valid() + assert max_serializer.validated_data == input_data + + assert min_serializer.is_valid() + assert min_serializer.validated_data == input_data + + assert max_min_serializer.is_valid() + assert min_serializer.validated_data == input_data + + def test_min_max_length_six_items(self): + input_data = {'many_int': [{'some_int': i} for i in range(6)]} + + max_serializer = self.MaxLengthSerializer(data=input_data) + min_serializer = self.MinLengthSerializer(data=input_data) + max_min_serializer = self.MaxMinLengthSerializer(data=input_data) + + assert not max_serializer.is_valid() + + assert min_serializer.is_valid() + assert min_serializer.validated_data == input_data + + assert not max_min_serializer.is_valid() From 250479dc3799a281429c2c10d9605a1a85d3e517 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 22 Sep 2021 09:57:17 +0200 Subject: [PATCH 496/907] Added pytz to install_requires. pytz will not automatically be installed with Django from v4.0. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d755a00fe2..b8e220cb43 100755 --- a/setup.py +++ b/setup.py @@ -82,7 +82,7 @@ def get_version(package): author_email='tom@tomchristie.com', # SEE NOTE BELOW (*) packages=find_packages(exclude=['tests*']), include_package_data=True, - install_requires=["django>=2.2"], + install_requires=["django>=2.2", "pytz"], python_requires=">=3.5", zip_safe=False, classifiers=[ From f651878df33bf12d3b637f2377e234a2f0a0523c Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 22 Sep 2021 09:58:34 +0200 Subject: [PATCH 497/907] Adjusted DateTimeField docs for zoneinfo. --- docs/api-guide/fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 04f9939425..b986009f9b 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -325,7 +325,7 @@ Corresponds to `django.db.models.fields.DateTimeField`. * `format` - A string representing the output format. If not specified, this defaults to the same value as the `DATETIME_FORMAT` settings key, which will be `'iso-8601'` unless set. Setting to a format string indicates that `to_representation` return values should be coerced to string output. Format strings are described below. Setting this value to `None` indicates that Python `datetime` objects should be returned by `to_representation`. In this case the datetime encoding will be determined by the renderer. * `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATETIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. -* `default_timezone` - A `pytz.timezone` representing the timezone. If not specified and the `USE_TZ` setting is enabled, this defaults to the [current timezone][django-current-timezone]. If `USE_TZ` is disabled, then datetime objects will be naive. +* `default_timezone` - A `tzinfo` subclass (`zoneinfo` or `pytz`) prepresenting the timezone. If not specified and the `USE_TZ` setting is enabled, this defaults to the [current timezone][django-current-timezone]. If `USE_TZ` is disabled, then datetime objects will be naive. #### `DateTimeField` format strings. From 2d9eee5d022bf77d6bb25e1d8b57da9e9c94f96f Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 22 Sep 2021 10:00:49 +0200 Subject: [PATCH 498/907] Adjusted URLPatternsTestCase to use addClassCleanup() from Django 4.0. Refs https://github.com/django/django/commit/faba5b702a9c5bb9452a543100928bcb5f66ebcf. addClassCleanup() is available from Python 3.8, which is the minimum supported Python from Django 4.0. --- rest_framework/test.py | 30 ++++++++++++++++++++++-------- tests/test_testing.py | 22 +++++++++++++++++----- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index e934eff55d..0212348ee0 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -3,6 +3,7 @@ import io from importlib import import_module +import django from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.core.handlers.wsgi import WSGIHandler @@ -357,6 +358,13 @@ class APILiveServerTestCase(testcases.LiveServerTestCase): client_class = APIClient +def cleanup_url_patterns(cls): + if hasattr(cls, '_module_urlpatterns'): + cls._module.urlpatterns = cls._module_urlpatterns + else: + del cls._module.urlpatterns + + class URLPatternsTestCase(testcases.SimpleTestCase): """ Isolate URL patterns on a per-TestCase basis. For example, @@ -385,14 +393,20 @@ def setUpClass(cls): cls._module.urlpatterns = cls.urlpatterns cls._override.enable() + + if django.VERSION > (4, 0): + cls.addClassCleanup(cls._override.disable) + cls.addClassCleanup(cleanup_url_patterns, cls) + super().setUpClass() - @classmethod - def tearDownClass(cls): - super().tearDownClass() - cls._override.disable() + if django.VERSION < (4, 0): + @classmethod + def tearDownClass(cls): + super().tearDownClass() + cls._override.disable() - if hasattr(cls, '_module_urlpatterns'): - cls._module.urlpatterns = cls._module_urlpatterns - else: - del cls._module.urlpatterns + if hasattr(cls, '_module_urlpatterns'): + cls._module.urlpatterns = cls._module_urlpatterns + else: + del cls._module.urlpatterns diff --git a/tests/test_testing.py b/tests/test_testing.py index cc60e4f003..5066ee142e 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,5 +1,6 @@ from io import BytesIO +import django from django.contrib.auth.models import User from django.shortcuts import redirect from django.test import TestCase, override_settings @@ -282,6 +283,10 @@ def test_empty_request_content_type(self): assert request.META['CONTENT_TYPE'] == 'application/json' +def check_urlpatterns(cls): + assert urlpatterns is not cls.urlpatterns + + class TestUrlPatternTestCase(URLPatternsTestCase): urlpatterns = [ path('', view), @@ -293,11 +298,18 @@ def setUpClass(cls): super().setUpClass() assert urlpatterns is cls.urlpatterns - @classmethod - def tearDownClass(cls): - assert urlpatterns is cls.urlpatterns - super().tearDownClass() - assert urlpatterns is not cls.urlpatterns + if django.VERSION > (4, 0): + cls.addClassCleanup( + check_urlpatterns, + cls + ) + + if django.VERSION < (4, 0): + @classmethod + def tearDownClass(cls): + assert urlpatterns is cls.urlpatterns + super().tearDownClass() + assert urlpatterns is not cls.urlpatterns def test_urlpatterns(self): assert self.client.get('/').status_code == 200 From 4916854492e6c999977b8577b5a15e6ffc784550 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 22 Sep 2021 10:02:28 +0200 Subject: [PATCH 499/907] Removed USE_L10N setting from Django 4.0. USE_L10N defaults to True from Django 4.0, and will be removed in Django 5.0. --- tests/conftest.py | 4 +++- tests/test_fields.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index cc32cc6373..79cabd5e1a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,6 +18,8 @@ def pytest_addoption(parser): def pytest_configure(config): from django.conf import settings + # USE_L10N is deprecated, and will be removed in Django 5.0. + use_l10n = {"USE_L10N": True} if django.VERSION < (4, 0) else {} settings.configure( DEBUG_PROPAGATE_EXCEPTIONS=True, DATABASES={ @@ -33,7 +35,6 @@ def pytest_configure(config): SITE_ID=1, SECRET_KEY='not very secret in tests', USE_I18N=True, - USE_L10N=True, STATIC_URL='/static/', ROOT_URLCONF='tests.urls', TEMPLATES=[ @@ -68,6 +69,7 @@ def pytest_configure(config): PASSWORD_HASHERS=( 'django.contrib.auth.hashers.MD5PasswordHasher', ), + **use_l10n, ) # guardian is optional diff --git a/tests/test_fields.py b/tests/test_fields.py index 2d4cc44ae0..a3b37584bc 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1220,12 +1220,12 @@ class TestNoStringCoercionDecimalField(FieldValues): class TestLocalizedDecimalField(TestCase): - @override_settings(USE_L10N=True, LANGUAGE_CODE='pl') + @override_settings(LANGUAGE_CODE='pl') def test_to_internal_value(self): field = serializers.DecimalField(max_digits=2, decimal_places=1, localize=True) assert field.to_internal_value('1,1') == Decimal('1.1') - @override_settings(USE_L10N=True, LANGUAGE_CODE='pl') + @override_settings(LANGUAGE_CODE='pl') def test_to_representation(self): field = serializers.DecimalField(max_digits=2, decimal_places=1, localize=True) assert field.to_representation(Decimal('1.1')) == '1,1' From 19b609155479f967a41ffc20a58bb09229f1e64b Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 22 Sep 2021 10:06:10 +0200 Subject: [PATCH 500/907] Adjusted authentication test for internal CSRF changes. Private _get_new_csrf_token() was removed in https://github.com/django/django/commit/231de683d86374c2b74da2185efc6ddfb5eb3341. --- tests/authentication/test_authentication.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/authentication/test_authentication.py b/tests/authentication/test_authentication.py index a73e0d79c7..d771aaf8b4 100644 --- a/tests/authentication/test_authentication.py +++ b/tests/authentication/test_authentication.py @@ -1,5 +1,6 @@ import base64 +import django import pytest from django.conf import settings from django.contrib.auth.models import User @@ -218,7 +219,16 @@ def test_post_form_session_auth_passing_csrf(self): Ensure POSTing form over session authentication with CSRF token succeeds. Regression test for #6088 """ - from django.middleware.csrf import _get_new_csrf_token + # Remove this shim when dropping support for Django 2.2. + if django.VERSION < (3, 0): + from django.middleware.csrf import _get_new_csrf_token + else: + from django.middleware.csrf import ( + _get_new_csrf_string, _mask_cipher_secret + ) + + def _get_new_csrf_token(): + return _mask_cipher_secret(_get_new_csrf_string()) self.csrf_client.login(username=self.username, password=self.password) From f46c33e4e2c545e3fd90de692242c53a6fe6e7e9 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 22 Sep 2021 10:07:00 +0200 Subject: [PATCH 501/907] Fixed TestDefaultTZDateTimeField to allow multiple tzinfo implementations. zoneinfo was made the default time zone implementation in https://github.com/django/django/commit/306607d5b99b6eca6ae2c1e726d8eb32b9b2ca1b. --- tests/test_fields.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index a3b37584bc..7a5304a82a 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1464,15 +1464,24 @@ def setup_class(cls): cls.field = serializers.DateTimeField() cls.kolkata = pytz.timezone('Asia/Kolkata') + def assertUTC(self, tzinfo): + """ + Check UTC for datetime.timezone, ZoneInfo, and pytz tzinfo instances. + """ + assert ( + tzinfo is utc or + (getattr(tzinfo, "key", None) or getattr(tzinfo, "zone", None)) == "UTC" + ) + def test_default_timezone(self): - assert self.field.default_timezone() == utc + self.assertUTC(self.field.default_timezone()) def test_current_timezone(self): - assert self.field.default_timezone() == utc + self.assertUTC(self.field.default_timezone()) activate(self.kolkata) assert self.field.default_timezone() == self.kolkata deactivate() - assert self.field.default_timezone() == utc + self.assertUTC(self.field.default_timezone()) @pytest.mark.skipif(pytz is None, reason='pytz not installed') From c62e3ca764d90e7b8402cc4022cffad2a07fa5be Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 22 Sep 2021 10:08:14 +0200 Subject: [PATCH 502/907] Added Django 4.0 to test matrix. --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 25f8418219..6f49d373fc 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = {py35,py36,py37}-django22, {py36,py37,py38,py39}-django31, {py36,py37,py38,py39}-django32, - {py38,py39}-djangomain, + {py38,py39}-{django40,djangomain}, base,dist,docs, [travis:env] @@ -11,6 +11,7 @@ DJANGO = 2.2: django22 3.1: django31 3.2: django32 + 4.0: django40 main: djangomain [testenv] @@ -23,6 +24,7 @@ deps = django22: Django>=2.2,<3.0 django31: Django>=3.1,<3.2 django32: Django>=3.2,<4.0 + django40: Django>=4.0a1,<5.0 djangomain: https://github.com/django/django/archive/main.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 1fa5bc31c0c047eb2109929b17f8e53e84b7d40b Mon Sep 17 00:00:00 2001 From: Akhil Kokani Date: Mon, 27 Sep 2021 14:31:47 +0530 Subject: [PATCH 503/907] Update serializers.md (#8189) * Update serializers.md Removed unwanted word, "neither". * Update docs/api-guide/serializers.md Co-authored-by: Tom Christie --- docs/api-guide/serializers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index cf8525748c..377f732acd 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -116,7 +116,7 @@ Calling `.save()` will either create a new instance, or update an existing insta # .save() will update the existing `comment` instance. serializer = CommentSerializer(comment, data=data) -Both the `.create()` and `.update()` methods are optional. You can implement either neither, one, or both of them, depending on the use-case for your serializer class. +Both the `.create()` and `.update()` methods are optional. You can implement either none, one, or both of them, depending on the use-case for your serializer class. #### Passing additional attributes to `.save()` From 605a624da6958bd5e633a391eb65e72d0526f37c Mon Sep 17 00:00:00 2001 From: Phil Leggetter Date: Tue, 5 Oct 2021 14:02:34 +0100 Subject: [PATCH 504/907] Add PostHog as premium sponsors (#8193) * Add PostHog as premium sponsors * Adding 275x250 PostHog image --- README.md | 5 ++++- docs/img/premium/posthog-readme.png | Bin 0 -> 2402 bytes docs/index.md | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 docs/img/premium/posthog-readme.png diff --git a/README.md b/README.md index e3bcc2a1c2..8ba0a5e1d3 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,9 @@ The initial aim is to provide a single full-time position on REST framework. [![][esg-img]][esg-url] [![][retool-img]][retool-url] [![][bitio-img]][bitio-url] +[![][posthog-img]][posthog-url] -Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [ESG][esg-url], [Retool][retool-url], and [bit.io][bitio-url]. +Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [ESG][esg-url], [Retool][retool-url], [bit.io][bitio-url], and [PostHog][posthog-url]. --- @@ -195,6 +196,7 @@ Please see the [security policy][security-policy]. [esg-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/esg-readme.png [retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png [bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png +[posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png [sentry-url]: https://getsentry.com/welcome/ [stream-url]: https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer @@ -202,6 +204,7 @@ Please see the [security policy][security-policy]. [esg-url]: https://software.esg-usa.com/ [retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship [bitio-url]: https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship +[posthog-url]: https://posthog.com?utm_source=drf&utm_medium=sponsorship&utm_campaign=open-source-sponsorship [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 diff --git a/docs/img/premium/posthog-readme.png b/docs/img/premium/posthog-readme.png new file mode 100644 index 0000000000000000000000000000000000000000..9ca8b0ecf04599491790c65f6ac2382a1f45cb34 GIT binary patch literal 2402 zcmai$dpy(oAIIm?kqEUVMI1|COS+h2jFQXTx1B9IU5@L*+;d$jLMd%T+LDdA=Y9!U zO`&8o_mayIDdv_-A-UVxJbwN6dpyqL@p*i{ulM)+e7#=Z@At0{8Hcslxnuth5D2u> z$`W-31QG!Y^y+po!Cqx!8!Whp6OA#(AW(UN)Vj+y!7LJV#=-^wfTCV04r1+CN^`RT6k>eVYF zBcruOb2^VqMINXu5{N;Nm zqHJ#!fI!<5tx(2xq5U(1Q-n$wWLu}W2EJU6rPH6guvvy==MQlDuww4toQBkVvx=gB zS56+3F_shmk9B{jb$qEsJ>}{Re&V>w8v9)6amureTuaCH72!MtnmxS`rtIbfQ8e9$UWfTA8?9)t?!XzL_}Y0;vq!}N8n+<^&H^8AcCFY4@fNOCpmAEU**@Zl%mYXyJ-UgseWGAX^U|40*v+ zfXPrB@bc8vNIp9*MDikKcSEHwu|0PLVtGV@`6R2u#yu$wc8g+LOl>S2ad zNlq75jnbj!Li+}4Hk8eTq?md-io$%ik8Vif4I@P%8K6VEX@g_9_%%>-ohogqEmNrl zWWBQA=;}w`oukPTxMpbNgHp2WFZqNl@k?fK$Cs8z-!G%x@j@t#e39C>$l zgZtJ{NLKmfDJJjzpM)p-zow=T7c!IAXmXAAa3Y5p5EM;Z>c zOqrTzK1V;PP3h$!@uGlamb4CXM%Fvf9sS;PpLbw?dN;3?t`-CD<~==K$ffI8?KJBV z$O0~1mn?g`EfXfhy#hsnfXpWyk2r#9m7z44^L5I$0d_@TD)J6|dUee^N(9NF1QIdW z7c8JDBqEOj8U4gaCAneZ3DCwRAn``nY$?~Xx0?q80yo_Ds5fs2k_s70qc`RT8gvp` zTmuBwT)-mjhWgODYlM1lVq4QO!B6S!<=I0&irx##s3-*T9Gs{@SVZkJf7v0pGL<_l zXb8u|uZMka*Uc{xCpT@S)B;2g$(-4-#?`YOSJi-6!ACCYAY)CZ^w9FXyr)y%fk_sq zKox&aL8E}ZYC|6VH1U2bs(rYDQ1M*QV@F-69=a(k=n+(2Uo7kG{ah1=LObq9E{v5P z@wfuEW_yVEt^(zEYC-g_v**Icv2Uw@^!n2qc0lw;GgSyEajkc()wgzg zMwx4?mmd*ZSzv-sS*PrXmPwoHCu{J#mEU6f3zKHcfstl%v;N|g@enS*%Ae54Yj}uj z%lRoIKV@-jL<`+`t5!Mnl8aSt*vsldhPXT0zMwmyK)w{wBavcnpw(X>_Rr*HGi{_F z`~2dwy1Ho@*a$7Y4t*SX^aNdgA^0qU)M;vy`e}AuIy{*?sC@UQs*0i<6PuK90L3BJ z#nu2OGmkAxPU;{s;ymw-l>?7T5Yauir!94LW*z^DztyXG!f=i;5#G|L`JzA-TT^-W zHSb#W!vG3FzjIEZh*9}Hxs6!gW;WIx?N=MJ7)t$m?ZjAUQpgDMNjqCLrYR@e8Ul8~ z+Psy>pX(WZ4Gf-eKGYK;%Zx|s>8`Sae1=jgch4VabK)gvvD*?Qv2i2$B^`H8g(+00 z8erfHDDIDivGiM~LzTQwW+tc7($y{!-|=5XBhem8GYv~9_Zy_fot1a<^}{Qx3@~0n zS>`OXmrvV6GeX5woY=vOc{V)GTp#|EC$#lWGRBhLZsws0(C2FIb2}u9JexkenOO|F z=vsfrkhza|EXMqeXfR2?fdF^x_TMO+4y!qL_>}X}ju(jP#?Fv{$BUg`IpaUv(gW(Z zokCrLI|RF?YB?TgRZY3pmX*-GG86H1Km1GAOX<@~Si73-#j?8`_8RP`)TGykPIkqn zX{gu7UC#Qv&Yk?s{{C}%Q7(!+Rdy)#y!uRZldTj(uRUy_tCLN2kOmNxK6b8dBUqK0 z3rQnZ0wp{|!dKhD!%!+SW46E;%T$M|%&@3Z6X%=% E0-^VOCIA2c literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md index f000d2e093..54b7881072 100644 --- a/docs/index.md +++ b/docs/index.md @@ -72,10 +72,11 @@ continued development by **[signing up for a paid plan][funding]**.
    • Rollbar
    • Retool
    • bit.io
    • +
    • PostHog
    -*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), and [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship).* +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), and [bit.io](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship).* --- From 6ea95b6ad1bc0d4a4234a267b1ba32701878c6bb Mon Sep 17 00:00:00 2001 From: thetarby <45286577+thetarby@users.noreply.github.com> Date: Tue, 5 Oct 2021 17:33:55 +0300 Subject: [PATCH 505/907] Highlight `select_related` and `prefetch_related` usage in documentation (#7610) * docs updated to highlight use of select_related and prefetch related to avoid n+1 problems * Apply suggestions from code review cosmetic changes Co-authored-by: Xavier Ordoquy * cosmetic changes Co-authored-by: Xavier Ordoquy --- docs/api-guide/fields.md | 10 +++++++++- docs/api-guide/generic-views.md | 7 +++++++ docs/api-guide/relations.md | 31 +++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index b986009f9b..5b9688dcab 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -78,7 +78,14 @@ Defaults to `False` ### `source` -The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `URLField(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `EmailField(source='user.email')`. When serializing fields with dotted notation, it may be necessary to provide a `default` value if any object is not present or is empty during attribute traversal. +The name of the attribute that will be used to populate the field. May be a method that only takes a `self` argument, such as `URLField(source='get_absolute_url')`, or may use dotted notation to traverse attributes, such as `EmailField(source='user.email')`. + +When serializing fields with dotted notation, it may be necessary to provide a `default` value if any object is not present or is empty during attribute traversal. Beware of possible n+1 problems when using source attribute if you are accessing a relational orm model. For example: + + class CommentSerializer(serializers.Serializer): + email = serializers.EmailField(source="user.email") + +would require user object to be fetched from database when it is not prefetched. If that is not wanted, be sure to be using `prefetch_related` and `select_related` methods appropriately. For more information about the methods refer to [django documentation][django-docs-select-related]. The value `source='*'` has a special meaning, and is used to indicate that the entire object should be passed through to the field. This can be useful for creating nested representations, or for fields which require access to the complete object in order to determine the output representation. @@ -855,3 +862,4 @@ The [django-rest-framework-hstore][django-rest-framework-hstore] package provide [django-hstore]: https://github.com/djangonauts/django-hstore [python-decimal-rounding-modes]: https://docs.python.org/3/library/decimal.html#rounding-modes [django-current-timezone]: https://docs.djangoproject.com/en/stable/topics/i18n/timezones/#default-time-zone-and-current-time-zone +[django-docs-select-related]: https://docs.djangoproject.com/en/3.1/ref/models/querysets/#django.db.models.query.QuerySet.select_related diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index afc2cab563..fbafec93ad 100644 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -96,6 +96,12 @@ For example: user = self.request.user return user.accounts.all() +--- + +**Note:** If the serializer_class used in the generic view spans orm relations, leading to an n+1 problem, you could optimize your queryset in this method using `select_related` and `prefetch_related`. To get more information about n+1 problem and use cases of the mentioned methods refer to related section in [django documentation][django-docs-select-related]. + +--- + #### `get_object(self)` Returns an object instance that should be used for detail views. Defaults to using the `lookup_field` parameter to filter the base queryset. @@ -389,3 +395,4 @@ The following third party packages provide additional generic view implementatio [UpdateModelMixin]: #updatemodelmixin [DestroyModelMixin]: #destroymodelmixin [django-rest-multiple-models]: https://github.com/MattBroach/DjangoRestMultipleModels +[django-docs-select-related]: https://docs.djangoproject.com/en/3.1/ref/models/querysets/#django.db.models.query.QuerySet.select_related \ No newline at end of file diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index f444125cff..4547253b0a 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -17,6 +17,37 @@ Relational fields are used to represent model relationships. They can be applie --- +--- + +**Note:** REST Framework does not attempt to automatically optimize querysets passed to serializers in terms of `select_related` and `prefetch_related` since it would be too much magic. A serializer with a field spanning an orm relation through its source attribute could require an additional database hit to fetch related object from the database. It is the programmer's responsibility to optimize queries to avoid additional database hits which could occur while using such a serializer. + +For example, the following serializer would lead to a database hit each time evaluating the tracks field if it is not prefetched: + + class AlbumSerializer(serializers.ModelSerializer): + tracks = serializers.SlugRelatedField( + many=True, + read_only=True, + slug_field='title' + ) + + class Meta: + model = Album + fields = ['album_name', 'artist', 'tracks'] + + # For each album object, tracks should be fetched from database + qs = Album.objects.all() + print(AlbumSerializer(qs, many=True).data) + +If `AlbumSerializer` is used to serialize a fairly large queryset with `many=True` then it could be a serious performance problem. Optimizing the queryset passed to `AlbumSerializer` with: + + qs = Album.objects.prefetch_related('tracks') + # No additional database hits required + print(AlbumSerializer(qs, many=True).data) + +would solve the issue. + +--- + #### Inspecting relationships. When using the `ModelSerializer` class, serializer fields and relationships will be automatically generated for you. Inspecting these automatically generated fields can be a useful tool for determining how to customize the relationship style. From 53a0585dacea328ce74083f0da0dea10c4df03e5 Mon Sep 17 00:00:00 2001 From: Edmund <2623895+edmundlam@users.noreply.github.com> Date: Thu, 7 Oct 2021 04:09:00 -0400 Subject: [PATCH 506/907] Update permissions.md to fix garden path sentences (#8206) --- docs/api-guide/permissions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 6912c375c2..19bc0e66ae 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -24,9 +24,9 @@ A slightly less strict style of permission would be to allow full access to auth Permissions in REST framework are always defined as a list of permission classes. Before running the main body of the view each permission in the list is checked. -If any permission check fails an `exceptions.PermissionDenied` or `exceptions.NotAuthenticated` exception will be raised, and the main body of the view will not run. +If any permission check fails, an `exceptions.PermissionDenied` or `exceptions.NotAuthenticated` exception will be raised, and the main body of the view will not run. -When the permissions checks fail either a "403 Forbidden" or a "401 Unauthorized" response will be returned, according to the following rules: +When the permission checks fail, either a "403 Forbidden" or a "401 Unauthorized" response will be returned, according to the following rules: * The request was successfully authenticated, but permission was denied. *— An HTTP 403 Forbidden response will be returned.* * The request was not successfully authenticated, and the highest priority authentication class *does not* use `WWW-Authenticate` headers. *— An HTTP 403 Forbidden response will be returned.* From ddc5cd7e4b5f4bf250afd412b314af6728ea1726 Mon Sep 17 00:00:00 2001 From: Uzair Ali <72073401+uzair-ali10@users.noreply.github.com> Date: Thu, 7 Oct 2021 20:22:44 +0530 Subject: [PATCH 507/907] Imported Response (#8207) --- docs/api-guide/views.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-guide/views.md b/docs/api-guide/views.md index 2224c1f3a5..878a291b22 100644 --- a/docs/api-guide/views.md +++ b/docs/api-guide/views.md @@ -145,6 +145,7 @@ REST framework also allows you to work with regular function based views. It pr The core of this functionality is the `api_view` decorator, which takes a list of HTTP methods that your view should respond to. For example, this is how you would write a very simple view that just manually returns some data: from rest_framework.decorators import api_view + from rest_framework.response import Response @api_view() def hello_world(request): From 00cd4ef864a8bf6d6c90819a983017070f9f08a5 Mon Sep 17 00:00:00 2001 From: rgermain Date: Fri, 15 Oct 2021 16:13:20 +0200 Subject: [PATCH 508/907] add third packages nested-multipart-parser (#8208) --- docs/community/third-party-packages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 933244a6a9..e25421f503 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -106,6 +106,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [djangorestframework-msgpack][djangorestframework-msgpack] - Provides MessagePack renderer and parser support. * [djangorestframework-jsonapi][djangorestframework-jsonapi] - Provides a parser, renderer, serializers, and other tools to help build an API that is compliant with the jsonapi.org spec. * [djangorestframework-camel-case][djangorestframework-camel-case] - Provides camel case JSON renderers and parsers. +* [nested-multipart-parser][nested-multipart-parser] - Provides nested parser for http multipart request ### Renderers @@ -183,6 +184,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [wq.db.rest]: https://wq.io/docs/about-rest [djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack [djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case +[nested-multipart-parser]: https://github.com/remigermain/nested-multipart-parser [djangorestframework-csv]: https://github.com/mjumbewu/django-rest-framework-csv [drf_ujson2]: https://github.com/Amertz08/drf_ujson2 [rest-pandas]: https://github.com/wq/django-rest-pandas From 781890b7df88086d9cba07904e53db346ec4a715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lavoie?= Date: Mon, 8 Nov 2021 03:59:32 -0600 Subject: [PATCH 509/907] docs(api-guide-testing): Fix typo 'CRSF' and plural of word (#8238) --- docs/api-guide/testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/testing.md b/docs/api-guide/testing.md index 73de68a76b..62eb8dd1a5 100644 --- a/docs/api-guide/testing.md +++ b/docs/api-guide/testing.md @@ -234,7 +234,7 @@ If you're using `SessionAuthentication` then you'll need to include a CSRF token for any `POST`, `PUT`, `PATCH` or `DELETE` requests. You can do so by following the same flow that a JavaScript based client would use. -First make a `GET` request in order to obtain a CRSF token, then present that +First, make a `GET` request in order to obtain a CSRF token, then present that token in the following request. For example... @@ -259,7 +259,7 @@ With careful usage both the `RequestsClient` and the `CoreAPIClient` provide the ability to write test cases that can run either in development, or be run directly against your staging server or production environment. -Using this style to create basic tests of a few core piece of functionality is +Using this style to create basic tests of a few core pieces of functionality is a powerful way to validate your live service. Doing so may require some careful attention to setup and teardown to ensure that the tests run in a way that they do not directly affect customer data. From 060a3b632f6f6ff2f84235d1be5da55020c40ff3 Mon Sep 17 00:00:00 2001 From: Anton Burnashev Date: Wed, 10 Nov 2021 17:31:15 +0100 Subject: [PATCH 510/907] Docs: fix broken link (#8245) --- docs/api-guide/pagination.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 632af6a823..379c1975ad 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -312,7 +312,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 described 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 REST API documentation][github-traversing-with-pagination]. [cite]: https://docs.djangoproject.com/en/stable/topics/pagination/ [link-header]: ../img/link-header-pagination.png @@ -322,3 +322,4 @@ The [`django-rest-framework-link-header-pagination` package][drf-link-header-pag [drf-link-header-pagination]: https://github.com/tbeadle/django-rest-framework-link-header-pagination [disqus-cursor-api]: https://cra.mr/2011/03/08/building-cursors-for-the-disqus-api [float_cursor_pagination_example]: https://gist.github.com/keturn/8bc88525a183fd41c73ffb729b8865be#file-fpcursorpagination-py +[github-traversing-with-pagination]: https://docs.github.com/en/rest/guides/traversing-with-pagination From 0d5250cffada2ac250e24407953d4862d04d3dae Mon Sep 17 00:00:00 2001 From: Dmytro Litvinov Date: Mon, 15 Nov 2021 10:54:19 +0200 Subject: [PATCH 511/907] Fix link to installation of httpie (#8257) Right now httpie moved to "httpie" organization (https://github.com/httpie/httpie) and they don't have "installation" at their GitHub. Instead of that, they have "Getting started" section with link to "Installation instructions". --- docs/tutorial/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index ee839790f1..f4dcc5606c 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -225,4 +225,4 @@ If you want to get a more in depth understanding of how REST framework fits toge [image]: ../img/quickstart.png [tutorial]: 1-serialization.md [guide]: ../api-guide/requests.md -[httpie]: https://github.com/jakubroztocil/httpie#installation +[httpie]: https://httpie.io/docs#installation From 580bf45ccfd5c423a938729907d813f4862dca38 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Mon, 22 Nov 2021 16:48:58 +0600 Subject: [PATCH 512/907] test v4 beta 1 (#8222) * test v4 beta 1 * django 4 rc1 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 6f49d373fc..1ab5051953 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,7 @@ deps = django22: Django>=2.2,<3.0 django31: Django>=3.1,<3.2 django32: Django>=3.2,<4.0 - django40: Django>=4.0a1,<5.0 + django40: Django>=4.0rc1,<5.0 djangomain: https://github.com/django/django/archive/main.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 380ac8e79dd85e6798eb00a730b7d4c4c4a86ebd Mon Sep 17 00:00:00 2001 From: Yecine Megdiche Date: Mon, 6 Dec 2021 16:32:33 +0100 Subject: [PATCH 513/907] Remove old-style `super` calls (#8226) --- docs/api-guide/filtering.md | 2 +- docs/api-guide/serializers.md | 2 +- docs/tutorial/4-authentication-and-permissions.md | 2 +- rest_framework/fields.py | 2 ++ rest_framework/schemas/coreapi.py | 8 ++++---- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 3541388ca2..512acafbd9 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -241,7 +241,7 @@ To dynamically change search fields based on request content, it's possible to s 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) + return super().get_search_fields(view, request) For more details, see the [Django documentation][search-django-admin]. diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 377f732acd..4d032bd9ec 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -1095,7 +1095,7 @@ For example, if you wanted to be able to set which fields should be used by a se fields = kwargs.pop('fields', None) # Instantiate the superclass normally - super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) if fields is not None: # Drop any fields that are not specified in the `fields` argument. diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 79ce355c93..cb0321ea21 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -38,7 +38,7 @@ And now we can add a `.save()` method to our model class: formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options) self.highlighted = highlight(self.code, lexer, formatter) - super(Snippet, self).save(*args, **kwargs) + super().save(*args, **kwargs) When that's all done we'll need to update our database tables. Normally we'd create a database migration in order to do that, but for the purposes of this tutorial, let's just delete the database and start again. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 5cafed5556..d7e7816cee 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -1491,6 +1491,8 @@ def to_internal_value(self, data): self.fail('empty') return { + # Arguments for super() are needed because of scoping inside + # comprehensions. super(MultipleChoiceField, self).to_internal_value(item) for item in data } diff --git a/rest_framework/schemas/coreapi.py b/rest_framework/schemas/coreapi.py index 75ed5671af..179f0fa3c8 100644 --- a/rest_framework/schemas/coreapi.py +++ b/rest_framework/schemas/coreapi.py @@ -58,7 +58,7 @@ class LinkNode(OrderedDict): def __init__(self): self.links = [] self.methods_counter = Counter() - super(LinkNode, self).__init__() + super().__init__() def get_available_key(self, preferred_key): if preferred_key not in self: @@ -120,7 +120,7 @@ def __init__(self, title=None, url=None, description=None, patterns=None, urlcon assert coreapi, '`coreapi` must be installed for schema support.' assert coreschema, '`coreschema` must be installed for schema support.' - super(SchemaGenerator, self).__init__(title, url, description, patterns, urlconf) + super().__init__(title, url, description, patterns, urlconf) self.coerce_method_names = api_settings.SCHEMA_COERCE_METHOD_NAMES def get_links(self, request=None): @@ -346,7 +346,7 @@ def __init__(self, manual_fields=None): * `manual_fields`: list of `coreapi.Field` instances that will be added to auto-generated fields, overwriting on `Field.name` """ - super(AutoSchema, self).__init__() + super().__init__() if manual_fields is None: manual_fields = [] self._manual_fields = manual_fields @@ -587,7 +587,7 @@ def __init__(self, fields, description='', encoding=None): * `fields`: list of `coreapi.Field` instances. * `description`: String description for view. Optional. """ - super(ManualSchema, self).__init__() + super().__init__() assert all(isinstance(f, coreapi.Field) for f in fields), "`fields` must be a list of coreapi.Field instances" self._fields = fields self._description = description From dabf2216c33a365f80354d962177d72914e8936f Mon Sep 17 00:00:00 2001 From: Jaap Roes Date: Wed, 8 Dec 2021 15:30:34 +0100 Subject: [PATCH 514/907] Update django-cors-headers links (#8176) --- docs/topics/ajax-csrf-cors.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/ajax-csrf-cors.md b/docs/topics/ajax-csrf-cors.md index 646f3f5638..a65e3fdf8d 100644 --- a/docs/topics/ajax-csrf-cors.md +++ b/docs/topics/ajax-csrf-cors.md @@ -31,11 +31,11 @@ In order to make AJAX requests, you need to include CSRF token in the HTTP heade The best way to deal with CORS in REST framework is to add the required response headers in middleware. This ensures that CORS is supported transparently, without having to change any behavior in your views. -[Otto Yiu][ottoyiu] maintains the [django-cors-headers] package, which is known to work correctly with REST framework APIs. +[Adam Johnson][adamchainz] maintains the [django-cors-headers] package, which is known to work correctly with REST framework APIs. [cite]: https://blog.codinghorror.com/preventing-csrf-and-xsrf-attacks/ [csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) [csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax [cors]: https://www.w3.org/TR/cors/ -[ottoyiu]: https://github.com/ottoyiu/ -[django-cors-headers]: https://github.com/ottoyiu/django-cors-headers/ +[adamchainz]: https://github.com/adamchainz +[django-cors-headers]: https://github.com/adamchainz/django-cors-headers From 37b73ef46e8cf4cc746709542d7d26f6b152a26d Mon Sep 17 00:00:00 2001 From: Jeremy Langley Date: Wed, 8 Dec 2021 06:33:41 -0800 Subject: [PATCH 515/907] IsAdmin permissions changed to IsAdminUser (#8227) Documentation change to keep up with the code permission changes. Co-authored-by: Jeremy Langley --- docs/api-guide/viewsets.md | 2 +- docs/community/3.9-announcement.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index d4ab5a7317..4179725078 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -125,7 +125,7 @@ You may inspect these attributes to adjust behaviour based on the current action if self.action == 'list': permission_classes = [IsAuthenticated] else: - permission_classes = [IsAdmin] + permission_classes = [IsAdminUser] return [permission() for permission in permission_classes] ## Marking extra actions for routing diff --git a/docs/community/3.9-announcement.md b/docs/community/3.9-announcement.md index fee6e69096..d673fdd183 100644 --- a/docs/community/3.9-announcement.md +++ b/docs/community/3.9-announcement.md @@ -110,7 +110,7 @@ You can now compose permission classes using the and/or operators, `&` and `|`. For example... ```python -permission_classes = [IsAuthenticated & (ReadOnly | IsAdmin)] +permission_classes = [IsAuthenticated & (ReadOnly | IsAdminUser)] ``` If you're using custom permission classes then make sure that you are subclassing From 3a762d9aac526f26ea2e9798140cb99a1d3ebc18 Mon Sep 17 00:00:00 2001 From: Matthew Pull Date: Wed, 8 Dec 2021 14:35:06 +0000 Subject: [PATCH 516/907] Update permissions.md (#8260) I might just be misunderstanding something (always a strong possibility!), but it seems to me that the table on the Permissions page is slightly inaccurate. For `permission_classes`, wouldn't it have global-level permissions for list actions (rather than no permission control, as is currently listed)? --- docs/api-guide/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 19bc0e66ae..5d6462b45d 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -286,7 +286,7 @@ The following table lists the access restriction methods and the level of contro | | `queryset` | `permission_classes` | `serializer_class` | |------------------------------------|------------|----------------------|--------------------| -| Action: list | global | no | object-level* | +| Action: list | global | global | object-level* | | Action: create | no | global | object-level | | Action: retrieve | global | object-level | object-level | | Action: update | global | object-level | object-level | From b0d407fd6344e6be9a0f1374cf53cf7e5286b67f Mon Sep 17 00:00:00 2001 From: Alexander Klimenko Date: Wed, 8 Dec 2021 17:37:32 +0300 Subject: [PATCH 517/907] Made api_setting.UNICODE_JSON/ensure_ascii affecting json schema (#7991) --- rest_framework/renderers.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 5b7ba8a8c8..b0ddca2b59 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -1035,13 +1035,16 @@ class CoreAPIJSONOpenAPIRenderer(_BaseOpenAPIRenderer): media_type = 'application/vnd.oai.openapi+json' charset = None format = 'openapi-json' + ensure_ascii = not api_settings.UNICODE_JSON def __init__(self): assert coreapi, 'Using CoreAPIJSONOpenAPIRenderer, but `coreapi` is not installed.' def render(self, data, media_type=None, renderer_context=None): structure = self.get_structure(data) - return json.dumps(structure, indent=4).encode('utf-8') + return json.dumps( + structure, indent=4, + ensure_ascii=self.ensure_ascii).encode('utf-8') class OpenAPIRenderer(BaseRenderer): @@ -1065,6 +1068,9 @@ class JSONOpenAPIRenderer(BaseRenderer): charset = None encoder_class = encoders.JSONEncoder format = 'openapi-json' + ensure_ascii = not api_settings.UNICODE_JSON def render(self, data, media_type=None, renderer_context=None): - return json.dumps(data, cls=self.encoder_class, indent=2).encode('utf-8') + return json.dumps( + data, cls=self.encoder_class, indent=2, + ensure_ascii=self.ensure_ascii).encode('utf-8') From 47ee3fc9a999440e721424aa5100f9eb216f0096 Mon Sep 17 00:00:00 2001 From: Chen Wen Kang <23054115+cwkang1998@users.noreply.github.com> Date: Wed, 8 Dec 2021 22:38:42 +0800 Subject: [PATCH 518/907] Update docs related to coreapi to include deprecation notice (#8186) * Update docs related to coreapi to include deprecation notice * Update docs to use reference to version 3.10 release notes instead of 3.9 --- docs/coreapi/7-schemas-and-client-libraries.md | 11 +++++++++++ docs/coreapi/from-documenting-your-api.md | 11 +++++++++++ docs/coreapi/index.md | 4 ++-- docs/coreapi/schemas.md | 8 ++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/docs/coreapi/7-schemas-and-client-libraries.md b/docs/coreapi/7-schemas-and-client-libraries.md index 203d81ea5d..d95019dab6 100644 --- a/docs/coreapi/7-schemas-and-client-libraries.md +++ b/docs/coreapi/7-schemas-and-client-libraries.md @@ -1,5 +1,16 @@ # Tutorial 7: Schemas & client libraries +---- + +**DEPRECATION NOTICE:** Use of CoreAPI-based schemas were deprecated with the introduction of native OpenAPI-based schema generation as of Django REST Framework v3.10. See the [Version 3.10 Release Announcement](../community/3.10-announcement.md) for more details. + +If you are looking for information regarding schemas, you might want to look at these updated resources: + +1. [Schema](../api-guide/schemas.md) +2. [Documenting your API](../topics/documenting-your-api.md) + +---- + A schema is a machine-readable document that describes the available API endpoints, their URLS, and what operations they support. diff --git a/docs/coreapi/from-documenting-your-api.md b/docs/coreapi/from-documenting-your-api.md index 604dfa6686..65ad71c7a7 100644 --- a/docs/coreapi/from-documenting-your-api.md +++ b/docs/coreapi/from-documenting-your-api.md @@ -1,6 +1,17 @@ ## Built-in API documentation +---- + +**DEPRECATION NOTICE:** Use of CoreAPI-based schemas were deprecated with the introduction of native OpenAPI-based schema generation as of Django REST Framework v3.10. See the [Version 3.10 Release Announcement](../community/3.10-announcement.md) for more details. + +If you are looking for information regarding schemas, you might want to look at these updated resources: + +1. [Schema](../api-guide/schemas.md) +2. [Documenting your API](../topics/documenting-your-api.md) + +---- + The built-in API documentation includes: * Documentation of API endpoints. diff --git a/docs/coreapi/index.md b/docs/coreapi/index.md index 9195eb33e4..dbcb115840 100644 --- a/docs/coreapi/index.md +++ b/docs/coreapi/index.md @@ -1,8 +1,8 @@ # Legacy CoreAPI Schemas Docs -Use of CoreAPI-based schemas were deprecated with the introduction of native OpenAPI-based schema generation in Django REST Framework v3.10. +Use of CoreAPI-based schemas were deprecated with the introduction of native OpenAPI-based schema generation as of Django REST Framework v3.10. -See the [Version 3.10 Release Announcement](/community/3.10-announcement.md) for more details. +See the [Version 3.10 Release Announcement](../community/3.10-announcement.md) for more details. ---- diff --git a/docs/coreapi/schemas.md b/docs/coreapi/schemas.md index 653105a7a1..9f1482d2d8 100644 --- a/docs/coreapi/schemas.md +++ b/docs/coreapi/schemas.md @@ -2,6 +2,14 @@ source: schemas.py # Schemas +---- + +**DEPRECATION NOTICE:** Use of CoreAPI-based schemas were deprecated with the introduction of native OpenAPI-based schema generation as of Django REST Framework v3.10. See the [Version 3.10 Release Announcement](../community/3.10-announcement.md) for more details. + +You are probably looking for [this page](../api-guide/schemas.md) if you want latest information regarding schemas. + +---- + > A machine-readable [schema] describes what resources are available via the API, what their URLs are, how they are represented and what operations they support. > > — Heroku, [JSON Schema for the Heroku Platform API][cite] From 6e0cb8a7aa2db1694b46fa3eff5a5271fd7d828e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 8 Dec 2021 14:53:06 +0000 Subject: [PATCH 519/907] Add CryptAPI sponsorship (#8283) --- README.md | 5 ++++- docs/img/premium/cryptapi-readme.png | Bin 0 -> 17864 bytes 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docs/img/premium/cryptapi-readme.png diff --git a/README.md b/README.md index 8ba0a5e1d3..4b089469f6 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,9 @@ The initial aim is to provide a single full-time position on REST framework. [![][retool-img]][retool-url] [![][bitio-img]][bitio-url] [![][posthog-img]][posthog-url] +[![][cryptapi-img]][cryptapi-url] -Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [ESG][esg-url], [Retool][retool-url], [bit.io][bitio-url], and [PostHog][posthog-url]. +Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [ESG][esg-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], and [CryptAPI][cryptapi-url]. --- @@ -197,6 +198,7 @@ Please see the [security policy][security-policy]. [retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png [bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png [posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png +[posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png [sentry-url]: https://getsentry.com/welcome/ [stream-url]: https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer @@ -205,6 +207,7 @@ Please see the [security policy][security-policy]. [retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship [bitio-url]: https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship [posthog-url]: https://posthog.com?utm_source=drf&utm_medium=sponsorship&utm_campaign=open-source-sponsorship +[cryptapi-url]: https://cryptapi.io [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 diff --git a/docs/img/premium/cryptapi-readme.png b/docs/img/premium/cryptapi-readme.png new file mode 100644 index 0000000000000000000000000000000000000000..163f6a9ea20f17909e6e38537a627f19e5766a66 GIT binary patch literal 17864 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 G_5WXyfz+@7 literal 0 HcmV?d00001 From d1bab643ab249f2c18cc0e6047033ca726c51605 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 8 Dec 2021 14:53:45 +0000 Subject: [PATCH 520/907] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b089469f6..8875d650ef 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ Please see the [security policy][security-policy]. [retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png [bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png [posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png -[posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png +[cryptapi-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png [sentry-url]: https://getsentry.com/welcome/ [stream-url]: https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer From c05998f5ddedec7c8c012a9be08aa41130a46b75 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 8 Dec 2021 15:11:55 +0000 Subject: [PATCH 521/907] Add CryptAPI to docs homepage --- docs/index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 54b7881072..a86349df13 100644 --- a/docs/index.md +++ b/docs/index.md @@ -72,11 +72,12 @@ continued development by **[signing up for a paid plan][funding]**.

  • Rollbar
  • Retool
  • bit.io
  • -
  • PostHog
  • +
  • PostHog
  • +
  • CryptAPI
  • -*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), and [bit.io](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship).* +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), and [CryptAPI](https://cryptapi.io).* --- From 1cb3fa2e81ae33d52f4dcfc8b873f7b8093acd50 Mon Sep 17 00:00:00 2001 From: Jet Li Date: Fri, 10 Dec 2021 17:31:05 +0800 Subject: [PATCH 522/907] Test Django 4.0 (#8280) * Test Django 4.0 Django 4.0 released today. * Test Django 4.0 * Test Django 4.0 * Test Django 4.0 --- README.md | 2 +- docs/index.md | 2 +- setup.py | 1 + tox.ini | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8875d650ef..7a899cdb41 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements * Python (3.5, 3.6, 3.7, 3.8, 3.9) -* Django (2.2, 3.0, 3.1, 3.2) +* Django (2.2, 3.0, 3.1, 3.2, 4.0) We **highly recommend** and only officially support the latest patch release of each Python and Django series. diff --git a/docs/index.md b/docs/index.md index a86349df13..294e1e6d37 100644 --- a/docs/index.md +++ b/docs/index.md @@ -86,7 +86,7 @@ continued development by **[signing up for a paid plan][funding]**. REST framework requires the following: * Python (3.5, 3.6, 3.7, 3.8, 3.9) -* Django (2.2, 3.0, 3.1, 3.2) +* Django (2.2, 3.0, 3.1, 3.2, 4.0) We **highly recommend** and only officially support the latest patch release of each Python and Django series. diff --git a/setup.py b/setup.py index b8e220cb43..394845e148 100755 --- a/setup.py +++ b/setup.py @@ -93,6 +93,7 @@ def get_version(package): 'Framework :: Django :: 3.0', 'Framework :: Django :: 3.1', 'Framework :: Django :: 3.2', + 'Framework :: Django :: 4.0', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', diff --git a/tox.ini b/tox.ini index 1ab5051953..f2ae6cd6bd 100644 --- a/tox.ini +++ b/tox.ini @@ -24,7 +24,7 @@ deps = django22: Django>=2.2,<3.0 django31: Django>=3.1,<3.2 django32: Django>=3.2,<4.0 - django40: Django>=4.0rc1,<5.0 + django40: Django>=4.0,<5.0 djangomain: https://github.com/django/django/archive/main.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 16ca0c24d3d6fdf5663f761cadc2d4b1baf8acc8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 10 Dec 2021 11:53:48 +0000 Subject: [PATCH 523/907] Add 3.10 to tox.ini and setup.py --- setup.py | 2 +- tox.ini | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 394845e148..c18975ef29 100755 --- a/setup.py +++ b/setup.py @@ -99,11 +99,11 @@ def get_version(package): 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3 :: Only', 'Topic :: Internet :: WWW/HTTP', ], diff --git a/tox.ini b/tox.ini index f2ae6cd6bd..b7d62e0814 100644 --- a/tox.ini +++ b/tox.ini @@ -2,8 +2,8 @@ envlist = {py35,py36,py37}-django22, {py36,py37,py38,py39}-django31, - {py36,py37,py38,py39}-django32, - {py38,py39}-{django40,djangomain}, + {py36,py37,py38,py39,py310}-django32, + {py38,py39,py310}-{django40,djangomain}, base,dist,docs, [travis:env] @@ -54,3 +54,6 @@ ignore_outcome = true [testenv:py39-djangomain] ignore_outcome = true + +[testenv:py310-djangomain] +ignore_outcome = true From 217b0bf3af0b9332023113aab40283ea6929842d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 10 Dec 2021 12:04:27 +0000 Subject: [PATCH 524/907] Add Python 3.10 to test matrix (#8287) * Add Python 3.10 to test matrix * Update README, docs homepage to properly reflect Python versions that we test against --- .github/workflows/main.yml | 1 + README.md | 2 +- docs/index.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fc166c434d..42fee2a124 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -18,6 +18,7 @@ jobs: - '3.7' - '3.8' - '3.9' + - '3.10' steps: - uses: actions/checkout@v2 diff --git a/README.md b/README.md index 7a899cdb41..18d1364c69 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements -* Python (3.5, 3.6, 3.7, 3.8, 3.9) +* Python (3.6, 3.7, 3.8, 3.9, 3.10) * Django (2.2, 3.0, 3.1, 3.2, 4.0) We **highly recommend** and only officially support the latest patch release of diff --git a/docs/index.md b/docs/index.md index 294e1e6d37..2954f793ac 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,7 +85,7 @@ continued development by **[signing up for a paid plan][funding]**. REST framework requires the following: -* Python (3.5, 3.6, 3.7, 3.8, 3.9) +* Python (3.6, 3.7, 3.8, 3.9, 3.10) * Django (2.2, 3.0, 3.1, 3.2, 4.0) We **highly recommend** and only officially support the latest patch release of From 773f479719755193af8b0b7cb9915893738df152 Mon Sep 17 00:00:00 2001 From: Paolo Melchiorre Date: Fri, 10 Dec 2021 16:31:01 +0100 Subject: [PATCH 525/907] Python/Django compatibility updates (#8288) * Update python and django versions in tox.ini * Update python requires in setup.py * Update tox.ini Co-authored-by: Tom Christie --- setup.py | 2 +- tox.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index c18975ef29..210cc9ed0f 100755 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ def get_version(package): packages=find_packages(exclude=['tests*']), include_package_data=True, install_requires=["django>=2.2", "pytz"], - python_requires=">=3.5", + python_requires=">=3.6", zip_safe=False, classifiers=[ 'Development Status :: 5 - Production/Stable', diff --git a/tox.ini b/tox.ini index b7d62e0814..a41176d72f 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - {py35,py36,py37}-django22, + {py36,py37,py38,py39}-django22, {py36,py37,py38,py39}-django31, {py36,py37,py38,py39,py310}-django32, {py38,py39,py310}-{django40,djangomain}, From ba25869045f203c62a0a9ddf5c54b7f882d8308c Mon Sep 17 00:00:00 2001 From: Alexander Clausen Date: Mon, 13 Dec 2021 09:57:55 +0100 Subject: [PATCH 526/907] Fix `REQUIRED_PYTHON` in setup.py (#8292) Just a left-over from #8288 to sync the "Unsupported Python version" message with `python_requires`. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 210cc9ed0f..3c3761c866 100755 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ from setuptools import find_packages, setup CURRENT_PYTHON = sys.version_info[:2] -REQUIRED_PYTHON = (3, 5) +REQUIRED_PYTHON = (3, 6) # This check and everything above must remain compatible with Python 2.7. if CURRENT_PYTHON < REQUIRED_PYTHON: From d0bb4d877f95ea85446b8fc66d247f01337897d2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 13 Dec 2021 09:33:03 +0000 Subject: [PATCH 527/907] Tweak test_description (#8293) --- tests/test_description.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_description.py b/tests/test_description.py index 3b7d95e0a1..363ad6513e 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -23,7 +23,7 @@ # hash style header # -``` json +```json [{ "alpha": 1, "beta": "this is a string" @@ -107,7 +107,7 @@ class MockView(APIView): # hash style header # - ``` json + ```json [{ "alpha": 1, "beta": "this is a string" From 9c97946531b85858fcee5df56240de6d29571da2 Mon Sep 17 00:00:00 2001 From: tim-mccurrach <34194722+tim-mccurrach@users.noreply.github.com> Date: Mon, 13 Dec 2021 13:08:40 +0000 Subject: [PATCH 528/907] Make api_view respect standard wrapper assignments (#8291) --- rest_framework/decorators.py | 20 ++++---------------- tests/test_decorators.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 30b9d84d4e..7ba43d37c8 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -7,6 +7,7 @@ methods on viewsets that should be included by routers. """ import types +from functools import update_wrapper from django.forms.utils import pretty_name @@ -22,18 +23,8 @@ def api_view(http_method_names=None): def decorator(func): - WrappedAPIView = type( - 'WrappedAPIView', - (APIView,), - {'__doc__': func.__doc__} - ) - - # Note, the above allows us to set the docstring. - # It is the equivalent of: - # - # class WrappedAPIView(APIView): - # pass - # WrappedAPIView.__doc__ = func.doc <--- Not possible to do this + class WrappedAPIView(APIView): + pass # api_view applied without (method_names) assert not(isinstance(http_method_names, types.FunctionType)), \ @@ -52,9 +43,6 @@ def handler(self, *args, **kwargs): for method in http_method_names: setattr(WrappedAPIView, method.lower(), handler) - WrappedAPIView.__name__ = func.__name__ - WrappedAPIView.__module__ = func.__module__ - WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes', APIView.renderer_classes) @@ -73,7 +61,7 @@ def handler(self, *args, **kwargs): WrappedAPIView.schema = getattr(func, 'schema', APIView.schema) - return WrappedAPIView.as_view() + return update_wrapper(WrappedAPIView.as_view(), func) return decorator diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 99ba13e60c..116d6f1be4 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -162,6 +162,16 @@ def view(request): assert isinstance(view.cls.schema, CustomSchema) + def test_wrapper_assignments(self): + @api_view(["GET"]) + def test_view(request): + """example docstring""" + pass + + assert test_view.__name__ == "test_view" + assert test_view.__doc__ == "example docstring" + assert test_view.__qualname__ == "DecoratorTestCase.test_wrapper_assignments..test_view" + class ActionDecoratorTestCase(TestCase): From 7a84dc749cbc0db106c1ad40d7776ec307d08559 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 13 Dec 2021 13:10:17 +0000 Subject: [PATCH 529/907] Version 3.13 (#8285) * Version 3.12.5 * Version 3.13 * Version 3.13 --- docs/community/3.13-announcement.md | 55 +++++++++++++++++++++++++++++ docs/community/release-notes.md | 16 +++++++++ mkdocs.yml | 1 + rest_framework/__init__.py | 2 +- 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 docs/community/3.13-announcement.md diff --git a/docs/community/3.13-announcement.md b/docs/community/3.13-announcement.md new file mode 100644 index 0000000000..e2c1fefa64 --- /dev/null +++ b/docs/community/3.13-announcement.md @@ -0,0 +1,55 @@ + + +# Django REST framework 3.13 + +## Django 4.0 support + +The latest release now fully supports Django 4.0. + +Our requirements are now: + +* Python 3.6+ +* Django 4.0, 3.2, 3.1, 2.2 (LTS) + +## Fields arguments are now keyword-only + +When instantiating fields on serializers, you should always use keyword arguments, +such as `serializers.CharField(max_length=200)`. This has always been the case, +and all the examples that we have in the documentation use keyword arguments, +rather than positional arguments. + +From REST framework 3.13 onwards, this is now *explicitly enforced*. + +The most feasible cases where users might be accidentally omitting the keyword arguments +are likely in the composite fields, `ListField` and `DictField`. For instance... + +```python +aliases = serializers.ListField(serializers.CharField()) +``` + +They must now use the more explicit keyword argument style... + +```python +aliases = serializers.ListField(child=serializers.CharField()) +``` + +This change has been made because using positional arguments here *does not* result in the expected behaviour. + +See Pull Request [#7632](https://github.com/encode/django-rest-framework/pull/7632) for more details. diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index baeeaf8741..d3e9dd7cc2 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -34,6 +34,22 @@ You can determine your currently installed version using `pip show`: --- +## 3.13.x series + +### 3.13.0 + +Date: 13th December 2021 + +* Django 4.0 compatability. [#8178] +* Add `max_length` and `min_length` options to `ListSerializer`. [#8165] +* Add `get_request_serializer` and `get_response_serializer` hooks to `AutoSchema`. [#7424] +* Fix OpenAPI representation of null-able read only fields. [#8116] +* Respect `UNICODE_JSON` setting in API schema outputs. [#7991] +* Fix for `RemoteUserAuthentication`. [#7158] +* Make Field constructors keyword-only. [#7632] + +--- + ## 3.12.x series ### 3.12.4 diff --git a/mkdocs.yml b/mkdocs.yml index 573898bca0..439245a8d2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -66,6 +66,7 @@ nav: - 'Contributing to REST framework': 'community/contributing.md' - 'Project management': 'community/project-management.md' - 'Release Notes': 'community/release-notes.md' + - '3.13 Announcement': 'community/3.13-announcement.md' - '3.12 Announcement': 'community/3.12-announcement.md' - '3.11 Announcement': 'community/3.11-announcement.md' - '3.10 Announcement': 'community/3.10-announcement.md' diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 0c75d3617e..88d86c03e5 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -10,7 +10,7 @@ import django __title__ = 'Django REST framework' -__version__ = '3.12.4' +__version__ = '3.13.0' __author__ = 'Tom Christie' __license__ = 'BSD 3-Clause' __copyright__ = 'Copyright 2011-2019 Encode OSS Ltd' From b3beb15b00ce8b251205aa5a344d6e6ddfac74a8 Mon Sep 17 00:00:00 2001 From: Jameel Al-Aziz <247849+jalaziz@users.noreply.github.com> Date: Mon, 13 Dec 2021 06:03:09 -0800 Subject: [PATCH 530/907] Fix CursorPagination parameter schema type (#7708) The CursorPagination's cursor query parameter expects a string and not an integer. Fixes #7691 --- rest_framework/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index dc120d8e86..e815d8d5cf 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -961,7 +961,7 @@ def get_schema_operation_parameters(self, view): 'in': 'query', 'description': force_str(self.cursor_query_description), 'schema': { - 'type': 'integer', + 'type': 'string', }, } ] From f3bb5b9cdc7cb53c27535e4817112e6d2eba08a0 Mon Sep 17 00:00:00 2001 From: Abhineet Date: Mon, 13 Dec 2021 19:34:04 +0530 Subject: [PATCH 531/907] Add missing commas in pagination response samples (#8185) --- docs/api-guide/pagination.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 379c1975ad..aadc1bbc7f 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -78,7 +78,7 @@ This pagination style accepts a single number page number in the request query p HTTP 200 OK { - "count": 1023 + "count": 1023, "next": "https://api.example.org/accounts/?page=5", "previous": "https://api.example.org/accounts/?page=3", "results": [ @@ -126,7 +126,7 @@ This pagination style mirrors the syntax used when looking up multiple database HTTP 200 OK { - "count": 1023 + "count": 1023, "next": "https://api.example.org/accounts/?limit=100&offset=500", "previous": "https://api.example.org/accounts/?limit=100&offset=300", "results": [ From 2d52c9e8bca06d5427596ea7f73b8294aa984036 Mon Sep 17 00:00:00 2001 From: juliangeissler <81534590+juliangeissler@users.noreply.github.com> Date: Mon, 13 Dec 2021 15:08:55 +0100 Subject: [PATCH 532/907] Update Tutorial - quickstart (#7943) * Tutorial - Adjust quickstart Add asgi.py file Also add paragraph for the second user, which is later displayed * Tutorial - Adjust quickstart It seems that there is no CLI command to easily create a user Remove the second user from the Markdown Image next * Tutorial - quickstart - Update browsable API image Only show the admin user New Image has similar width and is compressed --- docs/img/quickstart.png | Bin 39050 -> 27279 bytes docs/tutorial/quickstart.md | 13 +------------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/docs/img/quickstart.png b/docs/img/quickstart.png index 5006d60fe0c3f89723524ae5e8d45a115cd8e6d8..e3581f308b75b53443bab679e6ceb5a38b3776fa 100644 GIT binary patch literal 27279 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≥1YOF3HI=x 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 aw|wZ70#vl|AAy6>q{S7)%0%D1`#%6AGMOC! diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index f4dcc5606c..4fa2fbbe52 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -42,6 +42,7 @@ The project layout should look like: ./tutorial/quickstart/models.py ./tutorial/quickstart/tests.py ./tutorial/quickstart/views.py + ./tutorial/asgi.py ./tutorial/settings.py ./tutorial/urls.py ./tutorial/wsgi.py @@ -176,12 +177,6 @@ We can now access our API, both from the command-line, using tools like `curl`.. "url": "http://127.0.0.1:8000/users/1/", "username": "admin" }, - { - "email": "tom@example.com", - "groups": [], - "url": "http://127.0.0.1:8000/users/2/", - "username": "tom" - } ] } @@ -202,12 +197,6 @@ Or using the [httpie][httpie], command line tool... "url": "http://localhost:8000/users/1/", "username": "paul" }, - { - "email": "tom@example.com", - "groups": [], - "url": "http://127.0.0.1:8000/users/2/", - "username": "tom" - } ] } From a780e80debac0d12bb62fbb0ed98635e601e76de Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 15 Dec 2021 15:16:38 +0000 Subject: [PATCH 533/907] Revert "Make api_view respect standard wrapper assignments (#8291)" (#8297) This reverts commit 9c97946531b85858fcee5df56240de6d29571da2. --- rest_framework/decorators.py | 20 ++++++++++++++++---- tests/test_decorators.py | 10 ---------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 7ba43d37c8..30b9d84d4e 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -7,7 +7,6 @@ methods on viewsets that should be included by routers. """ import types -from functools import update_wrapper from django.forms.utils import pretty_name @@ -23,8 +22,18 @@ def api_view(http_method_names=None): def decorator(func): - class WrappedAPIView(APIView): - pass + WrappedAPIView = type( + 'WrappedAPIView', + (APIView,), + {'__doc__': func.__doc__} + ) + + # Note, the above allows us to set the docstring. + # It is the equivalent of: + # + # class WrappedAPIView(APIView): + # pass + # WrappedAPIView.__doc__ = func.doc <--- Not possible to do this # api_view applied without (method_names) assert not(isinstance(http_method_names, types.FunctionType)), \ @@ -43,6 +52,9 @@ def handler(self, *args, **kwargs): for method in http_method_names: setattr(WrappedAPIView, method.lower(), handler) + WrappedAPIView.__name__ = func.__name__ + WrappedAPIView.__module__ = func.__module__ + WrappedAPIView.renderer_classes = getattr(func, 'renderer_classes', APIView.renderer_classes) @@ -61,7 +73,7 @@ def handler(self, *args, **kwargs): WrappedAPIView.schema = getattr(func, 'schema', APIView.schema) - return update_wrapper(WrappedAPIView.as_view(), func) + return WrappedAPIView.as_view() return decorator diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 116d6f1be4..99ba13e60c 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -162,16 +162,6 @@ def view(request): assert isinstance(view.cls.schema, CustomSchema) - def test_wrapper_assignments(self): - @api_view(["GET"]) - def test_view(request): - """example docstring""" - pass - - assert test_view.__name__ == "test_view" - assert test_view.__doc__ == "example docstring" - assert test_view.__qualname__ == "DecoratorTestCase.test_wrapper_assignments..test_view" - class ActionDecoratorTestCase(TestCase): From f4cf0260bf3c9323e798325702be690ca25949ca Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 15 Dec 2021 15:18:24 +0000 Subject: [PATCH 534/907] Version 3.13.1 --- docs/community/release-notes.md | 6 ++++++ rest_framework/__init__.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index d3e9dd7cc2..8629e4fee7 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -36,6 +36,12 @@ You can determine your currently installed version using `pip show`: ## 3.13.x series +### 3.13.1 + +Date: 15th December 2021 + +* Revert schema naming changes with function based `@api_view`. [#8297] + ### 3.13.0 Date: 13th December 2021 diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 88d86c03e5..8b0679ef95 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -10,7 +10,7 @@ import django __title__ = 'Django REST framework' -__version__ = '3.13.0' +__version__ = '3.13.1' __author__ = 'Tom Christie' __license__ = 'BSD 3-Clause' __copyright__ = 'Copyright 2011-2019 Encode OSS Ltd' From 45082b39368729caa70534dde11b0788ef186a37 Mon Sep 17 00:00:00 2001 From: Guilouf Date: Fri, 17 Dec 2021 16:20:14 +0100 Subject: [PATCH 535/907] Fixed missing "fields" meta argument in docs (#8243) * Fixed missing "fields" meta argument in docs, leading to an assertion error * Update docs/api-guide/serializers.md Co-authored-by: Tom Christie Co-authored-by: Tom Christie --- docs/api-guide/serializers.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md index 4d032bd9ec..da57ac0a49 100644 --- a/docs/api-guide/serializers.md +++ b/docs/api-guide/serializers.md @@ -524,6 +524,7 @@ You can add extra fields to a `ModelSerializer` or override the default fields b class Meta: model = Account + fields = ['url', 'groups'] Extra fields can correspond to any property or callable on the model. From bce9df9b5e0f54a6076519835393fea59accb40c Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Wed, 22 Dec 2021 15:08:58 +0000 Subject: [PATCH 536/907] Make ReturnDict support dict union operators on Python 3.9 and later (#8302) Fixes issue #8301 --- rest_framework/utils/serializer_helpers.py | 17 +++++++++++++++++ tests/test_serializer.py | 22 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index 4cd2ada314..54068f5fb0 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -1,3 +1,4 @@ +import sys from collections import OrderedDict from collections.abc import Mapping, MutableMapping @@ -28,6 +29,22 @@ def __reduce__(self): # but preserve the raw data. return (dict, (dict(self),)) + if sys.version_info >= (3, 9): + # These are basically copied from OrderedDict, with `serializer` added. + def __or__(self, other): + if not isinstance(other, dict): + return NotImplemented + new = self.__class__(self, serializer=self.serializer) + new.update(other) + return new + + def __ror__(self, other): + if not isinstance(other, dict): + return NotImplemented + new = self.__class__(other, serializer=self.serializer) + new.update(self) + return new + class ReturnList(list): """ diff --git a/tests/test_serializer.py b/tests/test_serializer.py index afefd70e1c..c4c29ba4ad 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -740,3 +740,25 @@ class TestSerializer(A, B): 'f4': serializers.CharField, 'f5': serializers.CharField, } + + +class Test8301Regression: + @pytest.mark.skipif( + sys.version_info < (3, 9), + reason="dictionary union operator requires Python 3.9 or higher", + ) + def test_ReturnDict_merging(self): + # Serializer.data returns ReturnDict, this is essentially a test for that. + + class TestSerializer(serializers.Serializer): + char = serializers.CharField() + + s = TestSerializer(data={'char': 'x'}) + assert s.is_valid() + assert s.data | {} == {'char': 'x'} + assert s.data | {'other': 'y'} == {'char': 'x', 'other': 'y'} + assert {} | s.data == {'char': 'x'} + assert {'other': 'y'} | s.data == {'char': 'x', 'other': 'y'} + + assert (s.data | {}).__class__ == s.data.__class__ + assert ({} | s.data).__class__ == s.data.__class__ From 5b2abbed25ef41c4cc1b3806037c6bd7631db327 Mon Sep 17 00:00:00 2001 From: kaushik kothiya <46051127+kaushikk25@users.noreply.github.com> Date: Wed, 22 Dec 2021 20:47:57 +0530 Subject: [PATCH 537/907] Correct variable name (#8306) urlpatterns name variable name in space remove. --- docs/api-guide/format-suffixes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md index dfdf24953d..da4a1af78b 100644 --- a/docs/api-guide/format-suffixes.md +++ b/docs/api-guide/format-suffixes.md @@ -62,7 +62,7 @@ Also note that `format_suffix_patterns` does not support descending into `includ If using the `i18n_patterns` function provided by Django, as well as `format_suffix_patterns` you should make sure that the `i18n_patterns` function is applied as the final, or outermost function. For example: - url patterns = [ + urlpatterns = [ … ] From f9ccbad4d910d19e4298300ee90467051fc08f47 Mon Sep 17 00:00:00 2001 From: Suntae Kim Date: Thu, 6 Jan 2022 22:55:44 +0900 Subject: [PATCH 538/907] minor update on tutorial serialization (#8323) --- docs/tutorial/1-serialization.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 908b7474a0..67746c517e 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -45,7 +45,7 @@ We'll need to add our new `snippets` app and the `rest_framework` app to `INSTAL INSTALLED_APPS = [ ... 'rest_framework', - 'snippets.apps.SnippetsConfig', + 'snippets', ] Okay, we're ready to roll. @@ -77,7 +77,7 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni We'll also need to create an initial migration for our snippet model, and sync the database for the first time. python manage.py makemigrations snippets - python manage.py migrate + python manage.py migrate snippets ## Creating a Serializer class @@ -307,8 +307,8 @@ Quit out of the shell... Validating models... 0 errors found - Django version 1.11, using settings 'tutorial.settings' - Development server is running at http://127.0.0.1:8000/ + Django version 4.0,1 using settings 'tutorial.settings' + Starting Development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. In another terminal window, we can test the server. From c5be86a6dbf3d21b00a296af5994fa075826bf0b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 11 Jan 2022 13:01:25 +0000 Subject: [PATCH 539/907] Update sponsor URL (#8328) * Update sponsor URL * Update index.md --- README.md | 2 +- docs/index.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 18d1364c69..d8fd83d672 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ Please see the [security policy][security-policy]. [cryptapi-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png [sentry-url]: https://getsentry.com/welcome/ -[stream-url]: https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer +[stream-url]: https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage [rollbar-url]: https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial [esg-url]: https://software.esg-usa.com/ [retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship diff --git a/docs/index.md b/docs/index.md index 2954f793ac..4871ba4587 100644 --- a/docs/index.md +++ b/docs/index.md @@ -67,7 +67,7 @@ continued development by **[signing up for a paid plan][funding]**.

    -*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=sponsorship&utm_content=developer), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), and [CryptAPI](https://cryptapi.io).* +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), and [CryptAPI](https://cryptapi.io).* --- From 94eb804abe5d414d59b3a84332e93fbb58811e9c Mon Sep 17 00:00:00 2001 From: Shivendra Pratap Kushwaha <33164379+kushshiv@users.noreply.github.com> Date: Mon, 17 Jan 2022 15:11:23 +0530 Subject: [PATCH 540/907] Update filtering.md (#8331) --- docs/api-guide/filtering.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 512acafbd9..9a4ae27457 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -224,7 +224,7 @@ The search behavior may be restricted by prepending various characters to the `s * '^' Starts-with search. * '=' Exact matches. -* '@' Full-text search. (Currently only supported Django's [PostgreSQL backend](https://docs.djangoproject.com/en/dev/ref/contrib/postgres/search/).) +* '@' Full-text search. (Currently only supported Django's [PostgreSQL backend][postgres-search].) * '$' Regex search. For example: @@ -374,3 +374,4 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter] [drf-url-filter]: https://github.com/manjitkumar/drf-url-filters [HStoreField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#hstorefield [JSONField]: https://docs.djangoproject.com/en/3.0/ref/contrib/postgres/fields/#jsonfield +[postgres-search]: https://docs.djangoproject.com/en/stable/ref/contrib/postgres/search/ From 02eeb6fa003b5cbe3851ac18392f129d31a1a6bd Mon Sep 17 00:00:00 2001 From: denniskloyn <97915409+denniskloyn@users.noreply.github.com> Date: Tue, 18 Jan 2022 08:52:11 +0000 Subject: [PATCH 541/907] Change `MIDDLEWARE_CLASSES` to `MIDDLEWARE` (#8333) The `MIDDLEWARE_CLASSES` setting got changed to `MIDDLEWARE` in [Django 1.10](https://docs.djangoproject.com/en/1.10/topics/http/middleware/). --- docs/topics/internationalization.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/internationalization.md b/docs/topics/internationalization.md index c20cf9e339..267ccdb377 100644 --- a/docs/topics/internationalization.md +++ b/docs/topics/internationalization.md @@ -17,9 +17,9 @@ You can change the default language by using the standard Django `LANGUAGE_CODE` LANGUAGE_CODE = "es-es" -You can turn on per-request language requests by adding `LocalMiddleware` to your `MIDDLEWARE_CLASSES` setting: +You can turn on per-request language requests by adding `LocalMiddleware` to your `MIDDLEWARE` setting: - MIDDLEWARE_CLASSES = [ + MIDDLEWARE = [ ... 'django.middleware.locale.LocaleMiddleware' ] @@ -90,7 +90,7 @@ If you're only translating custom error messages that exist inside your project ## How the language is determined -If you want to allow per-request language preferences you'll need to include `django.middleware.locale.LocaleMiddleware` in your `MIDDLEWARE_CLASSES` setting. +If you want to allow per-request language preferences you'll need to include `django.middleware.locale.LocaleMiddleware` in your `MIDDLEWARE` setting. You can find more information on how the language preference is determined in the [Django documentation][django-language-preference]. For reference, the method is: From f378f98a401f28df4ef9bdaf3ee56a1017c195ab Mon Sep 17 00:00:00 2001 From: Sevdimali Date: Thu, 27 Jan 2022 19:02:20 +0400 Subject: [PATCH 542/907] if else optimization (#8340) Removed redundant parentheses --- rest_framework/decorators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 30b9d84d4e..3b572c09ef 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -142,7 +142,7 @@ def action(methods=None, detail=None, url_path=None, url_name=None, **kwargs): how the `@renderer_classes` etc. decorators work for function- based API views. """ - methods = ['get'] if (methods is None) else methods + methods = ['get'] if methods is None else methods methods = [method.lower() for method in methods] assert detail is not None, ( From a5d741aba473d5099d59ff622dbf9702df842441 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 1 Feb 2022 09:42:43 +0000 Subject: [PATCH 543/907] Update renderers.py --- rest_framework/renderers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index b0ddca2b59..8824fa6601 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -105,7 +105,7 @@ def render(self, data, accepted_media_type=None, renderer_context=None): # We always fully escape \u2028 and \u2029 to ensure we output JSON # that is a strict javascript subset. - # See: http://timelessrepo.com/json-isnt-a-javascript-subset + # See: https://gist.github.com/damncabbage/623b879af56f850a6ddc ret = ret.replace('\u2028', '\\u2028').replace('\u2029', '\\u2029') return ret.encode() From c26ec5a3b8080d528112557f86423aee317ed8a4 Mon Sep 17 00:00:00 2001 From: Bhuwan Panta <67514385+Bhuwan-web@users.noreply.github.com> Date: Tue, 1 Feb 2022 19:33:22 +0545 Subject: [PATCH 544/907] Update 6-viewsets-and-routers.md (#8349) # Basename key missing in the tutorial ```diff +router.register(r'snippets', views.SnippetViewSet,basename="snippets") +router.register(r'users', views.UserViewSet,basename="users") -router.register(r'snippets', views.SnippetViewSet) -router.register(r'users', views.UserViewSet) ``` --- docs/tutorial/6-viewsets-and-routers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index f012677d30..18066f0563 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -112,8 +112,8 @@ Here's our re-wired `snippets/urls.py` file. # Create a router and register our viewsets with it. router = DefaultRouter() - router.register(r'snippets', views.SnippetViewSet) - router.register(r'users', views.UserViewSet) + router.register(r'snippets', views.SnippetViewSet,basename="snippets") + router.register(r'users', views.UserViewSet,basename="users") # The API URLs are now determined automatically by the router. urlpatterns = [ From 5bea22f32179276596b4c8152616d54a735a5341 Mon Sep 17 00:00:00 2001 From: Partho Kumar Rajvor Date: Thu, 3 Feb 2022 17:57:47 +0600 Subject: [PATCH 545/907] Added http 102, 103, 421, and 425 status codes (#8350) --- rest_framework/status.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rest_framework/status.py b/rest_framework/status.py index 2561d7689e..7df26b388c 100644 --- a/rest_framework/status.py +++ b/rest_framework/status.py @@ -29,6 +29,8 @@ def is_server_error(code): HTTP_100_CONTINUE = 100 HTTP_101_SWITCHING_PROTOCOLS = 101 +HTTP_102_PROCESSING = 102 +HTTP_103_EARLY_HINTS = 103 HTTP_200_OK = 200 HTTP_201_CREATED = 201 HTTP_202_ACCEPTED = 202 @@ -67,9 +69,11 @@ def is_server_error(code): HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 HTTP_417_EXPECTATION_FAILED = 417 HTTP_418_IM_A_TEAPOT = 418 +HTTP_421_MISDIRECTED_REQUEST = 421 HTTP_422_UNPROCESSABLE_ENTITY = 422 HTTP_423_LOCKED = 423 HTTP_424_FAILED_DEPENDENCY = 424 +HTTP_425_TOO_EARLY = 425 HTTP_426_UPGRADE_REQUIRED = 426 HTTP_428_PRECONDITION_REQUIRED = 428 HTTP_429_TOO_MANY_REQUESTS = 429 From efc7c1d664e5909f5f1f4d07a7bb70daef1c396e Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 12 Feb 2022 10:21:59 -0600 Subject: [PATCH 546/907] Update `accepted_media_type` argument in Renderer docs (#8364) --- docs/api-guide/renderers.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index f13b7ba946..67dff441c2 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -257,7 +257,7 @@ This renderer is used for rendering HTML multipart form data. **It is not suita # Custom renderers -To implement a custom renderer, you should override `BaseRenderer`, set the `.media_type` and `.format` properties, and implement the `.render(self, data, media_type=None, renderer_context=None)` method. +To implement a custom renderer, you should override `BaseRenderer`, set the `.media_type` and `.format` properties, and implement the `.render(self, data, accepted_media_type=None, renderer_context=None)` method. The method should return a bytestring, which will be used as the body of the HTTP response. @@ -267,7 +267,7 @@ The arguments passed to the `.render()` method are: The request data, as set by the `Response()` instantiation. -### `media_type=None` +### `accepted_media_type=None` Optional. If provided, this is the accepted media type, as determined by the content negotiation stage. @@ -291,7 +291,7 @@ The following is an example plaintext renderer that will return a response with media_type = 'text/plain' format = 'txt' - def render(self, data, media_type=None, renderer_context=None): + def render(self, data, accepted_media_type=None, renderer_context=None): return smart_text(data, encoding=self.charset) ## Setting the character set @@ -303,7 +303,7 @@ By default renderer classes are assumed to be using the `UTF-8` encoding. To us format = 'txt' charset = 'iso-8859-1' - def render(self, data, media_type=None, renderer_context=None): + def render(self, data, accepted_media_type=None, renderer_context=None): return data.encode(self.charset) Note that if a renderer class returns a unicode string, then the response content will be coerced into a bytestring by the `Response` class, with the `charset` attribute set on the renderer used to determine the encoding. @@ -318,7 +318,7 @@ In some cases you may also want to set the `render_style` attribute to `'binary' charset = None render_style = 'binary' - def render(self, data, media_type=None, renderer_context=None): + def render(self, data, accepted_media_type=None, renderer_context=None): return data --- From a53e523f939332189b4ba8db7f99758b7d63e59b Mon Sep 17 00:00:00 2001 From: Timothy Allen Date: Tue, 8 Mar 2022 05:39:16 -0500 Subject: [PATCH 547/907] We've renamed the drf-renderer-xlsx package to drf-excel. (#8396) --- docs/api-guide/renderers.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 67dff441c2..685a98f5e0 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -470,15 +470,15 @@ Modify your REST framework settings. [MessagePack][messagepack] is a fast, efficient binary serialization format. [Juan Riaza][juanriaza] maintains the [djangorestframework-msgpack][djangorestframework-msgpack] package which provides MessagePack renderer and parser support for REST framework. -## XLSX (Binary Spreadsheet Endpoints) +## Microsoft Excel: XLSX (Binary Spreadsheet Endpoints) -XLSX is the world's most popular binary spreadsheet format. [Tim Allen][flipperpa] of [The Wharton School][wharton] maintains [drf-renderer-xlsx][drf-renderer-xlsx], which renders an endpoint as an XLSX spreadsheet using OpenPyXL, and allows the client to download it. Spreadsheets can be styled on a per-view basis. +XLSX is the world's most popular binary spreadsheet format. [Tim Allen][flipperpa] of [The Wharton School][wharton] maintains [drf-excel][drf-excel], which renders an endpoint as an XLSX spreadsheet using OpenPyXL, and allows the client to download it. Spreadsheets can be styled on a per-view basis. #### Installation & configuration Install using pip. - $ pip install drf-renderer-xlsx + $ pip install drf-excel Modify your REST framework settings. @@ -488,15 +488,15 @@ Modify your REST framework settings. 'DEFAULT_RENDERER_CLASSES': [ 'rest_framework.renderers.JSONRenderer', 'rest_framework.renderers.BrowsableAPIRenderer', - 'drf_renderer_xlsx.renderers.XLSXRenderer', + 'drf_excel.renderers.XLSXRenderer', ], } To avoid having a file streamed without a filename (which the browser will often default to the filename "download", with no extension), we need to use a mixin to override the `Content-Disposition` header. If no filename is provided, it will default to `export.xlsx`. For example: from rest_framework.viewsets import ReadOnlyModelViewSet - from drf_renderer_xlsx.mixins import XLSXFileMixin - from drf_renderer_xlsx.renderers import XLSXRenderer + from drf_excel.mixins import XLSXFileMixin + from drf_excel.renderers import XLSXRenderer from .models import MyExampleModel from .serializers import MyExampleSerializer @@ -549,7 +549,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily [mjumbewu]: https://github.com/mjumbewu [flipperpa]: https://github.com/flipperpa [wharton]: https://github.com/wharton -[drf-renderer-xlsx]: https://github.com/wharton/drf-renderer-xlsx +[drf-excel]: https://github.com/wharton/drf-excel [vbabiy]: https://github.com/vbabiy [rest-framework-yaml]: https://jpadilla.github.io/django-rest-framework-yaml/ [rest-framework-xml]: https://jpadilla.github.io/django-rest-framework-xml/ From 0e3bc2b1e0189ecc143868ca31dd3393a8aa76f4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 11 Mar 2022 10:58:59 +0000 Subject: [PATCH 548/907] Throttling disclaimer (#8403) * Throttling disclaimer * Expand throttling disclaimer. --- docs/api-guide/throttling.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index a3e42cacf9..6fab79a2b5 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -19,6 +19,8 @@ Multiple throttles can also be used if you want to impose both burst throttling Throttles do not necessarily only refer to rate-limiting requests. For example a storage service might also need to throttle against bandwidth, and a paid data service might want to throttle against a certain number of a records being accessed. +**The application-level throttling that REST framework provides should not be considered a security measure or protection against brute forcing or denial-of-service attacks. Deliberately malicious actors will always be able to spoof IP origins, and application-level throttling is intended for implementing policies such as different business tiers and basic protections against service over-use.** + ## How throttling is determined As with permissions and authentication, throttling in REST framework is always defined as a list of classes. @@ -79,7 +81,7 @@ Throttle classes set in this way will override any viewset level class settings. } return Response(content) -## How clients are identified +## How clients are identified The `X-Forwarded-For` HTTP header and `REMOTE_ADDR` WSGI variable are used to uniquely identify client IP addresses for throttling. If the `X-Forwarded-For` header is present then it will be used, otherwise the value of the `REMOTE_ADDR` variable from the WSGI environment will be used. From b3083d83ae197e92591116d1cd4231ae9565dbfa Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 16 Mar 2022 11:35:04 +0000 Subject: [PATCH 549/907] Update index.md (#8411) Update security email. --- docs/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 4871ba4587..e58f24df87 100644 --- a/docs/index.md +++ b/docs/index.md @@ -194,9 +194,11 @@ For priority support please sign up for a [professional or premium sponsorship p ## Security -If you believe you’ve found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**. +Security issues are handled under the supervision of the [Django security team](https://www.djangoproject.com/foundation/teams/#security-team). -Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure. +**Please report security issues by emailing security@djangoproject.com**. + +The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure. ## License From b521160c92783b9d2a76d6bc3fa30df10d7251ac Mon Sep 17 00:00:00 2001 From: baseplate-admin <61817579+baseplate-admin@users.noreply.github.com> Date: Wed, 16 Mar 2022 17:35:24 +0600 Subject: [PATCH 550/907] Fix code block in `README.md` (#8408) Hi there, The code block below show imply `Python` as it lives in `settings.py` ``` INSTALLED_APPS = [ ... 'rest_framework', ] ``` This pull request essentially fixes that. --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d8fd83d672..bf40167512 100644 --- a/README.md +++ b/README.md @@ -67,11 +67,12 @@ Install using `pip`... pip install djangorestframework Add `'rest_framework'` to your `INSTALLED_APPS` setting. - - INSTALLED_APPS = [ - ... - 'rest_framework', - ] +```python +INSTALLED_APPS = [ + ... + 'rest_framework', +] +``` # Example From 070c32f4a62ef0544f58de404c87d86db36fd825 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 16 Mar 2022 12:12:25 +0000 Subject: [PATCH 551/907] Update SECURITY.md (#8412) --- SECURITY.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index d3faefa3cb..a92a1b0cf1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,8 +2,8 @@ ## Reporting a Vulnerability -If you believe you've found something in Django REST framework which has security implications, please **do not raise the issue in a public forum**. +Security issues are handled under the supervision of the [Django security team](https://www.djangoproject.com/foundation/teams/#security-team). -Send a description of the issue via email to [rest-framework-security@googlegroups.com][security-mail]. The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure. + **Please report security issues by emailing security@djangoproject.com**. -[security-mail]: mailto:rest-framework-security@googlegroups.com + The project maintainers will then work with you to resolve any issues where required, prior to any public disclosure. From 0b88583a101d6eb95bf9bfb5dd9df47014a45a71 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 23 Mar 2022 12:28:46 +0100 Subject: [PATCH 552/907] Bumped versions in Github actions configuration to v3. (#8414) --- .github/workflows/main.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 42fee2a124..c88dc55cd9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,18 +21,13 @@ jobs: - '3.10' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - - - uses: actions/cache@v2 - with: - path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('requirements/*.txt') }} - restore-keys: | - ${{ runner.os }}-pip- + cache: 'pip' + cache-dependency-path: 'requirements/*.txt' - name: Upgrade packaging tools run: python -m pip install --upgrade pip setuptools virtualenv wheel From 75f19981233c112326b13e4b2abb4b71effd9fbe Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 23 Mar 2022 11:52:26 +0000 Subject: [PATCH 553/907] Update throttling docs (#8424) --- docs/api-guide/throttling.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 6fab79a2b5..719378247d 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -19,7 +19,9 @@ Multiple throttles can also be used if you want to impose both burst throttling Throttles do not necessarily only refer to rate-limiting requests. For example a storage service might also need to throttle against bandwidth, and a paid data service might want to throttle against a certain number of a records being accessed. -**The application-level throttling that REST framework provides should not be considered a security measure or protection against brute forcing or denial-of-service attacks. Deliberately malicious actors will always be able to spoof IP origins, and application-level throttling is intended for implementing policies such as different business tiers and basic protections against service over-use.** +**The application-level throttling that REST framework provides should not be considered a security measure or protection against brute forcing or denial-of-service attacks. Deliberately malicious actors will always be able to spoof IP origins. In addition to this, the built-in throttling implementations are implemented using Django's cache framework, and use non-atomic operations to determine the request rate, which may sometimes result in some fuzziness. + +The application-level throttling provided by REST framework is intended for implementing policies such as different business tiers and basic protections against service over-use.** ## How throttling is determined From a4334a81260b15df07550f7a9e96c1753beaa269 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 23 Mar 2022 11:52:45 +0000 Subject: [PATCH 554/907] Update contribution guidelines (#8422) --- CONTRIBUTING.md | 6 +++++- docs/community/contributing.md | 15 ++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7f17b1a35..d567d45a87 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,7 @@ # Contributing to REST framework -See the [Contributing guide in the documentation](https://www.django-rest-framework.org/community/contributing/). +At this point in it's lifespan we consider Django REST framework to be essentially feature-complete. We may accept pull requests that track the continued development of Django versions, but would prefer not to accept new features or code formatting changes. + +Apart from minor documentation changes, the [GitHub discussions page](https://github.com/encode/django-rest-framework/discussions) should generally be your starting point. Please only raise an issue or pull request if you've been recommended to do so after discussion. + +The [Contributing guide in the documentation](https://www.django-rest-framework.org/community/contributing/) gives some more information on our process and code of conduct. diff --git a/docs/community/contributing.md b/docs/community/contributing.md index de1f8db0fb..2232bd10b9 100644 --- a/docs/community/contributing.md +++ b/docs/community/contributing.md @@ -6,6 +6,12 @@ There are many ways you can contribute to Django REST framework. We'd like it to be a community-led project, so please get involved and help shape the future of the project. +--- + +**Note**: At this point in it's lifespan we consider Django REST framework to be essentially feature-complete. We may accept pull requests that track the continued development of Django versions, but would prefer not to accept new features or code formatting changes. + +--- + ## Community The most important thing you can do to help push the REST framework project forward is to be actively involved wherever possible. Code contributions are often overvalued as being the primary way to get involved in a project, we don't believe that needs to be the case. @@ -26,14 +32,13 @@ The [Django code of conduct][code-of-conduct] gives a fuller set of guidelines f # Issues -It's really helpful if you can make sure to address issues on the correct channel. Usage questions should be directed to the [discussion group][google-group]. Feature requests, bug reports and other issues should be raised on the GitHub [issue tracker][issues]. +Our contribution process is that the [GitHub discussions page](https://github.com/encode/django-rest-framework/discussions) should generally be your starting point. Please only raise an issue or pull request if you've been recommended to do so after discussion. -Some tips on good issue reporting: +Some tips on good potential issue reporting: * When describing issues try to phrase your ticket in terms of the *behavior* you think needs changing rather than the *code* you think need changing. -* Search the issue list first for related items, and make sure you're running the latest version of REST framework before reporting an issue. -* If reporting a bug, then try to include a pull request with a failing test case. This will help us quickly identify if there is a valid issue, and make sure that it gets fixed more quickly if there is one. -* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation. +* Search the GitHub project page for related items, and make sure you're running the latest version of REST framework before reporting an issue. +* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation. At this point in it's lifespan we consider Django REST framework to be essentially feature-complete. * Closing an issue doesn't necessarily mean the end of a discussion. If you believe your issue has been closed incorrectly, explain why and we'll consider if it needs to be reopened. ## Triaging issues From 4464ce7270e6242fec212a4257aa1277d023ff1b Mon Sep 17 00:00:00 2001 From: Shivendra Pratap Kushwaha <33164379+kushshiv@users.noreply.github.com> Date: Wed, 23 Mar 2022 17:37:44 +0530 Subject: [PATCH 555/907] Link added for Test Case Classes and corrected stable link for Refresh from DB (#8381) Link added for Test Case Classes and corrected stable link for Refresh from DB. --- docs/api-guide/testing.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/testing.md b/docs/api-guide/testing.md index 62eb8dd1a5..261df80f27 100644 --- a/docs/api-guide/testing.md +++ b/docs/api-guide/testing.md @@ -299,7 +299,7 @@ similar way as with `RequestsClient`. # API Test cases -REST framework includes the following test case classes, that mirror the existing Django test case classes, but use `APIClient` instead of Django's default `Client`. +REST framework includes the following test case classes, that mirror the existing [Django's test case classes][provided_test_case_classes], but use `APIClient` instead of Django's default `Client`. * `APISimpleTestCase` * `APITransactionTestCase` @@ -413,5 +413,6 @@ For example, to add support for using `format='html'` in test requests, you migh [client]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#the-test-client [requestfactory]: https://docs.djangoproject.com/en/stable/topics/testing/advanced/#django.test.client.RequestFactory [configuration]: #configuration -[refresh_from_db_docs]: https://docs.djangoproject.com/en/1.11/ref/models/instances/#django.db.models.Model.refresh_from_db +[refresh_from_db_docs]: https://docs.djangoproject.com/en/stable/ref/models/instances/#django.db.models.Model.refresh_from_db [session_objects]: https://requests.readthedocs.io/en/master/user/advanced/#session-objects +[provided_test_case_classes]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#provided-test-case-classes From 7e4e6d207070d50736827a281b5cb70eb161b782 Mon Sep 17 00:00:00 2001 From: Wagner de Lima Date: Wed, 23 Mar 2022 13:09:05 +0100 Subject: [PATCH 556/907] docs: include drf-social-oauth2 to docs. (#8310) django-rest-framework-social-oauth2 is not a maintained library anymore. --- docs/api-guide/authentication.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 57bbaeb679..2f23e17185 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -420,9 +420,9 @@ There are currently two forks of this project. * [Django-rest-auth][django-rest-auth] is the original project, [but is not currently receiving updates](https://github.com/Tivix/django-rest-auth/issues/568). * [Dj-rest-auth][dj-rest-auth] is a newer fork of the project. -## django-rest-framework-social-oauth2 +## drf-social-oauth2 -[Django-rest-framework-social-oauth2][django-rest-framework-social-oauth2] library provides an easy way to integrate social plugins (facebook, twitter, google, etc.) to your authentication system and an easy oauth2 setup. With this library, you will be able to authenticate users based on external tokens (e.g. facebook access token), convert these tokens to "in-house" oauth2 tokens and use and generate oauth2 tokens to authenticate your users. +[Drf-social-oauth2][drf-social-oauth2] is a framework that helps you authenticate with major social oauth2 vendors, such as Facebook, Google, Twitter, Orcid, etc. It generates tokens in a JWTed way with an easy setup. ## django-rest-knox @@ -473,7 +473,7 @@ More information can be found in the [Documentation](https://django-rest-durin.r [djoser]: https://github.com/sunscrapers/djoser [django-rest-auth]: https://github.com/Tivix/django-rest-auth [dj-rest-auth]: https://github.com/jazzband/dj-rest-auth -[django-rest-framework-social-oauth2]: https://github.com/PhilipGarnero/django-rest-framework-social-oauth2 +[drf-social-oauth2]: https://github.com/wagnerdelima/drf-social-oauth2 [django-rest-knox]: https://github.com/James1345/django-rest-knox [drfpasswordless]: https://github.com/aaronn/django-rest-framework-passwordless [django-rest-authemail]: https://github.com/celiao/django-rest-authemail From df4d16d2f17bd53626266eb829fed40cf8a73e48 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 24 Mar 2022 09:23:16 +0000 Subject: [PATCH 557/907] Add StaleBot (#8423) --- .github/stale.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 0000000000..f9ebbced4a --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,22 @@ +# Documentation: https://github.com/probot/stale + +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 + +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 + +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed if no further activity occurs. Thank you + for your contributions. + +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false + +# Limit the number of actions per hour, from 1-30. Default is 30 +limitPerRun: 1 + +# Label to use when marking as stale +staleLabel: stale From df92e57ad6c8394ca54654dfc7a2722f822ed8c8 Mon Sep 17 00:00:00 2001 From: hashlash Date: Thu, 24 Mar 2022 16:57:42 +0700 Subject: [PATCH 558/907] Added test client support for HTTP 307 and 308 redirects (#8419) * Add retain test data on follow=True * Simplify TestAPITestClient.test_follow_redirect Inspired from Django's ClientTest.test_follow_307_and_308_redirect * Add 307 308 follow redirect test --- rest_framework/test.py | 12 +++---- tests/test_testing.py | 72 +++++++++++++++++++++--------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/rest_framework/test.py b/rest_framework/test.py index 0212348ee0..07df743c8e 100644 --- a/rest_framework/test.py +++ b/rest_framework/test.py @@ -288,7 +288,7 @@ def request(self, **kwargs): def get(self, path, data=None, follow=False, **extra): response = super().get(path, data=data, **extra) if follow: - response = self._handle_redirects(response, **extra) + response = self._handle_redirects(response, data=data, **extra) return response def post(self, path, data=None, format=None, content_type=None, @@ -296,7 +296,7 @@ def post(self, path, data=None, format=None, content_type=None, response = super().post( path, data=data, format=format, content_type=content_type, **extra) if follow: - response = self._handle_redirects(response, **extra) + response = self._handle_redirects(response, data=data, format=format, content_type=content_type, **extra) return response def put(self, path, data=None, format=None, content_type=None, @@ -304,7 +304,7 @@ def put(self, path, data=None, format=None, content_type=None, response = super().put( path, data=data, format=format, content_type=content_type, **extra) if follow: - response = self._handle_redirects(response, **extra) + response = self._handle_redirects(response, data=data, format=format, content_type=content_type, **extra) return response def patch(self, path, data=None, format=None, content_type=None, @@ -312,7 +312,7 @@ def patch(self, path, data=None, format=None, content_type=None, response = super().patch( path, data=data, format=format, content_type=content_type, **extra) if follow: - response = self._handle_redirects(response, **extra) + response = self._handle_redirects(response, data=data, format=format, content_type=content_type, **extra) return response def delete(self, path, data=None, format=None, content_type=None, @@ -320,7 +320,7 @@ def delete(self, path, data=None, format=None, content_type=None, response = super().delete( path, data=data, format=format, content_type=content_type, **extra) if follow: - response = self._handle_redirects(response, **extra) + response = self._handle_redirects(response, data=data, format=format, content_type=content_type, **extra) return response def options(self, path, data=None, format=None, content_type=None, @@ -328,7 +328,7 @@ def options(self, path, data=None, format=None, content_type=None, response = super().options( path, data=data, format=format, content_type=content_type, **extra) if follow: - response = self._handle_redirects(response, **extra) + response = self._handle_redirects(response, data=data, format=format, content_type=content_type, **extra) return response def logout(self): diff --git a/tests/test_testing.py b/tests/test_testing.py index 5066ee142e..b6579e3690 100644 --- a/tests/test_testing.py +++ b/tests/test_testing.py @@ -1,7 +1,10 @@ +import itertools from io import BytesIO +from unittest.mock import patch import django from django.contrib.auth.models import User +from django.http import HttpResponseRedirect from django.shortcuts import redirect from django.test import TestCase, override_settings from django.urls import path @@ -14,7 +17,7 @@ ) -@api_view(['GET', 'POST']) +@api_view(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) def view(request): return Response({ 'auth': request.META.get('HTTP_AUTHORIZATION', b''), @@ -36,6 +39,11 @@ def redirect_view(request): return redirect('/view/') +@api_view(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']) +def redirect_307_308_view(request, code): + return HttpResponseRedirect('/view/', status=code) + + class BasicSerializer(serializers.Serializer): flag = fields.BooleanField(default=lambda: True) @@ -51,6 +59,7 @@ def post_view(request): path('view/', view), path('session-view/', session_view), path('redirect-view/', redirect_view), + path('redirect-view//', redirect_307_308_view), path('post-view/', post_view) ] @@ -146,41 +155,32 @@ def test_follow_redirect(self): """ Follow redirect by setting follow argument. """ - response = self.client.get('/redirect-view/') - assert response.status_code == 302 - response = self.client.get('/redirect-view/', follow=True) - assert response.redirect_chain is not None - assert response.status_code == 200 - - response = self.client.post('/redirect-view/') - assert response.status_code == 302 - response = self.client.post('/redirect-view/', follow=True) - assert response.redirect_chain is not None - assert response.status_code == 200 - - response = self.client.put('/redirect-view/') - assert response.status_code == 302 - response = self.client.put('/redirect-view/', follow=True) - assert response.redirect_chain is not None - assert response.status_code == 200 - - response = self.client.patch('/redirect-view/') - assert response.status_code == 302 - response = self.client.patch('/redirect-view/', follow=True) - assert response.redirect_chain is not None - assert response.status_code == 200 - - response = self.client.delete('/redirect-view/') - assert response.status_code == 302 - response = self.client.delete('/redirect-view/', follow=True) - assert response.redirect_chain is not None - assert response.status_code == 200 - - response = self.client.options('/redirect-view/') - assert response.status_code == 302 - response = self.client.options('/redirect-view/', follow=True) - assert response.redirect_chain is not None - assert response.status_code == 200 + for method in ('get', 'post', 'put', 'patch', 'delete', 'options'): + with self.subTest(method=method): + req_method = getattr(self.client, method) + response = req_method('/redirect-view/') + assert response.status_code == 302 + response = req_method('/redirect-view/', follow=True) + assert response.redirect_chain is not None + assert response.status_code == 200 + + def test_follow_307_308_preserve_kwargs(self, *mocked_methods): + """ + Follow redirect by setting follow argument, and make sure the following + method called with appropriate kwargs. + """ + methods = ('get', 'post', 'put', 'patch', 'delete', 'options') + codes = (307, 308) + for method, code in itertools.product(methods, codes): + subtest_ctx = self.subTest(method=method, code=code) + patch_ctx = patch.object(self.client, method, side_effect=getattr(self.client, method)) + with subtest_ctx, patch_ctx as req_method: + kwargs = {'data': {'example': 'test'}, 'format': 'json'} + response = req_method('/redirect-view/%s/' % code, follow=True, **kwargs) + assert response.redirect_chain is not None + assert response.status_code == 200 + for _, call_args, call_kwargs in req_method.mock_calls: + assert all(call_kwargs[k] == kwargs[k] for k in kwargs if k in call_kwargs) def test_invalid_multipart_data(self): """ From 86673a337a4fe8861c090b4532379b97e3921fef Mon Sep 17 00:00:00 2001 From: Kojo Idrissa Date: Thu, 14 Apr 2022 15:00:38 -0500 Subject: [PATCH 559/907] corrected grammar to improve clarity (#8466) --- docs/api-guide/relations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/relations.md b/docs/api-guide/relations.md index 4547253b0a..9c8295b853 100644 --- a/docs/api-guide/relations.md +++ b/docs/api-guide/relations.md @@ -19,7 +19,7 @@ Relational fields are used to represent model relationships. They can be applie --- -**Note:** REST Framework does not attempt to automatically optimize querysets passed to serializers in terms of `select_related` and `prefetch_related` since it would be too much magic. A serializer with a field spanning an orm relation through its source attribute could require an additional database hit to fetch related object from the database. It is the programmer's responsibility to optimize queries to avoid additional database hits which could occur while using such a serializer. +**Note:** REST Framework does not attempt to automatically optimize querysets passed to serializers in terms of `select_related` and `prefetch_related` since it would be too much magic. A serializer with a field spanning an orm relation through its source attribute could require an additional database hit to fetch related objects from the database. It is the programmer's responsibility to optimize queries to avoid additional database hits which could occur while using such a serializer. For example, the following serializer would lead to a database hit each time evaluating the tracks field if it is not prefetched: From b1004a47334a0dd1929e6d50b8f7ff6badc959f4 Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Mon, 25 Apr 2022 01:16:18 +0300 Subject: [PATCH 560/907] docs: Add a note on concurrency and races (#6950) Refs #5181 Co-authored-by: Adam Johnson --- docs/api-guide/throttling.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 719378247d..b875221978 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -106,6 +106,12 @@ If you need to use a cache other than `'default'`, you can do so by creating a c 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. +## A note on concurrency + +The built-in throttle implementations are open to [race conditions][race], so under high concurrency they may allow a few extra requests through. + +If your project relies on guaranteeing the number of requests during concurrent requests, you will need to implement your own throttle class. See [issue #5181][gh5181] for more details. + --- # API Reference @@ -214,3 +220,5 @@ The following is an example of a rate throttle, that will randomly throttle 1 in [identifying-clients]: http://oxpedia.org/wiki/index.php?title=AppSuite:Grizzly#Multiple_Proxies_in_front_of_the_cluster [cache-setting]: https://docs.djangoproject.com/en/stable/ref/settings/#caches [cache-docs]: https://docs.djangoproject.com/en/stable/topics/cache/#setting-up-the-cache +[gh5181]: https://github.com/encode/django-rest-framework/issues/5181 +[race]: https://en.wikipedia.org/wiki/Race_condition#Data_race From cdc956a96caafddcf4ecaf6218e340ebb3ce6d72 Mon Sep 17 00:00:00 2001 From: Q_back Date: Tue, 3 May 2022 12:10:37 +0200 Subject: [PATCH 561/907] Update description of docs/api-guide/fields.md -> required (#8476) fix: Documentation did not point out that default value of `required` `Field` parameter is `False` depending on the Django's `Model.field`. --- docs/api-guide/fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 5b9688dcab..e9ef5c6b64 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -42,7 +42,7 @@ Set to false if this field is not required to be present during deserialization. Setting this to `False` also allows the object attribute or dictionary key to be omitted from output when serializing the instance. If the key is not present it will simply not be included in the output representation. -Defaults to `True`. +Defaults to `True`. If you're using [Model Serializer](https://www.django-rest-framework.org/api-guide/serializers/#modelserializer) default value will be `False` if you have specified `blank=True` or `default` or `null=True` at your field in your `Model`. ### `default` From 33b86a8e5383ae7c6bba03cf09b12b8f7cadd20f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 18 May 2022 13:10:38 +0100 Subject: [PATCH 562/907] Recommend Django REST Knox (#8490) * Recommend Django REST Knox * Pin jinja2 to fix docs builds --- docs/api-guide/authentication.md | 26 +++++++++++++-------- requirements/requirements-documentation.txt | 1 + 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 2f23e17185..fca9374d0a 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -120,6 +120,14 @@ Unauthenticated responses that are denied permission will result in an `HTTP 401 ## TokenAuthentication +--- + +**Note:** The token authentication provided by Django REST framework is a fairly simple implementation. + +For an implementation which allows more than one token per user, has some tighter security implementation details, and supports token expiry, please see the [Django REST Knox][django-rest-knox] third party package. + +--- + This authentication scheme uses a simple token-based HTTP Authentication scheme. Token authentication is appropriate for client-server setups, such as native desktop and mobile clients. To use the `TokenAuthentication` scheme you'll need to [configure the authentication classes](#setting-the-authentication-scheme) to include `TokenAuthentication`, and additionally include `rest_framework.authtoken` in your `INSTALLED_APPS` setting: @@ -129,11 +137,9 @@ To use the `TokenAuthentication` scheme you'll need to [configure the authentica 'rest_framework.authtoken' ] ---- - -**Note:** Make sure to run `manage.py migrate` after changing your settings. The `rest_framework.authtoken` app provides Django database migrations. +Make sure to run `manage.py migrate` after changing your settings. ---- +The `rest_framework.authtoken` app provides Django database migrations. You'll also need to create tokens for your users. @@ -146,7 +152,7 @@ For clients to authenticate, the token key should be included in the `Authorizat Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b -**Note:** If you want to use a different keyword in the header, such as `Bearer`, simply subclass `TokenAuthentication` and set the `keyword` class variable. +*If you want to use a different keyword in the header, such as `Bearer`, simply subclass `TokenAuthentication` and set the `keyword` class variable.* If successfully authenticated, `TokenAuthentication` provides the following credentials. @@ -355,6 +361,10 @@ The following example will authenticate any incoming request as the user given b The following third-party packages are also available. +## django-rest-knox + +[Django-rest-knox][django-rest-knox] library provides models and views to handle token-based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into). + ## Django OAuth Toolkit The [Django OAuth Toolkit][django-oauth-toolkit] package provides OAuth 2.0 support and works with Python 3.4+. The package is maintained by [jazzband][jazzband] and uses the excellent [OAuthLib][oauthlib]. The package is well documented, and well supported and is currently our **recommended package for OAuth 2.0 support**. @@ -422,11 +432,7 @@ There are currently two forks of this project. ## drf-social-oauth2 -[Drf-social-oauth2][drf-social-oauth2] is a framework that helps you authenticate with major social oauth2 vendors, such as Facebook, Google, Twitter, Orcid, etc. It generates tokens in a JWTed way with an easy setup. - -## django-rest-knox - -[Django-rest-knox][django-rest-knox] library provides models and views to handle token-based authentication in a more secure and extensible way than the built-in TokenAuthentication scheme - with Single Page Applications and Mobile clients in mind. It provides per-client tokens, and views to generate them when provided some other authentication (usually basic authentication), to delete the token (providing a server enforced logout) and to delete all tokens (logs out all clients that a user is logged into). +[Drf-social-oauth2][drf-social-oauth2] is a framework that helps you authenticate with major social oauth2 vendors, such as Facebook, Google, Twitter, Orcid, etc. It generates tokens in a JWTed way with an easy setup. ## drfpasswordless diff --git a/requirements/requirements-documentation.txt b/requirements/requirements-documentation.txt index ad49287304..cf2dc26e88 100644 --- a/requirements/requirements-documentation.txt +++ b/requirements/requirements-documentation.txt @@ -1,2 +1,3 @@ # MkDocs to build our documentation. mkdocs>=1.1.2,<1.2 +jinja2>=2.10,<3.1.0 # contextfilter has been renamed From ed00e11cc5f338062ff2e90b552643804fc67dba Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 26 May 2022 10:40:46 +0100 Subject: [PATCH 563/907] Add FEZTO --- README.md | 11 ++++------- docs/img/premium/fezto-readme.png | Bin 0 -> 22592 bytes 2 files changed, 4 insertions(+), 7 deletions(-) create mode 100644 docs/img/premium/fezto-readme.png diff --git a/README.md b/README.md index bf40167512..9476e92d2d 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,13 @@ The initial aim is to provide a single full-time position on REST framework. [![][sentry-img]][sentry-url] [![][stream-img]][stream-url] -[![][rollbar-img]][rollbar-url] -[![][esg-img]][esg-url] [![][retool-img]][retool-url] [![][bitio-img]][bitio-url] [![][posthog-img]][posthog-url] [![][cryptapi-img]][cryptapi-url] +[![][fezto-img]][fezto-url] -Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [ESG][esg-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], and [CryptAPI][cryptapi-url]. +Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], [CryptAPI][cryptapi-url], and [FEZTO][fezto-url]. --- @@ -194,21 +193,19 @@ Please see the [security policy][security-policy]. [sentry-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/sentry-readme.png [stream-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/stream-readme.png -[rollbar-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/rollbar-readme.png -[esg-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/esg-readme.png [retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png [bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png [posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png [cryptapi-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png +[fezto-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/fezto-readme.png [sentry-url]: https://getsentry.com/welcome/ [stream-url]: https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage -[rollbar-url]: https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial -[esg-url]: https://software.esg-usa.com/ [retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship [bitio-url]: https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship [posthog-url]: https://posthog.com?utm_source=drf&utm_medium=sponsorship&utm_campaign=open-source-sponsorship [cryptapi-url]: https://cryptapi.io +[fezto-url]: https://www.fezto.xyz/?utm_source=DjangoRESTFramework [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 diff --git a/docs/img/premium/fezto-readme.png b/docs/img/premium/fezto-readme.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc3be6e6c18de05af1c8c2601f4edc627bac0b6 GIT binary patch literal 22592 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 Date: Thu, 26 May 2022 10:48:08 +0100 Subject: [PATCH 564/907] Resize sponsor images --- docs/img/premium/bitio-readme.png | Bin 18526 -> 18479 bytes docs/img/premium/cryptapi-readme.png | Bin 17864 -> 17824 bytes docs/img/premium/posthog-readme.png | Bin 2402 -> 4881 bytes docs/img/premium/retool-readme.png | Bin 8962 -> 8921 bytes docs/img/premium/sentry-readme.png | Bin 24504 -> 24506 bytes docs/img/premium/stream-readme.png | Bin 19213 -> 19170 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/img/premium/bitio-readme.png b/docs/img/premium/bitio-readme.png index c47118cc6956b8a27a1be8edf3b84f8fa119e54c..d5d6259e61946a911024aaf9835d885539323c31 100644 GIT binary patch delta 17556 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 a@`qhkO^(>-(f~YWgu&C*&t;ucLK6TrZLfp? delta 17605 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 QcrFQpr>mdKI;Vst0HO}u@&Et; diff --git a/docs/img/premium/cryptapi-readme.png b/docs/img/premium/cryptapi-readme.png index 163f6a9ea20f17909e6e38537a627f19e5766a66..10839b13b4835ea1abf18dbb89b0b8e9c5b6e0fa 100644 GIT binary patch delta 16101 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 Pe1VXcR*|ZfF#Y*I_$DB3 delta 16128 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 ME$q!}&R@OzA3!)iFaQ7m literal 2402 zcmai$dpy(oAIIm?kqEUVMI1|COS+h2jFQXTx1B9IU5@L*+;d$jLMd%T+LDdA=Y9!U zO`&8o_mayIDdv_-A-UVxJbwN6dpyqL@p*i{ulM)+e7#=Z@At0{8Hcslxnuth5D2u> z$`W-31QG!Y^y+po!Cqx!8!Whp6OA#(AW(UN)Vj+y!7LJV#=-^wfTCV04r1+CN^`RT6k>eVYF zBcruOb2^VqMINXu5{N;Nm zqHJ#!fI!<5tx(2xq5U(1Q-n$wWLu}W2EJU6rPH6guvvy==MQlDuww4toQBkVvx=gB zS56+3F_shmk9B{jb$qEsJ>}{Re&V>w8v9)6amureTuaCH72!MtnmxS`rtIbfQ8e9$UWfTA8?9)t?!XzL_}Y0;vq!}N8n+<^&H^8AcCFY4@fNOCpmAEU**@Zl%mYXyJ-UgseWGAX^U|40*v+ zfXPrB@bc8vNIp9*MDikKcSEHwu|0PLVtGV@`6R2u#yu$wc8g+LOl>S2ad zNlq75jnbj!Li+}4Hk8eTq?md-io$%ik8Vif4I@P%8K6VEX@g_9_%%>-ohogqEmNrl zWWBQA=;}w`oukPTxMpbNgHp2WFZqNl@k?fK$Cs8z-!G%x@j@t#e39C>$l zgZtJ{NLKmfDJJjzpM)p-zow=T7c!IAXmXAAa3Y5p5EM;Z>c zOqrTzK1V;PP3h$!@uGlamb4CXM%Fvf9sS;PpLbw?dN;3?t`-CD<~==K$ffI8?KJBV z$O0~1mn?g`EfXfhy#hsnfXpWyk2r#9m7z44^L5I$0d_@TD)J6|dUee^N(9NF1QIdW z7c8JDBqEOj8U4gaCAneZ3DCwRAn``nY$?~Xx0?q80yo_Ds5fs2k_s70qc`RT8gvp` zTmuBwT)-mjhWgODYlM1lVq4QO!B6S!<=I0&irx##s3-*T9Gs{@SVZkJf7v0pGL<_l zXb8u|uZMka*Uc{xCpT@S)B;2g$(-4-#?`YOSJi-6!ACCYAY)CZ^w9FXyr)y%fk_sq zKox&aL8E}ZYC|6VH1U2bs(rYDQ1M*QV@F-69=a(k=n+(2Uo7kG{ah1=LObq9E{v5P z@wfuEW_yVEt^(zEYC-g_v**Icv2Uw@^!n2qc0lw;GgSyEajkc()wgzg zMwx4?mmd*ZSzv-sS*PrXmPwoHCu{J#mEU6f3zKHcfstl%v;N|g@enS*%Ae54Yj}uj z%lRoIKV@-jL<`+`t5!Mnl8aSt*vsldhPXT0zMwmyK)w{wBavcnpw(X>_Rr*HGi{_F z`~2dwy1Ho@*a$7Y4t*SX^aNdgA^0qU)M;vy`e}AuIy{*?sC@UQs*0i<6PuK90L3BJ z#nu2OGmkAxPU;{s;ymw-l>?7T5Yauir!94LW*z^DztyXG!f=i;5#G|L`JzA-TT^-W zHSb#W!vG3FzjIEZh*9}Hxs6!gW;WIx?N=MJ7)t$m?ZjAUQpgDMNjqCLrYR@e8Ul8~ z+Psy>pX(WZ4Gf-eKGYK;%Zx|s>8`Sae1=jgch4VabK)gvvD*?Qv2i2$B^`H8g(+00 z8erfHDDIDivGiM~LzTQwW+tc7($y{!-|=5XBhem8GYv~9_Zy_fot1a<^}{Qx3@~0n zS>`OXmrvV6GeX5woY=vOc{V)GTp#|EC$#lWGRBhLZsws0(C2FIb2}u9JexkenOO|F z=vsfrkhza|EXMqeXfR2?fdF^x_TMO+4y!qL_>}X}ju(jP#?Fv{$BUg`IpaUv(gW(Z zokCrLI|RF?YB?TgRZY3pmX*-GG86H1Km1GAOX<@~Si73-#j?8`_8RP`)TGykPIkqn zX{gu7UC#Qv&Yk?s{{C}%Q7(!+Rdy)#y!uRZldTj(uRUy_tCLN2kOmNxK6b8dBUqK0 z3rQnZ0wp{|!dKhD!%!+SW46E;%T$M|%&@3Z6X%=% E0-^VOCIA2c diff --git a/docs/img/premium/retool-readme.png b/docs/img/premium/retool-readme.png index 56adba04d3acc812f5499e19ecd9c72d3632eb32..9715634274d3a17d50099384201259e6e2d4c172 100644 GIT binary patch literal 8921 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 diff --git a/docs/img/premium/sentry-readme.png b/docs/img/premium/sentry-readme.png index 420e8ee8770b07df2af147c9b95293331158db18..3c8858aca7093410b568e373f0a6ad78469f6c62 100644 GIT binary patch delta 23629 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

    #CGJW@%-Vi_DV!G=cC3Dxq)931F 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 Prr~5I6(!2W4gCKH;KJcb diff --git a/docs/img/premium/stream-readme.png b/docs/img/premium/stream-readme.png index 15da6ba71d07b65483d340b7dd519c6171126eba..a6a7317b7c2ef88c011e7cb8a98b649287ac0657 100644 GIT binary patch literal 19170 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 X{nT9-*dJL4I-tta)z4*}Q$iB}z?3lk From e5fb9af0eaffde683fa0af3987085f86cf0d2640 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 26 May 2022 11:19:09 +0100 Subject: [PATCH 565/907] Add FEZTO as a premium sponsor --- docs/index.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index e58f24df87..a96f06a30b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -68,16 +68,15 @@ continued development by **[signing up for a paid plan][funding]**.

    -*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [ESG](https://software.esg-usa.com/), [Rollbar](https://rollbar.com/?utm_source=django&utm_medium=sponsorship&utm_campaign=freetrial), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), and [CryptAPI](https://cryptapi.io).* +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [CryptAPI](https://cryptapi.io), and [FEZTO](https://www.fezto.xyz/?utm_source=DjangoRESTFramework).* --- From ce21454a431e0feb057ff9069a8295e2b071d79e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 6 Jun 2022 11:00:01 +0100 Subject: [PATCH 566/907] Update homepage sponsors --- docs/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index a96f06a30b..aa502a6218 100644 --- a/docs/index.md +++ b/docs/index.md @@ -68,6 +68,7 @@ continued development by **[signing up for a paid plan][funding]**.
    -*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [Cadre](https://cadre.com), [Kloudless](https://hubs.ly/H0f30Lf0), [Lights On Software](https://lightsonsoftware.com), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [CryptAPI](https://cryptapi.io), and [FEZTO](https://www.fezto.xyz/?utm_source=DjangoRESTFramework).* +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [Spacinov](https://www.spacinov.com/), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [CryptAPI](https://cryptapi.io), and [FEZTO](https://www.fezto.xyz/?utm_source=DjangoRESTFramework).* --- From 7069083b0f3c67f030dad1b9f5c6c079f30a84d2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 6 Jun 2022 11:07:25 +0100 Subject: [PATCH 567/907] Promote Spacinov to premium sponsorship --- README.md | 5 ++++- docs/img/premium/spacinov-readme.png | Bin 0 -> 56997 bytes 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docs/img/premium/spacinov-readme.png diff --git a/README.md b/README.md index 9476e92d2d..3fbd2f83a1 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,14 @@ The initial aim is to provide a single full-time position on REST framework. [![][sentry-img]][sentry-url] [![][stream-img]][stream-url] +[![][spacinov-img]][spacinov-url] [![][retool-img]][retool-url] [![][bitio-img]][bitio-url] [![][posthog-img]][posthog-url] [![][cryptapi-img]][cryptapi-url] [![][fezto-img]][fezto-url] -Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], [CryptAPI][cryptapi-url], and [FEZTO][fezto-url]. +Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Spacinov][spacinov-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], [CryptAPI][cryptapi-url], and [FEZTO][fezto-url]. --- @@ -193,6 +194,7 @@ Please see the [security policy][security-policy]. [sentry-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/sentry-readme.png [stream-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/stream-readme.png +[spacinov-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/spacinov-readme.png [retool-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/retool-readme.png [bitio-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/bitio-readme.png [posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png @@ -201,6 +203,7 @@ Please see the [security policy][security-policy]. [sentry-url]: https://getsentry.com/welcome/ [stream-url]: https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage +[spacinov-url]: https://www.spacinov.com/ [retool-url]: https://retool.com/?utm_source=djangorest&utm_medium=sponsorship [bitio-url]: https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship [posthog-url]: https://posthog.com?utm_source=drf&utm_medium=sponsorship&utm_campaign=open-source-sponsorship diff --git a/docs/img/premium/spacinov-readme.png b/docs/img/premium/spacinov-readme.png new file mode 100644 index 0000000000000000000000000000000000000000..20e925211aecab7d5be832d9c4cd96478fb2e909 GIT binary patch literal 56997 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 z zeQ7pu-9z}Zl1u38fazVUFQPM2n8_A4Us$C9?!t$ZMH5Q1XXqUao;`nTOc-xarRQ~$ z1h%Uu?twvJvzclbF#h|bxxn4`f-u0jPOSPi^>Q!GR1{;+Sa;Fzl7o&$jF<#Okvx`I z;k2OvGfQWW9ve9X@YY_ZXba180?ug#g}MU~wa#V*Pt%CRwF1ZhP&p9>;zmA%n7P+- z9rS6a%3CSlWm55Aw)82~Q&=`FI>hwDmVI_l7Xx?N5#KvYNxkI@iz;1J!F6W5iS^+@ zg$!?Oe&z~BofS**J$8)2eX2VY^GbBc8-5m$4`Av3ysTgeufwcH+1W5Kl?m zk929a_awfs7fZZ6gvUWf$u@fNQitzU=$Sye{Ca#hK5`RWH95m#fjoLnr^>IV-XeupA#Mp1rLc}FnUfoblgouX5C3;{=htaMhu>=*=_d%L9X|_OdGNy|1s{M z$aH_wCsBv@{&ie-qsLJA8!Bo|4rXJC!cCIq+^FE zXvC!G;Tj$9hyyV9QP9||@$Se8#z#B3Vq$b(p{gP|y>32%5 zSt_GP!>S|L1_2sO&9Y<+2SV`;F7MkzX`k{cOGUTRvKZLc*tHnyv?ZGj$4HL(Ci10*lxVSr}J25kLI~*j{~w%~8-sr#-kt znp!8GG>j)x0&k)tOl(A>Bt&d2Xu=&az37*6b}W4Up6mlX=n07&slL=xRH@6`%b;kN(CKg`z8&21JjJu_sCUVzL2A0Ps{c zHkXu|osL&ho#*=oFf=PF=ej))1B!_oqt8JtZNJy)-Z*=F39vbcAgrFkY|YOkR#mYvu{n! zTn5**waGDNej9;!WJSj|!{S7%-a$5~D$B$%N;DWONuW=AMSq}X`#pVjDXU7Bcmbw0 zvO@=Y)aMl_c;DQyK^}zbs*z0SQ=Aa$y*gT3?Got^s%>GuRj*X~l+5-LAlJzpUyqmV znd$qy=LeDjeKWUwFX&7sQW87?CeE2hj%5lV<7-t(er~ftff=Nl+}F%?N-9m{k@qdt zcw(<-Gb(>!h&rohm|K4_=$9inVUM|W-S%NEhzU@-JQzzmM7e7#JIPYdkSLYc!eRFk z$f>Q2lDSkN>6bJL4_<5iI^@&N%^I6qqQy3H&xPKW%p4CNj7fwI?+k&8(;W3hMaW2DML(z|% zV%cmu;pM63sCl*lIMn)l-zh%{LbBnLWP*>xnBSjm2nF;YeEF_gp`PYOQBNUIq6LBy0Cng_EocqE6>KY^2J_Uo%GDUEx=?REWI5x2Xv z_ZG1xcSrdq;iJ0?jaKBqO=VyHuy8Gj){aC0Cu-gYjf@Vvci;3j3@2N7>JOu{8{}D1 z5~Gq0C$ZEgaYlG*-MLAFTIMt~%uOJBg~rBpH(@LM#2$@U9Y-6Yx=yfv99`C~ZNI2J z286%L@aA?yU>n?LY46YXKHv5Jr03d5YAs&1^zJ2e=j#+IIW`~U0d7pDAS&MFCFm?7-weu z8cAhGVrVPK-2GE!w=sdDVkZfDUyQH~u!3i-trX*C3NMadOBa#yiXPxm6*YU^;-nb5 z;5Y2G*t%jyRqJx99OG+CnV86a{RKrC9)=yMDb=UMsnvorH5-H~Ns7d5Sg6oJ%RMBG z<7nmv-;Nu+ZvyGtymSm8p4^vVxZ2wW&STF(vpE=Y&}Q^F$>rC_((hdM@6Y=vip{kiEh~$lEHMH^QtHO`f$W z+V%_`{sHR_OnDbXdQgW~sAUW1vF@#_MWiVy*}{+4;?4?-^4lQmYRdbY-SM+jM~V8f zjzfPy3GyGp-nUL|Zjh_*RaZSf085!jr8wCuhMl9t$+0;>`_qdVNkPuguDcr&po}Cs zy8${u5DOfh-&1IYmr~FP@#pNnnn`VA+bW zHn+9<*4bIS&$?apHZBdT6r@>tdSXpl*$Vr22M*}o)GD02hyhPO?cUm!loK$6rU6Tk_0Ic zyVD-dVNm8;Ne$kWU{YwEJX1XKKP;iPsIL;ZYX$fEu}vBJzfrnYvErh*%-rN({td@!jp225d@X3jsFBQCM}14N2drMGp#d z7<~jq@f^W7gttu*)?c30f>JNa!9&(~&Nu}JBcn*tvbji$SG#0x(!7|Y=G9r-X2>%p z-ByO@F1k8ID65eEe$}-wor1x7@Cjl;d(B%}yiG2l=O)IE%-S$rtdL)5<%%`= zPwmfF=CiBdXQD+L131`BU|ZZ)tWjL9kx!SAR(EWa=NGP=8Lj@tCf=L{9R z9aRQ2+s;JYRN4xMIU{y4-{!>ARvsF*6C+H&kNxxB)-F&})4bM&kImL0+lRB@ zP)--&Zs>ZA^gK-NjI%4Ct}wp|u{(wt6Du456=`BIM=h})N^q?ryOC(hgF0tc$YZ^G zvv8n_sZHV+Q1GZ!W6&yoOk1soH8)@P_66X`3cJL5KAQmjMa8da>9-qb~Wr7Jb{BTF%l2ThGK)Jrfp! zV&<@+tXpRfD$t@tm$At$FIp#eV2X!iSqtHeNVtxk<^D&0Q*|Ax4xH1wza~p_eaCN5 z$?s1!k}-VoERTVQ_eG3MsYsHotV+~Ma0(KOd;<5d{GWGS0&WLyPD5z@i&O`tKvczaK< zZq1*`b$0pz^*fRhZs5X7H^kHLt70Rs(&B_xGXZuV_j$b(@#-%W`X(281)TBdjdNN#zg%jcX{yseK8 z5PWgxN`!g7)vzIr67A9wMz@ynnnuk$CJ8YPl^(G1aIB@L9|JU4)&%`jX6#51-Osh*uzIo?I2bV*vp>p5F&^4v>Ly6`zda*!cpD$NiipkXr-Pq&^b>K*!nz$R z-7^N)iFKEA1(GG><9gNti47`XsBI?+1f?$bVvj6U!1F?dzfwyLvXZ29%*#uvSD*yM z2v^>p^uBQZDOmy<4V9fUI6#+6vk_xJ8ioa>8gn^o!7o<;Z?z1smZAauNj$I-Vz`P& zZ9#2qXP%gQ&9?)~&UiUxf@#ul=E7e&(d5FiY!fuPIlf!g_ch>edI3m_l3 zLI3m!kY!?Q2r6WKTm%8g||85ZTkUSHf(9dG0xB?jST@5hvBp!VFJ=b9KtM#d@w39 z+YJQY18f3_6&Dr3jy*Gwt7sEgP zUJukE@~HpbT1s1o>JUqQ8NGd5E7mQV8O@m?;gn6C3^`eiXh9)`rC#l^v9ay?sC(tP z0*i)H&FK*vm=melCe^&cDQ<0t`CFUW|w{eAV7XcU=;ajw8&476kw;Omx-S zyeEvY5J6|U$fKqNU2qn^1-s2;26cgW;rEoa|RvrX-ctc=V#R>I|hv|I$)3!XXl%&?ur{ z)3Y_iFQ-v7NCeMPWB^GmCMm80zA9;Zo(7gS@e*9Rkbq>=`s6q4azzkp$?n zG~3EpnTyIbiHzDqgL|v|Pl}|}ur`Uk3cjonw;iA3*EEJL$U;8asUX{^VEoRU zJ-i9+p2(vq$;nRoq-K#L7)AT`A|0INONZN^ha{#3!RhP2frbq|1-?!b7l&{M^Gwul zIcT!S?tEVE{5E`Xx7(D{8=mNa4>jTk8Sy1Uj8Q)kq9Ejb=85SO_ zo6x&pquaxYGfa!3JhKpDqA(_AqBN`T^&`H|$ey8F8 zNXStf9~rHd{j9JMO}cp&fkL>gse^B0+_z1-2V(3_0jbNSq}A*koNvNMgIR~Rd3nH~ z->K3W_giN{N?iqYUaiyc=IjJgY!tE%I%VVo{QkIKnN;8A6+UeL#Z_rE1v!uCdmY5n z+0ZMa71z)mz}$9%Ubi+Q?6xeBI~85&8bQVv$vM&n3%e>Z6$< zKsAJTZP01KLwvR69fKR~@@{fGojn#GQhI8-#I;H~J1R?G7*L&WY8NO~rX73tyi`Z1 z!p}ckP*;v4=@CK2KSr5Tk4#B`mw^8uHgN%5^gC*VzyQ`&}32dWD*O}Z9-k?CdtXT)RPDw(YNdiw2tDZ~{;h-!3=+`9xCSI~V4;KtN z7nZ;0Q-oikzTCd3wu3%Xc&lIPv65&PVY#r1mDkOwx&Q6d@v@`Jn+x8h#Gie2EP8@@ zGHO_4&jE=bC{E#;pwI|e`&b}#)85~U4^dMIHp404*ufeOb8Q5{p=ygfdz!C|xSFcF zW!s{P-~0I*DQbWrio;El9l=(=GhhnfR?xF82M(8yesX{>ry`dqN=NM{K!BIUGs4U~ z{Ze3oCp`o#fTt*jyN@fR6T{AhF80$@6b+>#I4qPNe&`igmIyM2_|LE(h=iCRY6U&%wkrq-xz9nJ#S^r#e3k;P4H+xXN6j3MU$j6Y9RmsKmbWZK~$~d9!;X;PtK259{+&B9P_F*pXvZHb8J$%si1ZP=nnuq(g{5V?U{F(umtSv%la3<9hq zv*CDbdMzPj(-w}8Z{zATZIk>Bi=NIT<+IO?|Mj0v)tZ#}rI-zO@O|k=fNEC7`~nTH zb%;+805dgy8%6;{x)~qQZb6>{#N)0U?tY1r+o0s@OqrP%Gg&k{-gnwOv0J3Q>I_TM zll$&_=l(*W_#7)u8E=b5V5W5xv?MOHg$^aRUn1TN+kr%I@s;;sNz<^fU^`Dpsbz<7 zF(syzMm~PZKxw=URs8VJ-hTTJIcC%1F4WIu(KvLD=(1(Y`dMkI+s7ptv#|~LVyPNg zjq!oHUoGk=VgsR8^@Sn|nYvXH7-~2!TDM=t46(6AbL&NN$ZZ9Pg$UYuRK4L7ZlONN zj%WC|Idm7*CFb&Q(ukgxDJA0zq;WjVMN~zuTI6d74YbaMbt9NS4%znMr(46HSz3h1 zxdd@8jFIM{OPjQoM^`#*qH1}#{JJ`_((HUdGX&zz@Ma5Bin*tn>Uu`@wVX!dI7s0R z!6rK4t%_S5(_uWwG9es>H+#VB5}_l$yzOZO9uzbmpN|=|G%FfOGR*1deJrwE&|!!L zOZ+RMue*VE!t@zR#Lp`m+bkwq+CM zV{diBu|3`u^C2)&#yie&f9-#i*1?-I=4Li9Wve@jIeFg~%{#0YBz>6l@7W$+6;C;2 z`zyZ?Fd00AJFwF<lwvkf)KY=l-0ih~?LtGz92sJH7&~YS5u?Y7GmWvtBP?&rqY6Q7aHo2?aEp@2 z?X(P6VXoq=TW}lZZ6yW>f@KY;i7fK{TFJ-nTAt-Dfra_KEEWV$v|*Dns?@uuk=`2 zO54Xagw%5ac~e4lE%G&2CojZqBcD>^zXc20N5Iya4~24)uuUO@OMv9zKETR0BG@)5 z7!|DKJ72G*2Db85SxeoxNjt|ERE=iQv3O)Mjly|(-7Fv%E`j3{2z2#mZRkojq=N%( z_!W5HN~6*NSdaRkdAAl+aA;7JEz6o`=ENe-IAFs`cmvTwkBA7FCs~;{Ua#4+I0A~Z zNBigr*J6{@SvgvA^Q~%|e$(=LuhKd&0Cg;b+O`l(F_h$llIE{1-=mMGshamTtj#IEQGt*3C+VKJnl+Hfij`wUY?Tmr#sPpEt zbX3NgSz#ZB_=Q=EhWQzY++%+E6D3owpEtWdpayYO7%jH?*vCFL#=4_3{k`SAFi!&k zvRw57i};U6CKlSu9HfYcdRA=V(l&4b7TKns$EbMqnaHI!r7M?UhAzVqkL>)KTR zd2A(rsjk^G+C}6fp}`g0n>mA1mnQAtO&O<<5tBIPBlCd5w4zt}E5bWW`Aza-Y+%BGv zPnqNohZM7@kn~e+cUjLC*{w-!!tW!og`fYQFMY6TUW^|%W9bhWEd2@HxThhg@3MH> zioVl5*|?+Xpv%(*)D5i-CQh0zzgfZaMX`hDT*wu_)|8sRXG)C^GWPLKD)=VD$H-%1 zU0DO~rmXQ8ef;sq4@HI2M`5Txfv0p{E_c@YrVQ`ET#hvwXZ7{i|dvwS+XDt!WL9;Q1CJu=o%P%3Dd2Tz+Lj= z)niiwPbWt4-W_I({8Eh66~6ziQy&Ki3O8r8%4d03%+%r_xmvy!s&z?7bbuUo3)&eU$}#LhgNZeJY1Sd!UV4JV zRIA*i9>0;7*=olOU{-y6f)R}Fpw6)vh1lonoo9> F<;<N&~UKB_J(W5^*P>MtknutzDcc-KKFlEQFDvYeJ=A3g@ z@tGBa%d5jIXj;Ir4tmXJ_l=mi=a1>B{3i%=jE2S(E#2$<7rvW-P#w(p5SUM6Hf}g| z&T%fBt_p(o7X1v*uk((-%bBMC8qlwp($cl&H#RS2$WJtm0&UO}Hs`?U zh}wb$B*95PJek7-0zC7&O3)09_Q%^~5iWS`Bfk=}m5;{?6Kv;q*)RkavstB>HtXA) zXC}6rH@s#+$B@j!A8wlA^rR_r!LuoRmF&Nd+-PJ%A#-$*5?w$Udgho3sUxhzJEtT> zS_zTqB0*EaSo;%elOvaGn_uL6{y&?E&m3lhGx$g=sZh`HFDIi!tI3%Cw} zm-qzJ*!O-qF~ikwkQdXm5$An|s}pSD0CyVTGnoqnca3V}TueltT4#NA&BQLJW~VJj z`bE)Q*dq)yJ;v6@)1cE;yRwFv7M0ZV3(VS4uVteogh}Oo4vPjfAn1ujk7-_`*4dUts0!rYBH?7#B$P zKx9toHrJBj3@4%IXwj`?7YLpjMG@v4$t7d!k+}K#oSH!kqAdsRz~-Ohoo_I4dDy)B zlLyVo@yE>&-Oa)wl;;gZI4R`6%PAJPUc@ z5fkOqR$E`%YYro4En{obI$5&Zh`W?!47@@&7e8(>;ndh(;5BlQfak}A6jpvy+$HU@TJ$&NW z_rO4X#6?-06xQ3@JFkPJ3qoWFFu-Wx4Ln&D`8rF|__Cb1vUdTEBw0ZIY-rWUsp(r+dR!Gw%J`H%rB;8wS z@RZZ?qc?Iy;omcBHvg*mjXzjp{>dLO8As8KNzLSU_A%zrCVr#I;t~ULltl?#avP^lbzz4ma|A#R)lyjLg)lku z;rTGZJHoPPs`lI;c(#JDrGeOo*2-k_Hoe&-Wiy-&Vh0Ya#nJc_ODD=9IM6;! z^sA;ixY~>k^>a{Aj`xUq8I5LI*syu5P%8a5vef}VoE2-A3RyN37kP)ZY|%VFDMTi4 zT0nJ(g!-r+$+D|;`ycV79*ou&ZG|syoNvLgAPjxyop)|Uc>Dt>!%+2&UYl4TSi-~hjunuHP~l*;$J6%)PLW14-5yy(y0|K z``EY)T|f?f8bB_AZcdfA8$7^VAVtx@4OXGT8HL;fQ`u|2_b2n#zYMu`wI6;sH9zzB z##xc}6s6b?k(tr5Q<|L9pz`^5@Xi2g*$Tgi=TfI*^)hqK*T!+LMEMaCTJyo_a#8wf z>i_kui92g<`Q<@#!+XxLRexRw2bJ}O%#Iy95-y0Is7_3X({)<98uErILc=w3SlEYv zB;$N?WB>TxGRgl^F>PfZr06t~_!c^7uc0!aKZ2w_-hU+yNTx05=)np*H;*z7_B6Nr zouW(#FqkCpj3;xu*ZbOmo@E8Zh|hA#1XLetQP6!MPP&ZAhgGbX>d^)71k6k2&a2?i zY)ZXeU&~mZ6mYe=K<0DEx#Jm4< z(_6gXEM0o9*`X80E?XTqI`hj6%QKvl@dXs`NANz4_&$~AFG&&m7g)&qTYO6_8`hFH zszvuES_9zf)Q0*rG#*D6g`i}RzVkB9x7>Kc4L59N|B9XAB>E+pc@-QSa8h7@${>TC zZ1%F-;HfCpqhqJ`lD42lL?c>?d;4~zmM<+aQIU>8E*6XV*%eDYP;AV+C%9YD+gujk z#|v?TZ4(ReXR_N;Z1>o-G-VjEekEqjLA;!!NQccXONw^fh4ZE6jx%GXH+~9D<2h>J zAXzb0$-OL{OVj70452zN)}D_t*fdlCo&L@My(u!YRbc#sYerME)oeIF$hgP6G@Q8< zSNmDGfb1|@$5ECi9Ou)c!+)#^92WdeLf~^e)WWeJO2G->vBBQXq55P55uUZ!YM+)- zbA}i!9p=M6FH11yRdB56=@}U8E#1I&v@M8>f)g2f7spL`o)R>u&8+h%&|N9^O-$HY zw_%+>g`PT%P@B2-LpY~#cp^GtDuO3ii)fa}f!H(J`M6npierOe<+} zdwYXJYRtP#t^O`!9@hBGPC(XjYW|nJHG^%~k+}_+>rc)LzfXg2=x? z^Y|n9<_8p!T?+^R`*>D@*__r8_zEoRbbIM7_AjvX+f3U;#~eTeDWX zX}$(Y=T&e-Lxm!1kmzG_IZU_P)bqTc*YO68BVxIETOB~ zPp?2XYiFJg+)i&utt}0C_%>M6764>6enph3$Y8Ek4;r3IqsKf5Gha?4;qj->A2&zV zJ&xM?4DYm$>`y^Mt!UMYOf~s>nyq_I4ZlmGT|hG?IknW?Af(E`u+BC$tm2Fq`!LM* znaN@EBI6!z{7pGu7Q=+W$x- z3~xo3g*=DjLSaRrR^uCe>1JNkmQajzG9E%nyqPZ=%WRn@v&L~m_&8)R0HMa}&1Sh) zt@bb-8$cn+mR&txGrdDUh7q(6-Y;&-kJQk%s<)6Tj?pC1sy<6)%W+aLHE8teDW5SyQ( zh{yaA=}+B9q{^DBu~9A}-dyt$zUV*KI!)<1r^!7;ezhsbJ505(*Sz!3{?IJxA2FZ0 zPp8H&xH*3O_^GvP*Pi9FzlX_;m87CSOOO&$I>K^Y>pdsn5Ho#Cb-Rw}CZe$y#<)G#$UPsqumHB=Jp=#OS zQc5@XxSwY)#pi$ZG>Yp{BJbxpLAj*GT}B}`_hQdW4991>sF{=GxN}v5r{#FPshnSJ zFT-BYoy_hHG|Qrs!{8feo*qCxYpg>HkEfad^t@$Ja0gaYgl^HMCL}~OjwI0mKg0Ou zMOm0v!O@7DgY>svNl8%)Ifa8#wtu{+JqvK-z)rSspgSi%ae?d*7dB0=#;+T!!SIi> zI`DbEHhxZ?9Mt~ws8=B(2ip@Ipo0hheAvoFU%Z(fiepVEAIpC^9rOdta_lsB^erbFAh6W_I*$HGLJGUvXK?lI6>PSnBKh1RTVe9UJtRRCHq<(h~eb0O9VT zO7eVcd+O@th?lM9Fy*zF0{dVa!Xi0D$9BSY#Kqzg1lT1Dk9iXu9HUlfHsdmN!$3vU z%S+hTb+0n@SAlvUl(sAtAx6oJj!s`5tspH=PQdIF2nzKNS&0W(!qX!2)XWwFVrxV% zFx{iFDNMF~G*HJZ3rFJBIGFc5=XG4EgJ^c&Uon6F9uA&~4pR#Uc$lU|``{u99$4-o zOsLSg*vdxum)A)5V4sVw>fnH&G~H;Lg^i|BTyEETTn5d959B0!xz4h#;|$Dk780-; z!5i_4jOQEc!!6r~WVXed@yExajnPf%p<;W}LZQ5Q^JWQ-OQQVrU-%gd)E23D95%>+J(SHs9NQ z<+=$Q-%OXK`HU%)Ux1oFqB1toW28%E&;3;8*!r85ZQs$?fvu3)x;-K1l$Xjyyy@a7 z$!=kJc*+?cnzD_}HGH1mKtt^{{ryZ=Q#+6U*X}&1`14@c=AL}+bDyif?QL)EqiG*w zidNbH3$>KE)5hg;YzEK^6E`OyjYNU^w-LOI32tZG_*q)|il>^ErFkabnVdX-e(Tm( zoZqu&&&7QtNPPM)ymFqUp|QtE&>ogz=pIX5(Ku^?B#i?#N+Z3Rs+_qzyYH8Z+p9*~!5Pix8UWK=XNs$s7JO8r`&Y(j%)>0<6fqWt zY0QJ`4Wa2Vn0FGl6KJz-|J@qu1!3fBab(Z&0sWGU@?*&WGj$sGSO*O@AV?VZsBt0G zgMH@iuV1n{KSTKfbDm`E;RtPDf=37NJE;I4TdwtX;NW1-kggB%$!G1swgP>|XkTe> zlGOUwty^a)aN)GNW6TS2?%uMcI6gkk{*-6~C2MQ^(lo>y*2wn$3rMiX9}VE!K|lEV*&FQMk?H3o=Vfr0%9&Ob4G$vZ+1F?H4HUW+sS^ znu#+n5bdvK6LmpwB#d`HxYvvwd)h2tevsX@t65?2e?!Bytp8YpFrFQcWOXoyQrfEN ztgqR`C!+G352LjnDdSHjx{u`EY?8v~OwagVvRLdn{CXD5RI80|^)wnkiF(UFPyRz_ z9;^>pnEPe>Hjj;+(Yv+p3sYLN zG;NK1mT{(_9VwUNzEa%J9^O7G)K+PGwaSNRu)lJ(8$!wuM}vj4C{G{#@R%=TauSMZ z-%6fZj*@~nsJ8a zPWFx%ShAv51XJp?>TGO5evHes!}j?3xZT{{BfGq;0GTfR96TJ{(VqSqRx`eb)!%=Y z0pFWUz2>iuP=T+6*_D_rPn zwuv-ddegxb!7@`9J0uqPZ&o&Ch|#PfK3NE8ok4%h#K;%NA_5g->kr=+UDU zh6BF_n|+Rzq$k@xNbn27q{ZG8%(AkFhX9Eg?M<=l6OwqoQ*?e^w*u=*$ zr5nqcRf_Y9q*bOezx~nkW`f)3(xIO)nnu^q4ni-1JJ(WgD*cr=5(qPzj*1D@+W~GR zk)_9Vl+5s%h--7#*Z<^p*8<3r554*6S@)d2WkclB-nwZIg~7Z zNM|;7NPo6=D3i^XN=%UYNwkH6{xbyO&zUV8eYZ*i^a`7geQ?zL@u7p9ef;lCv-YnT zM*1&|BmNJ{Sc9plycr&G)J);?5oYsHK3yJ5IMzZ#SV_c`=Aj{YgWx0`0= zO{T)yn*}h;NH&;J{86>h_$DRR9XW@&Qj$es;%73nmUIef6z&PU>Y>(aaV-u}ET7R3 zEWWi|eD`72c_o*uam*Ju6sIY~8^(|)Lnzf8~6wT#4 zzhmkPQcARs(V+=b=^cU5o}~-;B#rS&Ds(?}%3?9Z-gG9^`)us#(zsZ7;I}CPf&}yB zQThS5TVAR5xG-oMaTjac*kRXt{OX@yV|MP+?BBc^9W%(JJrn`=pz14C3}GR|tR8$^T+ z4k)tqGOTN$n?_+~Unw=t<|srDQn7a+ZvMW_P}9{)P4v2uX)hRYIsqYp*(4TlXKVX3 z9jUYli}8&_qxyH5X8n_9r12?ZZsKw!qvy&Q*JT=^0DsOv;eaXk?Po0Dm1s%71x$zL zsEgTQrlVsATJX*h-9}Sgsh+1b(}Yc_yqP<5C|n5b(QL4OY*@DheAqNW`Q)c} zhcv7`KweyH(7Y37eGlUcuV%~xK{|!oU0E;pZ(pT`>aLU2A3;$6DK+#(o-?>BwzgDb z;mI5UFoQiq(2U8<6xcP*4xqd;9boHk1LBG3LZ|Uv3_rci^!EI$`PDzaVZLWu9(w2@ z#zxZ@(c01M%m{LkH8zfUf#VXR$w+MEg@%XaSjnOZQ&B=RACFA>=?9^4RMhqZ6+)C* z+w&qgI0jyQ!lk@4V-;?IQ_{eFP(xWl4F!K@gT|c`saeWE3A03l{Roc>zmNv1{@;5} zQJGB5rcaR?Zcz_c{%P~%0&-4^GF!ZSyy@c17Ot9YJx*|#^tF6VnQe(*V^*yyU&#Y^ zvl@~^^!`tOcdwb4{4TQ%f5!9Qso^uoH5fe9ttuVdxjb`1_6c7o{pr%|jMojX6US*D z8Zbq0L~rNq?qG|=4UBorUON{3E?`(q+JyJ_nXxg=Vx8l2F~MMFOI{G)i;Nb3dp-*+ zWHCHTh3u3-NCca=OZ#YW{f$PcFspVJ66&z$MR1fF4ebGyrqJu?Eb4m`DkAuy%2c63 zm0*&zxw6riVCV5#1ko%_wLU_h?VF5%@3)k-UGH4$&NW>1eT(NKF^G#&m41RTc_nnN@! zt@pr$3ZJDtOSHCR+dN$o=mP3mwoW`tln!9<@>PTtBrNWNS>M9@?lAxG(_80rg{ia- zwtnk0^u2mu6T3hwYy5$4JWu8YhL~6WU@{>S0EFuWK#7q@#r|fq&zv}M(W{o{WsIXy zDrv*NPT}eEJ5+pe9n}Nvvv=s`U_D1vh*$FRYoB`RDXnU&cSD;3D>u(EI`R$R*Hf8L zM!7jQ$#A>%haFyqLfn=+xCD}v>k{Np2vBxPt6Th#&fIKu_c${%x`S~K_D>YJp>5j< zGGW|o{xX?ck#g8y`k)Q~I*c$oX_gfZG2Zb?%6bcB+hEHrf`}%wHJ$Y^+jujPV!AZj z;!w!8dd+!4`2`B~?+&KYp-h~f-h2h86F$OzqkgmMhZ^r-F!R!lv<^;2udzT2ts`O_ zB6>9(tw{rnZS+&hY)hRhy2Vs8$X>2J9wdnQbQjVd)~gN=YX%QlHFtB#8VBl5v6Lhw z`nqi0&7W>1>QB)>+DqtA0xae-qm^khTFZvH*?(bAB9-IfQ3(AE3H>dUWy}Ltcrr%- zc)P>fiqH{2B}|t@2G1NFaW4s;FksASRcZVd))4%P`JHdAv9m36YnFkw3t>0eQ91@2 z>KMXLk-?WK8kazs<5qC||Lna9xLrqe=Ue*>cho#sl4VP>JYdUszy=c=NJMuMGeeRV zuM;rHT2;Gh)!M73Rk3YPXHN?_Ae$-9)^x3T z=i{>)Yss^qYc^SJNz*s8HUDj{XT`!fj*N)D=vTh_LJ_m$9B5$!Ot^2 zemCv+9Xi!c>~KiVx39HsYbTBVDS$-KCcDYgf~3Wpfo6^XkcVrHlV9;N1}DEgqSJ-E z@0%H>y@R=)mqV@>xzS^+x@?_wf4c3q+o~w%czmp0f1C|rL!5`N81+T^vH}J6kPC~$ znK++eV4_>WXy>vV`iEZmPil2;v6s@V8JbxSRk*fs0isoZ>n#Ye&a-k^aaS%M;b4}KUqNz-cx{#o17c;KH>%r=Qt}bF71t!@w#3h$ z7ablP=1bEJZ2hjfk)d7gWO9ewy7M`=^&$-!tkm!k2zT3WsDpGjQ_`0!J zVkf?g3h{2i1fPW&Og%-YDJ(*wnelS}j^?e*Fptl^^RT<^UvL#e=}A}x(jUr!QjOE4AC~sErk9<2R32;g zxh|MxCgkA>_@h_$luSrqWyGCNPtT+5Vjh>%KAT*3vWhcQi`>@Voig@LMc_JxF&M`w z6by3&p>U8sA$1aFBDqGtF_FkYvcLdKc*ac3O}G&7KCFax^TF@@VQsYX+5ahJ*h zUb0`SB0?@JIDK9g9OpXXYs;1iE#WN=;3O9?RJjD4-OQa#uWXgKh<(H!O<1d+D0}MUeBEPuhSPl=vFUahw0tS zisu+MMLX-JHu@w4@K=cOe+VP~eKzUAOxSQ_Cv2%ieaW*YmNw8qq=(bl2F{OYYf2CK zw9BF?5OT}oqR>tLe0nQ30V&Lbtpq$=Fkblgu2y=l+qP~wOnuhJ28zdzkCM#;h?FO>}x5CysG4u zUNG?hY5E7KVP-}X-J zG(F)y@P#f~L#q%!N!gcDW@wG8BCIV!UCCdaMxs61_PW=tqX0|z%D;^QEi~q#I<>~~(udrhnR~y*3Pyn#)`#R) zBo`Q^Nq2AW*V)UFtXsG4vyGFzu_78v;VH-!>Y%n13MCVI^;jJi|h!$=-` zz$>+ctC+)*XirBFGtbqjNgzO}>ZbBoLE8XY5I>&S8n)nD!vrf6(KLC4LvgahW3bV0 zz#hh@gLcK#`GVI_ZOKLJ?y~>wN%zE^F!bLIz|*r9En1Xr+_(`*Eg7p-t4xr)K6r<9 z^w`LOB|r{@SRf(hq?{PfKti0w$58F|E!+?8(fjYe|Az)V%^wlstPB!I5k;si3`hm1 zE+b~|jlO2PIS!o&NjJllm5jkwqTZ|pZ*$VNzCV83ko)7e?I-SeYL5LNTpLLNeT}LT zRf9R(YCMD)Lb+Q^jP7U?k!w7|--NegLNc08oXW*^VNM}aPuIe6uXCfdi`=0jYuzo^ z_k`uzmcpMU@5fPye8Kk*yPg`F>D|MWS040{ zoMj_-W?BGSyzSG(!~&KHiHy_>QYo>55zf^_T+SxBrR@5J$ELQT^ zGO=`?vG|hvxR9pEvEo^!|MVNkm)~I39tILN_(ejQiXD5ikqdPln%di;6iw~v z>B>6z>F~GCX|(#iSPUv~W)FEbmBsXyycQz(Std%ixc>C@%y($F=}b)erZ>H5eB{WH zlYI9c=lKCo%@_o<#fk`|mZLXvi0c#56?myMg%N2FKbb9ecp3l^ zFxV04xniqAZTmBmTLmfCxzJUl;vapX@7~yZhVMn4yi#;y-RCxsx&HDYmdf`6yqo&o zOI7m)HO13vi5ZwkR4sPasR=hXs55&8*-VQrI3`Q1O=|VkuIoV0EH;Ow zm9DND9pV_<6pry8ZR$!vfqo+$#~>fWuP1kNgdh4DLdS&feI8^|_pBL5VZnkL7r!mT(vvW7#yrH)@Znw=Kr|!7*EhBF9zyYSI{tWi~DZbDm zSg~nvgs16?rLY)90rOujvG1%=fa~PeEjW5Q#m{{>TDnry`=?CIRsXZAj{mmnTYSAc z*nQC~h}EiLZhxrgy@MxQ?S(zAIQ~(WjQn4yCFJsJbek5~`5Ou1bhfCqBaGto&_4Mm zzDR7(JZ8|XA($2m`|IzWBy#LS>T$GQt}I~xcYJw5NI>sjLfN1r^Z;5f0$Q$ z81kuVFPCHo{dzi+<&~pENy8)&i&vJ-l7?gvrWg1wYrj)*#{GZB31cltS&`S{qnDYv zu{O8lxU{90OHS#7UANqF%jva(waZrA`S}g3s&LUz!7d(RV-5$gyn2j8%kqGTBpe%Aa+4S}Gu>ev(u7{yY`0xn>ZEsej%@8DSa9Xr}T8s)Ji9_=oEm2eU z&U`2kveVAj-I|v9bUIE*&l%_6(W|cnwU><_Q>bmYEXrl(ViLwI4;Uz*{-k9 zeec>;tLm@1@x~$UZn0ro$aZSh^PjJ~tKT@_#w+_>sjC~*QU<$*#9~YnjTM;SR@)$3 z13URAqMerzqYjk;V+xE|>eOmWl4|GXmDnd-_gJ6n|Mde-^>D732U%(8-8byc8-EPO z@jX{8A=Vd;Q(l-JTDR2vogOueReJ{*z2k(m;l)h~32xQhghp<`9rFrFvV3EB%*Rlv zP{CL@Ky_1;!{liFQdg}Fy6gVkHusMAvhX#%wph7xWqsqtmk!dzr>eEuzof5p(dqpM z-`;~(*Sm@H2ETdbSCPVx`EYxoI~_r>_;*<29ev<|2fin;>6%$f9ImSi&6%as5T>=Z zuX{?EtLCh{9>Pab_pC7|-OQexH!?nc-m+!OX3pR~_r6hdC!B;Zw^M76Gbg~;j`1Q? zn`Isys^pH3YMmre@;IH{8PQ~!OfAkR#HhfRN9AWz4^n6k_G)|^nBRE?yI8JqmmR&t z{p)+?MPH_}oxXvr?_=KMsso3)ua)EYTB_wqlRdP{Q663_QbUW~;%y5yp+HI`6B5S` zpV^J52x8!q{^SZ0olWordIo|dm|Vz+V1r8zZ*;p3PrDuNyz|aF=fQ^A)1lqaCzzMe zZrs2nL<8oFR+Bv;GL%tCU_doJA!ypxtQiN(rV^#X9EWN#nAkAp4R06YLJKwOyy1!< zaK>)B!nO5ca_zvtzzkMbA|`Adgd02g_pUPd=XC!2d0-%#m{Y}1=Mm2ME}iTM!aK!r z^_<%(hs=aBC{tEkw9B;i0o?x{M)@9B8@twNC+;jnQ=i`){_58D|Ne_dUDp6QJ?fvL zocBP@-(@O+)i3ht^mL3ML8Q4!?TDMYi##fOEoB;v<=+k`@fH{C=xLuP`&R7MK2}=l zQg3-xz&>S*&YiN0`Xmbl8frg7=6h&%`{?=B7Z1Tjh)Ei_qjVwi@IDV?aKtl2Nt0@I ztWd3LliG~TtQiM$9PCf2BKcUctq{5Dw`y(FbgqoC@^e9XC+(o6qVsvj^A|0e`3|=X z=Z0P5sJe$+OQ&P~DZb2HuaHu4#yJ2}Gt7kvcnqytZXO;LQ+PWX+X}7OeZFwqOIoj8 z?)F^1$^EN8KhGG5Z;+0u&s+pFvtS3Ck*b4irGE;B{xt2Pl^0gR+BHZdnn~<-QB5x- z0GW1xA*?mZC+P6oCQSxuag4tU%XpiM*~q-gbhl3blBbmOU^RyQ$LS9zd5kxR21Es| zPZkm*LV0ptX2y~V6V#?CC@bzX95WvRgiPMJd2?T>RO&^zJh=O8dy&Rrkv0cd~)^S+s|| zimdqx+k8i0pjP6}_#3>jGmOQACOQqe(;#UdZ46^d)u01%+JJNkHOsxIg-fW?ad&dv zPIvug=N`Fd;!v%9j`JI;c6snG#@uT@fWo4&T`bx1l`agkDzOpT2s=!vND+u=C0K23 z3siAiffS^);W3ctmxua{N#xBuf(ib%X)Q9qv`J@WRyb4DNlH5~QC}}Xe}%ex{0ka# zOxfIg^G|eDD#cACDDjO(k&sp&DYuGlG~wBW{N6;~5&|NOd6S`9z4jHp_Q$^Ut#3US zQ%uL6wZx$f^_5CROKN%r^7gDF79vz_^iIL{H~>_qUcelw^q4N?6t2zyOdnlSj+C)~ z`LOFQNLMCXZPO>P*;$&Pu1z(1x~B=*x$m*2V8x}trB8`l@9La$sg1wFjV!&uz3~$a z|7Jd`KF2Sgx{u#D>PDG+D!QjBFOr9LnRb#qCd)n2PtMLcC#K9twz>sPSi9f}hy)Z& zgpYo9B7&rcb4F~tPhQ3roaI^66dlMKtf8jI`5GW82YC~6jTGB(H3b^>Ru~B^rKDKl z106PDO|cL;uvs&XZM$|IAFVNK?A52i#Bk;6FxI*cj**eMG>2y8^1)~$bm#F|yZ}8x zFYoK?x`MIZtR*D(@ta27(Z}|oyW@YjLiytiU3M`*JVD+}6y+*C)^^_3H(CtlN9v)> zb#7r#QMQ}b-^*+M7uTJ>)vaI6t?YNrY^fWRb8lekpLuAXd$xK965$<4M_=S)^esQ< z0YT{NEozNl{Y>qG^P%adnqYC!2YqaSIg~-wNcZf8@pZ7OtH6vN&$Yyi4n-A0YnsA+w znKk1eSKT^Qr8FG z?c}e!{^E7+mOIzE4}4{Ilh0PBx%k;ycXzXKZ>0DFo$RZW^=o{kzYUFO^#v0tql4WV zoE0e-k#ja^uO%>J1}))+Phl9rK1z75XF;shS!JK>x>d@=N}o}t(! zu2kis@e$2IP01`@zMMq}w~tMqhc$U6nzCQfLT2(|o=Dok&`(p;6OJ-g8Of4P(=ltt z5%R0kz&Zka%HGHVV-0pHk&kZ=6+*G#LVPKcjf}@G9y@+~*{8N{h4|;loWH-uF2O>elj}&a4M$Gq0bYj?i%DnIs{pa zIIGC@r_O%o$w0-qS3Gzj7 zkc-*rw$%0Yae33^LQQ)#Z9;W=^R~AhaXme|7;t@w=O1An(pFVvXq0b@;8y2VE3}Pl zm%xX`mW&NJ9a|hBrfTlJQ!W=B`H5$GOEgK91z^@teQS za0SNU9^*lkUZj;ac56^4*va5J#*w>BjL#e)U_!VD&%mO?!)*Z3W>@T5%JR5EPHULo ztN+$-{niO~ZJnU)hIw*|f_x(zX$Bg}NG8MFfFZ69;~3+p$RzzWOW4-IZ13z%Uth{e zc)OqBloz9@i&&8=Z>x!ME#Z^Ypf0uUWvFsBOHaGs|Ni&0!!R7nZox4l?}4GJTU_4j zDpfX5rGEkQ*ud9oGq|+!ps#bYnK9p@GA4P+=1hXmw|Cp9$pd4>N4o{u2N!ui6wq*2 zNUG&en8~l+vB#b4`X=j!oR}-A= zOl$k);X1`l2oH#}EJEH9*97^Wf^D5a{S?jj6TCg|yF zFp)*lVe{9u)k1m#7&pB{k3nm+v5ZnJ7HTxdM(4G8tPayo#1l zqi}v=LJG>7HA$Pu4ne*xN!t!#r!+IjN2o+Wos31<4~)aHSAyHgQYMBAsg?|Pxc=eq zbBmmmJ)Z-NgHkN{>8IVkN1k>4hb0kRWbKhA4aPxW)*i8H^?Iu3cGb|-c5PtV#ZL%} zh)LfXmoJapk%=_mxb74Au9$3OWpB`(TzCYiDKQR*f?tk!=pG^ZE+05Eo?*!8n_XpV zgc*qWHGyXf7z|TNqo4fb$JE9%GIPW@P}Y}lyO%b)uq{(1QTyqcX#7&Fk86bpsoDgC zzYh-k7WTHUMy=Y{KKjv*9{=!%KRm5XG!x6x5^@ZwX=#ZY$;a5a2xpfjr^LMn8+CBD zD&Z4|0Ms@!HL+8RuqT*V;*R(xEuSJM07!S{O;Fx7%ttS9qh0&ld;k28-NMBK?voFp zwQH-jYeauW?X99ID(&C=-9Iu_jwZ3f&(SVFM;X@gpcr_ zG6K`Ecd#F;KB|y~ndBfgldN^V!!tK?GC3-lhk^{VE;|ycCzQxnkbYxT&A6!SS>gJr zH4KGTA$2U^?g^~~%o=S|dF<#nt1V;6ex#1ane&j+W3HG?S#1&RdiurRnO2(+o+Xb_ zkjgmOL)k=A@ljo-90PaBS|%f}!F{zWcdcZ@*kZSZE&n>}a#jsJu2?%nn{1<`J1GkD z9Y{&)C1xU(1=}ctpO)YW{zYN@N|=JB@r}gH6>I1Df#60$t%f1&ocA)Y?XW-19%n<^c^2yILFZ;sIykK5lqeKzh&Csp=Q-fh9|VK!T8-DXv3=? z94nP(H4L3A#-UH~5Hqz}Sy73tnq|+Mn$>T00k3N?FiiIE0($A9(`#yrIyC`{B+vttYwTF7JvQ1)ltSYO{ zMZE6{lRWAP^F8(Rkvvwo&Fj17@^wl*-vQrOU=l2cId;j0=4kzz zpH07N-G;qkNY#m(NupAZn!#BlRBqJ418 z3|sLTZi6e)9M>ZKyw>%1No&}oX1rLe6iTH57{?$A!A!XDqcsU1fhG+DZv0s@^P=J! zOL>4KpGCmK`tJy)w?r^k4-{;gIDgm;eEDv#KC-Wos+?LWXQt zvD$Ji1izk}e5m)sy>83anQHHzL77^TNCEOE-!z8wxr3M|dEeu_w2p{R^5%Ks%cv^r z|B(+g*)7>NfQg9cCcr2l;BRFr?O=5qT#~G074LjEUQm5cYWg)isf~}PX@!lSyl-r- zT&frp?Tko$?Ti!(=`gxXy)Tz;I?g$fIGAgw(0}*9IQBDK83!rjr+m$NcMOT^D$zf& zz^yU(Gtna{pd`KzX1jqqBi}VLGW5oY>E}3P7?F6SxXo2+f5c|pU!`~60rPl)X9miE zXs_%uaF*Q0T-xN_P7+bY*Xmp}RJ(uy^nYTf>F=`W@;=vf(a$1zoIU2DCLUS$n5*{R z3$6V%^BuZ&@?ewH5Ib&18ytC36OF}3_}ixI6i(U+@Ok{XjLaF}3oNT9g&Row)2@dL z#7?8DW3*JN)Lc=ke%!Yr86*6*w5cCWE7nUQOsNW*^$Yg`2zH?gV^-T4=7e#mj$s_* zOp=a4j^mu5X^26pq$!zP#kPabd!q9+I1DW0VYqVe<_~q7=LuF8%TbaWk_YE; zihVVA)ANViLx0+K)#GQbyt0V2-3`B42}ViVy7^E!&@5)jWsm%lNzA0uFPfZUe&kQs zf&J);6)R??7;7%S(z+%AE{LL$f;&Zr_9f2MrGJAt`$Yqiu2D!9lka&{hC1&&o2^`I z&G>q8*KuaTnUG5%SgE>W#f5csnn?oVi_8SV3&hAf!zj}wwDU^deI29H3tjEldG31` zs{ZG%wL8`-bk;q0|=)V3TGt#xt^H$&yhd^w_YSl1&=k zv4E4N>JUi*hv4{+Q7hYVPu_U>257Ft)=JI%8JKs#n$j+wi;zq{gx=E|I}~f~gU@^7 z6ia7^U7`D<$Qi#uC;9@N=s|{)I)EfLf94;%4C-|G!q=t*m&Zf(;myYWaSlMbC7gRR zjq`3a-Tx=o$AP(9?(RL^7=7*=1B_cyM!mRu(2bY>KUeGe1fnWeesrNb9Wiia_EhEOtYc zj{TBzS}A)lYGbvU7SB&(G6=Db5q8}UbxZH8xXUaJV&~kMK2!)+%H{7Y>ZWb1$uwZ*$4or6Ydypw(?ghA(6u-pD*K}VZZBJ&{5*pwryLwdiCmB zPfwW@5)etU@Q8#A(z9Q=%OU&K3ctW1nE_IYWnmq?(?f zkZe8Y#q33{&wTO3Z8Fj~xoMd`yCs9vB zI84;hNLsEvr{+D>7^iteY57O*>Zwnzc1APT`T)Tw(Jq$OxVdcfS~tRW>g!GVYig`^ z2<9wIQebhU!q8v`^}mC1^L6bs@1$boo}9B|dE%QGZ1BXerbGtN2x;;9hn6hpXUfg%vYg;TK!XblO!oz&*a!G#d7(^O`F#A-*CgS`5&@m z1%p9;^@C$>xceX7(d5s$8u$}5>}Plrb~mDnm1%Hm#C(T58*Y_1j@KvH@ns^}SR?T<2e-k`$RF9`6+qMAH4GhgSAsH2?)3SW^>Z_SQy6E7+B1insu+fciBhN~&!X8>~2lk`1 zBWGQ7*)?@SrOdqv#A1jWq~h~x@lVPCK%n?3n-<*a8Ju6mV!4(Ju5)7-6fVx6BrgUi7SPGOmeh(bX0c7o9o zasj#3)qC8+lMM64-J6XV1WLOTzC}%O`PRtVo zjA9~6*^@kir9d4`C&tk>Ts57;)W9tUzLdBNndN#phtamXrHcn$?@y1r{b%SBs0Thh z=3cSA#x(@zQ60yj!po=^jw(>HKxdxlfiYiB(9qx`k32%e?95zC9I-Ux)Wac=bKj*(KZ(iJ_asmgqgv7# z6QRZmNdclwE@r9fd{$T%YrbP*;&~3ajeAiKA0Bn((qY!z_fiGlhiP!JvgwfET!q?I zO*_lSI5hM(*Z9%I)XN znk4Kd<JrGOTekY!NDC`LbCY|++4#_ z-$s7Vz&!5ZL%REkC!Uy-x}TRXK($;RW|iY9Dqxot-b+rZ!6_Qma8}E0(sYieLWyKz zB`Iv83~y&5Y2Bwj^{EA0wrrVwM{QG*(+APBhaiGemE#oai+ln9nhth16QgfR_Op5v>ulYG6BDI*b(WTlAo6zu5D@@W^I50g7*^4_Mr zLYhX#AyFQeMM@vaBG7N|);ON}y-YC``bx{b2ZI8v|jSlBT=ZWArp< zPp78SH1`^lIVTisGT>>%ohlZ4o?=dh#`1@8rjfP{XsqxyQ5MvQr^4ista2r?={t(W zbluXWOZ!>II!la$(rCry%m3z>`?L3a(T$IBk`u;JaNj~&(1a=7xoxi_;UwYe==Mww zW+;qdOomH1Cc)RK9IYtxaEepcOcNLf5?1P@U-Rv*mmAx++%;cxXPNpR81_fre$v(Y z2dHZ;&+bHoMN^nBmggFQZX)?K#RXI`&Jr3xK#!G&&)`D32%m)qhoy-y!ET08ooQKi zkt>Z%s3(RR`Zkte9z|<1QBQEt-*AV=rZ%*ilI}+8+Fq>|=TtxMgo3ra(u}-pQmLWg zY`9P;AEOquqf%!%z@?!?JCoI1yU3Y^C2ZSG(PN^^Qc|U z4%3_5_>r63=Fj>`y}7E_PIyL;_W0t$q@SipbTeozl3TR%0qw%_Y4Wh`!n5?TKp`Lt z$B}oNC&C81z%Z}KGukc4Cez>FUqFuNg_fy5eI$d#L3@aS#K+cBQdg-LkT{CTc95L= z5{IsZWKxpbrcxM3vIpJ|<7hP#>ZBE5cK#H`fnt4z2R}(pZ(?nK`RowRQR=(??U1|c zeUGx@@(pO_4#p4CDu&)n+bl*V`QA5)dy=RLC?*87N!%tVOO`nTkbU8B=hHNKB3$-5 zI>dDWzL)X7ufe{_^&ik2$o!in3#HIlyNRDh4)wZ8EmzSWPq3Qo=*h=*O49@z6Bw|^ zJ*9L3-ox^(t@pAQrq}gz7g>v0!E#|`7+u3P<~A4xd111_JYX1c+mIB9*Xk9wJ4wnv z8jK^pYZ=Yw=r?+Txdy2eJoAR7D|WK3rDI;o;`OUO^O?`Q`i?u!qW*|{4Gav7u|@p_ z#Ahwn-b%;%TL_$aq4Y>|kIeT@b=LThswHYt|74M;!_%6!6MP=7EetzM(>mJ3P=xII zHoCWbZ0)?OA>4<8=`J+56>X@(PF@`^mx-MmJLk+i`7pU7Pz+a)nGleLxBA<_O~BPD zO-$Rc3+1xLD1F?3)(cxm*n2>AHYANGaloMYSf{%y_3`7FQ=D-L@7=&P8`r!pRnkgB zrNlkPc-}z3qC!bBDc@Kv& zpzCiv=q_8h)73`bKtoF{0pfhZy{@L;W8&^tt43d!Q!f7Lz{QLvGx_KUn-6Q3*5W4s zX`jNC5cLX_KwrTZv(FuV;wZ`IuPtu7zUn^qot3zVPN zP!DTjAOm=Jpyp%X_S-)9o(X18f+kG3l-~RefxS9FDng zb|KaM46ONIBEH?g(d0|d$SK;Xl>CwRSKaoLoTyFyfq9Srf;jzATIGL`^Z^>4jXRp{ z!e@_uOgN1JQ?yBwNh(HZQ}gTmr59! z;LPHcCg6$=jW;AZv@u*1U9Q$@OBkvw;9@4kCu#L!c*0~Tdo>v0KrY;!K=4k2@9(PB zj!5jM988i{c%dVF^^fx^I!W5PCf)G`LiJjF-rX_5%VZ+#6?>pfB>XvP^JnQbL{%0v0TjC~?iLJ+DQHvs{={}wF?i>^8*78)o z@#&C%XH4y4bZgwDl0;phw$Ke72ortnK<6M#Cud8&x)Vp6?>HTbe^L{AvRR#|FTljk z-WDvvgv_FMGFW5ANE?YH4(*?8Hf$T!Ly_HvhPO)D8YJ_6HVpbzOGx#TT!V zzS8OAn$Hx~ot$dZ}U8SF8*z(Wa&;CBgoaWJ_Lu|DyJi}!DZqGP!y;xs= z^@O|>VE$O17T7#oD@evjNE6`{7Ewfa*)cw|Ls+3uM~^Bs$o{z&Q|apJLTS~iYOS)I z+0Oz?tu!xkhkaX+IMVtUzk~RnDRp;0SYEQE&B8ygPPea8x%^j$(F8^e@40Jlv;yumfV8ubwyE0?fMP=HtM2rzZS zrX8?>1{cysv6!U&^e2{N!%KK*65wD7Rw<$FV{Ah>{JGD4ZdTgNn=;RN=2hZ|`4lRZ zXL!*sv*qn+YF9T=pR?$Vj-z>saN*F_f(Y& zkOUwRBodqvT`YSD%%lYT1|QxlriqCar(R|-f)yM;ehWrOl(*CfHBM7k2i>wB$!$%h zSgUn07+J-$jFpw7#+ch(XqIWYb+N z&mN`zbXKE_ik!G&kV`WkPHQt})M=T1w6U|Z)NzWozMcuE;+i#Uc9Q43RPv18MM{x1 zC$x3z)9ya%Yx!yp?{OKFZdNnJwr(YERw+1!9bRlgKD?lrXior$0UZE31jkg(b}KOc z)}ZDD@|9rX=L3kWJ+{bw?;%a(N#Z(vN~NBT>8tS7=spR*VB}M?^Q}rvtoDHQ}(#PxHgTA+4kFiYpe9z8hV-;2O7i>wL{QRmto?F>yzk=c;px znhljYAn6~x)Dv|WJG5-`hcNSpdd%L+0&{Tk%P0*`#A(t?&Trx1GNUvB-se~6W}?Gx zl&8Cx<8U4trqEMO3+w9DbTt#71+An=`zW?%IXKIfKE({l-4&RJPcc8gXM}Mu45;%| zIqJ^V>hu7^7JXIDn(>SpHUBgM!zU)`k_#ZBm2Ta-#Tw3>wSrBS@!jt~<-U9fUFP}x2fZb8GvxI$2$9DnVyk29l`H{e=T>l!V>Pxn>U(FT#@7T zg-YnEp<_?w9V1DEle2r?CP1`}~$x8f+HwXC~1$~6g*yJT2R;z+r;$uUP{ zCpaP_*YMT!Zk(i_9cM0tb5QeV=B<(16}hKWdXx0^0ikE`zcuB(+l?5fF!U<@3ZOTK~RFfu{1h+Ja=L*dP5 z8a6?)2)Hb-tf13%F!@`1tt*Vb(rsKbKf~vvBMj>5FpgSd6|eLPaB>MkrcMN%yeI@@=G#Cr^xz@Bh<3{Zp2m=g-VfBXuY*oRCd8 zg+oXj#~Gi|lH5Y&`F@8K^A$|aG_)iS8je*8CTy87r86DA;DQTQ!mYK06%_8wR~crA zS4x63_p#;918(f(7~4E{tNer=f(DoJT3lJ67YmY;UiDlzsrqoTgkmmqV}ojxxi`z# zQz6IdMVLlC)J`^4q1>ibPoaob&7{o`O$&*C$B08dTTNlyQ7u;7NO#3u`B3Wa@?eT( z<6;+ms0+rXJserzwQqpqypwbL(?b1(Rx-0x^W?}fpC|lHXutjFqlZxKK5)T#=Y5}! z`VdUyS^%U@?+lwfZJL`6Nba=|??#lPEN0yM1Ufwac;(8KBWHbUdi$Dzpx%iny5*qz zrPqJTjn)>qO5tI;-_;CTeg&fH8@WP)MxYN9fD&Z!O#q6; zQ~cKQ3Np%3RGuHKmP&h1 zX&&(mm@~>aviB(#f50ifKV|&LU7tqPS=3~XNaK^i%sfu*<`yGy+{E){zC0h~*|KTV zrgM1CBfRZ>?;CY{ix0ZdnAuuuc@2tX@-RS%T7-z}b zAj4~q)m1(&L}c-Dv@<5(&}QiYGMq5fVD6>NHqpz8TCW|Wb{<80dFr*3?A!P!Fg^-% zd76*2mSN}JoE64VDwR&rDUY!Jd=wJZJ)iTkyKM%w$Fzs-FIhnO*70maJiL74#*Hgx zfa}aA(Rmj)G|X)Le)PRQPv%mN%#s`@LMKyCcxFpES#A;o=(DvhR~eoAJwD5OQJYm{ zF0`Ok92r@ZM_em(W-JTQk9tP*XQj5-9D|4v3YW>n8*U%k_zm$B5tEd8s9X5jzs!^5t${7EE^d+C5% zVH$tPfc-F;vj;Cq5qbN}6l==4=w6obV!x|&arAxL+}8CX=HUzF1+;B%lr-`Z1&b+s z=wTt)VklAakhmd%M-0T6P>7HfffUzx*y4nmg(P{7sqYDgL2DP?i${92+w=^Yv%)yy`%xEPtJKmz!@rlOX0ck&<4z^4T-Ewy}i$X11u(((%C#yKEz8r>tZ~;|?N0Nr55PAKc4*4Z0XrG4{7!*0FidS#TY()ehpH|FbHyLhs$*HEHyH-@{Jw3e~kGO&blnt8W;2LRd{Cg1pLzi56 z<&MwXdFQ<7-B2mpnX}3`xIFhL*B9&w!xc^NWHZ8f63V;Vy~I3*ndCw9u0tw%b*+|a z()2xrTJ4p0-+JpgK55EQURRx^z3O((K75;R)K?KYo}I_Knmy{2Nx_62lXZ4@-ld~N zb7yMZl_LAIS988{IZVQIEQopdC+otNVHU3buD=K;ci;gQu8tr2e&#U0exhl0hXh%0 z=C8R@aW6VbcDUk#jB#wMQ7&t=9e4iKp4sWho?$0Ts^6+pPkv$U5-Ap;(!@=fPxrGb+I2boy z=PKr=4y2y&i@&+Y)oPD05d0cMc5X8c4OffF2!jdcWaie?$EqyF7FNJCP+-ls9|?p1 zOrlUnFv$bkx^iR|c=B+P7(62ZPJoefSfZR?&UU7K8ipw_&D8C8T}*5r7|P~2Hf-2X zO4G`P?D4yh(M&JPu$&duR?*}b#P)3;QVz z%p<+bh|_kL>SnWZ#?TDxVc_a22CT>2SP98kTAO1Lx9)e-oK*=a|&iJ_v>4f0}iiWc4 z7+UbTOe&az2M=~FUA*`b%D$e7QKo2gfqC)-?#HUh_+i)8lS%0@&6%*zNavjFMG&*9 zLt|q`B0Fqc%ag(K;k2p^x?J>hJ7V%ujvzhBH|MyZUxs(flmmWTRcgmsF(-g>wixpq1ZT2IFohfPOs*}r=0S1fX(qN7rJGT``cL)dv|ZSeAV4|-nsTG zpZLT)?0B5~{iYb3x~*IJSaCU4ntTUB-ZRB?X9`TBIUfzwEmW&a;5ItWtP8+0`npd) zzO=VcTTZ861|L|Y-6G-)Vg?$97=w`C2U~ayQ~v7r;xq~~50)ekb)E=kQ38wra(D7K z$8o4F*@ddWr#T(mv@)V=OG~9n56t4?v|8Ut1HxF-y(sW`fVEoUM&{>dWjFR7mX!KrY zxnIq#^p_rAv0}+rzVelGmw7mD^-9wRc>CQG-so%wNZp+#ZtHHBJ9&6+rOh>ODb_2c z74@`y9@4{77{x;9%CiY$2A%;OGwN(>gjc>#M3s@~=^x@*n$qd*4&9RNlsNt#+P@EY2-cDY1lyvfeZ~E8=a3 z%FyJ&5MSJ{xpLutX1KI$JZEO*{*&EhH~xxpQhNn`7Hw&o6xD{Vz#PN?==`?)Dw}Zo zqxH-UH_>oKM!!m@$BP>|$wR4>mMA?QI(Wbv0^lP6D(hwbGV<}>X$ml#KU_wiC!{R>)bI8q`pPnt`8C6HSD{54Ig_hV@1+Hg3x0V zpK=H05&42iA9y4|7_8g@UN5LVAEg#AWi&R50Y)4!;Su~K<~fl4kj3Y{?%Dcb_sNH{ z{To~^-BYWMy{=lX+`x|4C)iE<9mewiR4f*sWQfxKV>(Gja~*ZgF%E_-^`l3J_oQj* zdFp;2o%9fm!)So+$=kgHu!7>w?;R#nt4tC97(Hwc!xV;3P!2PK3`MfKYUT`}O%;`bL zE4OazVou;?weDmc3T~hwF%M3aVqyb`_@ykZMks9~4BXrKEfJ?j2vhe*IH4ECf~GM{7vaNk4JAfu4p?gLE)No0$Zp zHKmf|=dGIKr-0B9_*^NTRQPGnN10$9M0yayT>RCqe)Y%~zxc(`x4-@E!$2xUMBJR+ ztb9loqTd@AmEA(RM6$HoRff-Z#o8JUVsPyunzsA~uvz6Yv&2?O;|x!j)2Q#J9J}3E z&vv)?;)8D6uV-TV%y2iSI<{t{w4hRCw`D<0V*!kuYZE<;+F9gdl}}?uGKqbo`!rngBEhjsDTwfdpE?tV4j8Ab4+t&k`_n%3f$DTf?@A+ zE=^I&k!rE@97B{B&V?aLe2a6Hao7!TaNZ*a4jdRk55fPSh1M5Kh5tYY6!Qq>jxEyC zI#7jlsmVw!t&F$w4I9#CC^Cn^H(Fci;}fC+B}9_*grgKbFY`0+!SyH0DK{4^*Z%`C zoO-O^RW6sGyZ7FEzl3hnkxMVVbZ)m+Uw>=at?OCj(t(A1$u4qf*GhDBT*ujut5EQ> ziON7tV@kYz7~{*FLEE-fKTjEW-0v~>u;1<6yWf4~*Tghp1K^y!=^ZRDDE1eBu8=0{ z)3mxkoI(RrMb7+_c_Ns>S{rAO5k8Nn$ql3#yT%|rLaBz_f~!Z|)@=A% zfZi!qdk&`kd(ary8Mt3# z7|@H~7@sk55~%&HJ!9e-*pY%G5wzk{e8#q7WImDdZ1LmO{sEnH`FXx>LomQM@nw4s zWypWddq@pF+rejJU>fgQw!obnS%Ze`*E7+0AwvOdDS}ScazIKuU14Ccz_ZC~x@i*_ zLxN6r%M?NCpsFN+XpTbd-70w~y{c0*C&|Mk5CXMAV*-uhWh|aB z{ej3Nj|A;(Rrh69SogXAoH32$mMvQr9vK>1SgF-Cr82@%;cxJ<8MyoIyEBQzBR%`S z=RD(}iqiMJ?|q{m``E{layi+9Dgx#XI1uEuqth{bZ?J|8^+~1AiHSHTFp8OBXKIFF z0#`yHdTO^|1xac>n~6&Q@DKm+0&%0qjvW($)Ml(%y;#yNA$B7}mbz+_yvT=5bULnX zTF-T+Ms2P>Ggs9Fs=QHcsphmlgNub+U1u+t)3M#EyV6UJiN0fxuzJ+D zisSjKlC;X|QPK@_w$5**qGa*KefttefH43CC{jJ&|7Wm-HjLo$=}m34Bj#ePL7~FM zphfAu*Kk>cTF3S!CCA}JT&7jX4^@*RgM)o6A9A-b^Ln&l_t41D-`sa!(>8R&a}NCc zC@>DIrr-FD-x&SqM?ZQ$a|AoklCGw`oSyhDzTmprAU4rytCV!!1V{9%q0K~0nIDHy zR*1Jw5rGt@Z;6b;yemr@{8{lRHBej{oUQ?8C6dP#P|^S^M~~qDCQG;Xqj2^xi1bXu zY^}RL-8$s1=l!D_bE-5EtZK8P^c}x*h zS5j&>WP%<{m;R8R#g}z_5B4rs?j3T2hfZ>(<-BY_+k&*Qb0?%)LPc&RJ`0sCq4ix0%`8U7y`}bV6aG%>bkTC@=KQE4zlB=PG71dhduV8k=aQ^%jbj$Bt`T3Dy z98_5P;SYa!_z(Wz4gjQgTd;jrkU)$SLE*~E&6i#sRc8Yhqz&xaN zL-PnS9PV=4P(^pDhy4IdFOvtIcR#Plo(YC9XmkgPj-@+IB+9diO_x(i?-ZdaD~xkJ*Z`BE!{^c} zie58s?7i~=KLYNAd5<#l1M66&_<0)XW$fyEpTtobPVLXHk%70x`7K7HPlK>xCI~5;=B^N-@B;jy4efW%MxvO_evNqPxTs@$x>D~ z&ZpOK?nZlM*c_JD*J8tPQtg}Zg>U3up$Ea_QjWnhKa*-0Qr`>MF5J86mCu8hhvNcc zJl`4Ad(j_ZZ1ZTYymMPu-*DkAX)U>=TCaVW{>~vpE#X@~wwxtZXx>81K*s#o^2c!N z;sHi0D<+8;jCFlpE2NiAxUHDtVC?Df60S71Dih)rgQ5sNbjj^7pWCa2;tuzJzj`Op zGTOJX=){Q=Trpi+!H1*t+@3v8ZQHhO{+O)KcUHeYN)ktW(G6xB8@*p*tWJB+`6zDKX!E7bcD9>Qwf{}PGwqxtfaQT8_{+pwywuK##d() z1q@5R{N*p7fS{yiag-IA=_n0K+YSrf{Asjdp?EHJJCS|R1yMV}>I?U`){#iKB`hiK zp**@>jX46pX{u2Nn(Rz9XCWzrv>Zg_ONyKA;@k-$eIJX@KL&h_+vaR^&`3K!t~auZ zvU~60{&dNb_3X5~gq0GLn9!7E8H)3Hm%L)`vIz-+vDGC}c&Sta6c3}V=3*)e5S$(l z=Tk+05r z9A_mpjgn|9H%nD(y1PqnV86y2XstKWYJ9ADj6nFERN^o);>QzHOym8&YD^LlRp?)E z0k8ZfwP1N6MF7oGOeSF{y@C6Wx5p!J{D#vkj~^9sQ-TElhhTF+Fu`cyMJ8aLX*H@xw5^+?%?= z1za+DO8tZVODk&baDUyce4_5|n}6w|Q5IYbQEt5G7uOUE^HcNFFgP!yRBe&VXBaC>%WEU6AUdNGgxw z`O^54d%xs0e0dh7A}~~0z(=CU8(@Nvu^8_>&2IxIxd&!oRERsn zCaT0QlZCb5C0u(B#Qd~`tNDl3OagZyiCl?$`6x{BJKy=v(XW5~>qpsPIL=10V;sM( zUUbn#nxT(qTd=lzt?p8H+qpxabTX~@P<4`MrfSmUdiKMzlYhf_Wek;$MKqWvy*B%0 zRV_B}moj1-tAflO+d*v-jKW0^GO8hcG&CQgY6Y(`f)E{$7^HqIA9I8s!=(>^t}&=1 zi7_u%EFqb-k;0HLY7AA<9~GvdSIp`myzh^qmE3yktzv)|Gj6|_cK$isKQ98}?RwPs z_L{H}xooC`Fgxvtzab@f;?QMc0u-UhA=^Ll3PT_4Zc<JI zufqQQ`!g8B(v{;;$<|qA(x~I*$mqR4?GOeiik%u~>7Ap)eZ7WuheBz(8!& z3IOLoEDyg_%64+|`6?2N7qjqfulwA<4!njlYEdzNF$@0*fq{goRk#kwDQgAQ7)p}M zL7>0L8tx-*^|Iq`V6Q#`FA6iI4)vq(rkti_|Gztq^fAP|$K%ZYX^ngXkM?(m^344 zWIGhSpRM^1aeV`;9qGe-!H=<|cY-+R_?%)MC0D6jzZWCQp+S}Jkuv0Z@2b_!;xvDdwjOUiJst6GchHt5Cd~1>gXpBXGM2Ffug9*4p zbw-@VR7fs(NJalJiR2*rKE7D0Ri7UI;2fVzQc=&M>DZypVwq-}^(l5_YT)}#8hQkg z{wJZP^N}SlVF*ieL%J9woZhrS=sSHz!41fXt{iGnVKYkM ziM=4W1Bes}p}kw%@RJ^t*=k?NHQEb6hdlVuLk}JM?svZ{#wsRqg8CUE?j+|TjxAic zaGcFYOr6YTvNe=5XG#Cal~-O_L?O*`q={b2fo_SUpUIoG`}XYVN0U_-?g~=J6DkWs ztwHq8Y6&vFbq-sz^L4jBu_h^%SHm!Fpf%YDSksPNOQ`D6w_dG~YB82BtVttvXc=w$ z&bFRM#-WsQXF2;*fRa2SBUAgDRN=XYEz^r}i+>1n2sSi?A>|bwWZCM8YIS5!TI<#U zwHKA~wfUlYCI2!IZWpuEY)?<#%NJ!6FM2brx<-^B8Y%F3(#vzrff0uh6A}5j4^e@A z&lRo*cuS1K{QAU&FrovDIWl-0MsNlv=^)^d-ws&$h%DUW4LAgY)6&H9D!p)8I`5V< zNFHV@iPAvRRW9}(1b!F(J%sL-o6dTG&dTzsdlso=2u3pa%2&Q}dcy?eF^Qp5m5&@b z(lb6hd`Y!dTgI7-R{+ixjD;I0!y?M|EdKqte~&U8*|1^57;b`3Z~ocs&s@j4&nw^p z+OP34y{|fPoPV&PMhH_SQJ9XO${htKyI5N`rHMjN3b9zsPy}&eoUW^h)tO99HA70Si{cXN(JL-kPaR_WJ zBIgZsq7@K>j^{T7g_tU{MJ_Vd4ZV|Jg=Xa492H^(+8mK#5+z!+kI%vlt3~8o9yA8T zkx~ftl-~wd11F{6vj=hOfZG~!>?2?`{+GdIG(mR~_c0^^HmDX(JoeaQBJNY9sVE*B zJvg;7zOr0RSQ1m=>YHNxu;MiXA1LU9-h*A{Vy!M1gocK_oc`6#@OoCz$u#f8Nmb5` zr0F5@eHspTKX6BSdwU1hu3c*y-VEa;KR177+cGwnY zih6OG7cJum2gP*I>)UJ?D?yz5a9=?4o`)^fdFoIidW^mmo&@}O7q|EN=-7Lk14CRh zh&s4W>TyPQ3~fr3@8$W39D~U~a$y3^!ect?G317AaS$>-2_||^JzCbK7@-^Crn3tM z8c$i$PR4NSj0^|U+yTPI2|G?Fsla_WD93(imP(Z-XF!>jfm@1=T0%SjvJ_bD+FS-C zch7J()#q8o&52KUJN`$KLH+vs0Etd;xIlb`)T$N4}Yf7czks=It#2M@? zT=51i&;T23%dL$YaAN|7LmrGV1z=-X#AUVsgeNco!m27Rc7-rIxr?7OVnyw#5HGaV z0OGChurzFCgAvG<)S(ZM`YYAR{u7u9ab?1bL>?mi1omIze-`jZm;^nHb~ovgozg19 zo9^7XniDmv>(x|S%HIP1wG?D6OpT&pD2EEB^4K2{=C?=+uimeJ#bbMkLGIuW@Y+fn z;r#H$#}gWcBB}^O>}x+1000^ENkl4Bc;Fm%fGCzn;3}3(g#LHPF(|Wj22z#GQzdbrxz;J3LRg_m|A2Z zlLdJ1RDP8-CJSz}M7c5{li=e+8C`ASj66l4o~elXHO4mdO4lf`q0W9H&X&9sABz@X z?Yl>x%WkeT&m-h0T2hqz!i^-B^e6eC{B(vRFy>UbA}qe)_2CiD2-4Dfgo;&KgMw%qm1!-#8c|cy2D&oRZaQ9<%>d}QxjMxJ(;%n` zQB466PJwc_!_|~vq9>w=NyQXmnS$Fe2WFAQO~lj)39wOeF-D^f1BBce!@(c3RpQp7 zC@dH)C}^0Yt?zpF!8EobWqhBFd3)BZSu^z|UwZJCyPhbI3>{fmDE3@XFVruiL|4G>*e;i^3cG*+7i1MSwXml zOxN-JG?l|)DR&{nZ_>Lull@_!i0i(6thG4 zX>n^<5z~^MqMJVpi*X@O3vYF+P#7yGw&pauFuOeqUuPQ(gt}R8p2+%5VzQ;MK#yWwJoCdex3>G*uxx7P2EIz2blwmjl zHm0F;q=_BS!aOG7)jEqNPwQx88|AGI0=EnQHYAOQn1nuZ(M1>K?+XbjC$YAsg|}Y6 zzb7qLm#~(zfzEkpAuVj;V1GY4LagFcZY#UQjo^+h<0&*u1T;8cRBnaE_b{nKegah` z#!v!2f-OMsSsPoR{~>G~WJ5&mMHJmtDb=4m|KPyD_O0(!UA$z>OPz5vHT14sy9Sx< z|89T(sqc>uvvH?TSX3-jE=~&F*KpqHD&_<>A+ugVf6`4W zP>I7n34{z1a|fa}C;}q=ig2?q97g2o+_GD_qX0r>shFD5ASRo`&?1&1V_jCw zPaPQ)FQK%=hbayK1*v%b;^seW=#rDs5KGj>M_L>oc<$g0$DZ5z(ioml zEt&Z-rV`^QH3g-9$(9HiDk(6IuWoKg26-vf=4hGhHY;B@W{$c;NF3Ssv-zUPrx~b z*lN6&iLgB*J+(tSZnR6S275F`#yu96fCHg@J*$IS>j2kWtf z-zm#OGd<2$ z^4+-E9y30~PEnf@P*zhl*WdbZ`P2)0*Rz(f3jIs}Hcd(wvDftq#EUM}@JJU4WdfU( z)0bI&TJNMde`SIE1XA}_6-1dNVaa^$iEo%uT7dk%j)vqR$poDlCS9mLq{c`UBk2vK zqx=qo{qwbY;i*csaDTN@IIE40?&0A2W_g8lFxh!q8T1J(561`LYhOfedB8 z-2Cpha%-=GM0dvFt{cK+2!kY|8B@Eer^XjN$_|~v z!e{{>XAXn$;lZDSwf72m=(rQ`sYk^IZHNOz&4t z@PdHQLvW5=H2EL#33zJQop|Qhcwung{_98GOK?s@^_Dk3mN@dYaT+%rMKr7NzQ+1` zbWA<@!WX_Ei9|XwbWiLlt~Xo-=`Ci9If^-LN2*tzo2K1#Nct!H5tti+P|NDPgxN|B zILs&$84V@8!*^`&Y5XeIV$=~l9^XR(BWOT+7w{7N=?|t!Omh_~x6o)AQFDufl;;%h zunYe{+SNtJNspC}9^K0!jM2P?F_b3(GIjIbdw2Fcf4IN&%%OoL5yY*a*!C91tGx(4u zx_wgO$=DuFqcUG{1ou7y_cO_-Q=G?{+jx%W1Sg31Q1;c-pTYd}ke(kp~01yd8 za~Fyc!<8)wLMi;^(v3L`%Y!H%PdY!p0XE4R$v=_$pP1*k|D@}>{|Cy7!<6eV_Ac&fI@!gA-t|&x2!?SOrMaUszJk(9;(xY$8y-|l`qII|dBtkif;6e4 zbfA_=#&NkdivE#+NIIxb+ zypd$mMGue`@E~5`3P$~oFDx|gq*r4U9MUnWgM4BVqs&bVz!*+;b$1WLEUKs|sc}C` z9lQSU!w=V2uU?&UA&@P%l1AU~IWY@Qb*(AXlgpXtd`DWWY^2koOo`2Og`^9cmC`zo ze;o}!^d9Qv9Iv(f^+Ga=gB=p(0~8M9Q4GY}%ouFx_Wy71e17A)syP0B&3iNBAF+ee z#KjGE(lmu4DzN|rEMmcevQKwiR6<2!&)#cRNc;nl*flIGq_z^RSWqMsKRtFaKesUdwK~)VS?)U6r*zlNUBlg`04y(s zYl|gL2jycI$4gz7IbDEcgm{#D9x{g0DzZ$H2z!vXs6&i$M7Yt4_;Y7#6piINpp?>I z<^eOn89PGTqg6yne{ zbDO2Tj3=2e1cs(yTdX<@4O&i6L4o~Ix?y}%Ve@>Wx|tb8ez#R1IMrAtrbc6_1j8b- z(TXkuqe7myjFj_FO8-vzTR6O0dFxltteAn^+ifY}faA8L+4Bmyw)AoRCzpU8zBYXH z3_i;TyB4{_bU5)^(bvOVV)fKhPgO`W@PxXESd>T^T1h_g$7KQ?TIF9j7JECNv;n zS=~wpw#Hzq z5{BBTnBAf^%2u(8dZx^ai`laG=gG-q{j<;e+KzVNOHjZ8$Cn`59lI>N8+*%({_OX^ zZT@mEy?8I~8Sgmyo-|m%yW06zK0Da*;w0)6M`1j6h+1=1QI4}!GXr}b2*c_aY^>YE zoL7<3Q(W6j&niA`Zdhotk}8JxNa`vR(H$j>9fCB!W<0aKss_9aNDvDY%o@zLGUKxG zq&94faIXZbTwMBpmp1E^zs*={%>7(Y#;dI-W^UkR_+BpInepA*O$s>RxJip$OC3Iz zw~8mmgYt3^1#JZ9g$35m3x*GN?L!{qKyc-|?|IQk>`nPzet_MPAH$GSXcd13=dyL| z&B3~8X=^5Ln0=UUCI=&bF5CnZ)ov$>_+EgbLY6v=29PLALZHxqhNKTw>hPSENNigJ zMxYW0?^bEN!cm&n!^;1(N_wx3bfS$j^T*r)F6g-%LC?m8-KKyfjkcqeWE~t@fbDhGZISmaC8D!Qi#uKC5lpJqs^!xomava1pbU<}s9hL!4wjWqUTzB;zBY5#9}(?rXkt@6%+!^N zgba%6*ye^=m71D5VI#>X{M=HeA%HSoe3T68Y;qv%0^L{Hje48P^|bQeK`zIZx_xIcyS0 zFvi)N(E*G)=&nnXws8Xv6lfmleGlXY*g*mQnos`j{AjOVw2~R` zpr5C4lt&zZ@?wkwjdPbP4#IdW^vjt4M0N_26_W>-_|Oy2MBf zK`?^e07Yhr{}YaU0~#=Wo8zi75rC}`95@+U80J)~J+Qj%>si2{Cyj=GKy_l6r@cvU zFVGl5fF=^>tA1OVO{&7C?AeRD4OjbtjtyiC?KNLTN$~-Tlr@yI7uYgc3!~(J+>l%Q z_u`?&bHDbWj}|vBJO~Qxe5?=RGu)Up1!R9>&}mHuY4B`W_|q`)Px*g-kpK3$F|4-W z{T{DPj)-rPXF20-aFVhIcjcf=4?f6I0_35Ozmuv=kzU~auq&AW(2-%($O3~$b55UD zWt%hI6CB6H2vNn$pMdEnh;fZsjSB$8`bJV9O@A}f?XgPCe|}Q{m;%3PZhO2AVZqmB2Mj90d7wiQeic6i@GRn-)sJu)ucWi?9!NtFX#o)?P zZ)xT1^N*gJ#cd?pcS-x?_uaG})d|6ZFL}{3KX`AfEc!{9ClgUCNn*d`-bRIARlR~E zF#Ta9k0UVGG*?mNbi(0B?{)Lin?u5wg-ega(Z>OS<8b2%5QAleA0s)i(%`KI z2394y2nTGGu)UCiN}i^e*_4yk!0vt+(x$FgtN`lN?a7Ja%P%hCF{pMvWTx-Gx_E|`;%ydQ z@1VOlPx~vv%+jGwJ9@m`@$zmJg`F}gy4ar@OSCzqUhloFsQ*pq6tY8Y#W_<1l3NFd1tY~@<3@`I=5xgWE4x7029zec)q#) zPFCeIN61ZK>20bH*tOX}UOJE5vBAav|CEupbUD0wzSWPiGxN@9PEF>08M*D)efe0# zjb=xnSbOk9yqxDnD;$a5@XKzi^wvl7DvY{O7h4yDB&fn^&Mb_v<8l~r zqYeK~0tuKc^iYWUW4uOSZk@sCa8Qc!00k=6$+T)9BkmXK@XJf@0t_3PrHg=rU%66C zfdTH2`Is4$6|o9y-&LRf*eLaTl%d%5*MW}f%wY+D>7(DgM(;k#ndRIo`(dxx8U#0r zFg~yu4ZLErz1$laBw6;+lUpZF)6<20O#%BCvahG;CU;v3z`Za0@k+M{(|9n!rf*qd zt}vZJmf+At zMpQKilqyIOzKjD2EHDlf$eC429%H`MmH-1uViU;*w-S_v;c}5xSFn@bV|Vy+l$QNV z{m!*Br!dWDf$_Fq%~kAg3h3{{g|9LN%nHEsp8Lax@o2sjcr#Ci=?2JfbSl_N*Mjlt zN*E0e1Yr^cF~$pTU=K_OM+*FrrBND;2OYniN1faoL*l5^R!9TvDPe7k>GT zby6-k3OEWl3OEWl3OEWl3OEWl3OEWl3OEWl3OEWl3OEWl3OEWl3OEWl3OEWl3OEWl f3OEYf6$Sne-T)PxgNG(p00000NkvXXu0mjfilCYh literal 0 HcmV?d00001 From 333f1ffb943370816f2e0b0137f0e7cb786a1d51 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 6 Jun 2022 13:39:06 +0200 Subject: [PATCH 568/907] Confirmed support for Django 4.1. (#8498) --- README.md | 2 +- docs/index.md | 2 +- rest_framework/fields.py | 3 +-- setup.py | 1 + tests/test_encoders.py | 5 +++-- tests/test_fields.py | 4 +++- tests/test_model_serializer.py | 10 +++++++--- tox.ini | 6 ++++-- 8 files changed, 21 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3fbd2f83a1..e6e43047e9 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements * Python (3.6, 3.7, 3.8, 3.9, 3.10) -* Django (2.2, 3.0, 3.1, 3.2, 4.0) +* Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1) We **highly recommend** and only officially support the latest patch release of each Python and Django series. diff --git a/docs/index.md b/docs/index.md index aa502a6218..2f44fae9a0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -86,7 +86,7 @@ continued development by **[signing up for a paid plan][funding]**. REST framework requires the following: * Python (3.6, 3.7, 3.8, 3.9, 3.10) -* Django (2.2, 3.0, 3.1, 3.2, 4.0) +* Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1) We **highly recommend** and only officially support the latest patch release of each Python and Django series. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index d7e7816cee..8d02b3206e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -27,7 +27,6 @@ from django.utils.encoding import is_protected_type, smart_str from django.utils.formats import localize_input, sanitize_separators from django.utils.ipv6 import clean_ipv6_address -from django.utils.timezone import utc from django.utils.translation import gettext_lazy as _ from pytz.exceptions import InvalidTimeError @@ -1190,7 +1189,7 @@ def enforce_timezone(self, value): except InvalidTimeError: self.fail('make_aware', timezone=field_timezone) elif (field_timezone is None) and timezone.is_aware(value): - return timezone.make_naive(value, utc) + return timezone.make_naive(value, datetime.timezone.utc) return value def default_timezone(self): diff --git a/setup.py b/setup.py index 3c3761c866..cb6708c6e9 100755 --- a/setup.py +++ b/setup.py @@ -94,6 +94,7 @@ def get_version(package): 'Framework :: Django :: 3.1', 'Framework :: Django :: 3.2', 'Framework :: Django :: 4.0', + 'Framework :: Django :: 4.1', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', diff --git a/tests/test_encoders.py b/tests/test_encoders.py index c104dd5a5d..953e5564bb 100644 --- a/tests/test_encoders.py +++ b/tests/test_encoders.py @@ -1,15 +1,16 @@ -from datetime import date, datetime, timedelta +from datetime import date, datetime, timedelta, timezone from decimal import Decimal from uuid import uuid4 import pytest from django.test import TestCase -from django.utils.timezone import utc from rest_framework.compat import coreapi from rest_framework.utils.encoders import JSONEncoder from rest_framework.utils.serializer_helpers import ReturnList +utc = timezone.utc + class MockList: def tolist(self): diff --git a/tests/test_fields.py b/tests/test_fields.py index 7a5304a82a..ec121c822c 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -9,7 +9,7 @@ from django.core.exceptions import ValidationError as DjangoValidationError from django.http import QueryDict from django.test import TestCase, override_settings -from django.utils.timezone import activate, deactivate, override, utc +from django.utils.timezone import activate, deactivate, override import rest_framework from rest_framework import exceptions, serializers @@ -17,6 +17,8 @@ BuiltinSignatureError, DjangoImageField, is_simple_callable ) +utc = datetime.timezone.utc + # Tests for helper functions. # --------------------------- diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 7da1b41ae5..abb4830d14 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -12,6 +12,7 @@ import tempfile from collections import OrderedDict +import django import pytest from django.core.exceptions import ImproperlyConfigured from django.core.serializers.json import DjangoJSONEncoder @@ -452,11 +453,14 @@ class Meta: model = ArrayFieldModel fields = ['array_field', 'array_field_with_blank'] + validators = "" + if django.VERSION < (4, 1): + validators = ", validators=[]" expected = dedent(""" TestSerializer(): - array_field = ListField(allow_empty=False, child=CharField(label='Array field', validators=[])) - array_field_with_blank = ListField(child=CharField(label='Array field with blank', validators=[]), required=False) - """) + array_field = ListField(allow_empty=False, child=CharField(label='Array field'%s)) + array_field_with_blank = ListField(child=CharField(label='Array field with blank'%s), required=False) + """ % (validators, validators)) self.assertEqual(repr(TestSerializer()), expected) @pytest.mark.skipif(hasattr(models, 'JSONField'), reason='has models.JSONField') diff --git a/tox.ini b/tox.ini index a41176d72f..c275a0abef 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ envlist = {py36,py37,py38,py39}-django22, {py36,py37,py38,py39}-django31, {py36,py37,py38,py39,py310}-django32, - {py38,py39,py310}-{django40,djangomain}, + {py38,py39,py310}-{django40,django41,djangomain}, base,dist,docs, [travis:env] @@ -12,6 +12,7 @@ DJANGO = 3.1: django31 3.2: django32 4.0: django40 + 4.1: django41 main: djangomain [testenv] @@ -24,7 +25,8 @@ deps = django22: Django>=2.2,<3.0 django31: Django>=3.1,<3.2 django32: Django>=3.2,<4.0 - django40: Django>=4.0,<5.0 + django40: Django>=4.0,<4.1 + django41: Django>=4.1a1,<4.2 djangomain: https://github.com/django/django/archive/main.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 5471f8a1d9f54e298b3826a4d910d56ad3c6206c Mon Sep 17 00:00:00 2001 From: Leonardo Gregianin Date: Mon, 6 Jun 2022 07:39:36 -0400 Subject: [PATCH 569/907] Added django-requestlogs in third party packages (#8497) --- docs/community/third-party-packages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index e25421f503..d4e590952d 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -148,6 +148,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [django-elasticsearch-dsl-drf][django-elasticsearch-dsl-drf] - Integrate Elasticsearch DSL with Django REST framework. Package provides views, serializers, filter backends, pagination and other handy add-ons. * [django-api-client][django-api-client] - DRF client that groups the Endpoint response, for use in CBVs and FBV as if you were working with Django's Native Models.. * [fast-drf] - A model based library for making API development faster and easier. +* [django-requestlogs] - Providing middleware and other helpers for audit logging for REST framework. [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework @@ -237,3 +238,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [graphwrap]: https://github.com/PaulGilmartin/graph_wrap [rest-framework-actions]: https://github.com/AlexisMunera98/rest-framework-actions [fast-drf]: https://github.com/iashraful/fast-drf +[django-requestlogs]: https://github.com/Raekkeri/django-requestlogs From 292ead1fe0f779254590a47a352d545d57132266 Mon Sep 17 00:00:00 2001 From: ghazi-git Date: Mon, 6 Jun 2022 12:46:11 +0100 Subject: [PATCH 570/907] add drf-standardized-errors to third party packages (#8487) Co-authored-by: Tom Christie --- docs/api-guide/exceptions.md | 9 +++++++++ docs/community/third-party-packages.md | 2 ++ 2 files changed, 11 insertions(+) diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index e62a7e4f9d..347541d56c 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -260,6 +260,15 @@ Set as `handler400`: handler400 = 'rest_framework.exceptions.bad_request' +# Third party packages + +The following third-party packages are also available. + +## DRF Standardized Errors + +The [drf-standardized-errors][drf-standardized-errors] package provides an exception handler that generates the same format for all 4xx and 5xx responses. It is a drop-in replacement for the default exception handler and allows customizing the error response format without rewriting the whole exception handler. The standardized error response format is easier to document and easier to handle by API consumers. + [cite]: https://doughellmann.com/blog/2009/06/19/python-exception-handling-techniques/ [authentication]: authentication.md [django-custom-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views +[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index d4e590952d..9513b13d1a 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -149,6 +149,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [django-api-client][django-api-client] - DRF client that groups the Endpoint response, for use in CBVs and FBV as if you were working with Django's Native Models.. * [fast-drf] - A model based library for making API development faster and easier. * [django-requestlogs] - Providing middleware and other helpers for audit logging for REST framework. +* [drf-standardized-errors][drf-standardized-errors] - DRF exception handler to standardize error responses for all API endpoints. [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework @@ -239,3 +240,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [rest-framework-actions]: https://github.com/AlexisMunera98/rest-framework-actions [fast-drf]: https://github.com/iashraful/fast-drf [django-requestlogs]: https://github.com/Raekkeri/django-requestlogs +[drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors From 281fc074ba255ed9c5724cc971fa86c78d4dca38 Mon Sep 17 00:00:00 2001 From: Krukov D Date: Mon, 6 Jun 2022 14:54:57 +0300 Subject: [PATCH 571/907] improve performance for noncallble attributes (#8502) Co-authored-by: Dima Kryukov --- rest_framework/fields.py | 3 +++ tests/test_fields.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 8d02b3206e..31e5b994cc 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -62,6 +62,9 @@ def is_simple_callable(obj): """ True if the object is a callable that takes no arguments. """ + if not callable(obj): + return False + # Bail early since we cannot inspect built-in function signatures. if inspect.isbuiltin(obj): raise BuiltinSignatureError( diff --git a/tests/test_fields.py b/tests/test_fields.py index ec121c822c..19f7345138 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -75,6 +75,10 @@ def invalid(param, param2='value'): assert is_simple_callable(valid_vargs_kwargs) assert not is_simple_callable(invalid) + @pytest.mark.parametrize('obj', (True, None, "str", b'bytes', 123, 1.23)) + def test_not_callable(self, obj): + assert not is_simple_callable(obj) + def test_4602_regression(self): from django.db import models From bb7dcef19bceae1dd43c19d5a9d9dddc75868c83 Mon Sep 17 00:00:00 2001 From: Daniel Gilge <33256939+dgilge@users.noreply.github.com> Date: Mon, 6 Jun 2022 14:31:00 +0200 Subject: [PATCH 572/907] Update get_schema in docs (#7402) --- docs/api-guide/schemas.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index b9de6745fe..5bdf5ee542 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -165,7 +165,7 @@ In order to customize the top-level schema, subclass as an argument to the `generateschema` command or `get_schema_view()` helper function. -### get_schema(self, request) +### get_schema(self, request=None, public=False) Returns a dictionary that represents the OpenAPI schema: From 563a20a04098937db1e2c353b2ee6a3059e92ebf Mon Sep 17 00:00:00 2001 From: Alan Crosswell Date: Mon, 6 Jun 2022 08:44:02 -0400 Subject: [PATCH 573/907] make get_reference public (#7515) --- docs/api-guide/schemas.md | 5 +++++ rest_framework/schemas/openapi.py | 14 +++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 5bdf5ee542..004e2d3ce9 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -313,6 +313,11 @@ Computes the component's name from the serializer. You may see warnings if your API has duplicate component names. If so you can override `get_component_name()` or pass the `component_name` `__init__()` kwarg (see below) to provide different names. +#### `get_reference()` + +Returns a reference to the serializer component. This may be useful if you override `get_schema()`. + + #### `map_serializer()` Maps serializers to their OpenAPI representations. diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 5e9d59f8bf..122846376f 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -636,7 +636,7 @@ def get_response_serializer(self, path, method): """ return self.get_serializer(path, method) - def _get_reference(self, serializer): + def get_reference(self, serializer): return {'$ref': '#/components/schemas/{}'.format(self.get_component_name(serializer))} def get_request_body(self, path, method): @@ -650,7 +650,7 @@ def get_request_body(self, path, method): if not isinstance(serializer, serializers.Serializer): item_schema = {} else: - item_schema = self._get_reference(serializer) + item_schema = self.get_reference(serializer) return { 'content': { @@ -674,7 +674,7 @@ def get_responses(self, path, method): if not isinstance(serializer, serializers.Serializer): item_schema = {} else: - item_schema = self._get_reference(serializer) + item_schema = self.get_reference(serializer) if is_list_view(path, method, self.view): response_schema = { @@ -808,3 +808,11 @@ def _allows_filters(self, path, method): RemovedInDRF314Warning, stacklevel=2 ) return self.allows_filters(path, method) + + def _get_reference(self, serializer): + warnings.warn( + "Method `_get_reference()` has been renamed to `get_reference()`. " + "The old name will be removed in DRF v3.14.", + RemovedInDRF314Warning, stacklevel=2 + ) + return self.get_reference(serializer) From 1396f6886a39acb7fe52729c7b99fe2d7d245dac Mon Sep 17 00:00:00 2001 From: Grigory Date: Mon, 6 Jun 2022 15:53:42 +0300 Subject: [PATCH 574/907] Respect model error_messages for relation (#7599) --- rest_framework/utils/field_mapping.py | 22 ++++++++++++++-------- tests/test_validators.py | 13 +++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index 4f8a4f1926..673821b5e1 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -217,15 +217,9 @@ def get_field_kwargs(field_name, model_field): ] if getattr(model_field, 'unique', False): - unique_error_message = model_field.error_messages.get('unique', None) - if unique_error_message: - unique_error_message = unique_error_message % { - 'model_name': model_field.model._meta.verbose_name, - 'field_label': model_field.verbose_name - } validator = UniqueValidator( queryset=model_field.model._default_manager, - message=unique_error_message) + message=get_unique_error_message(model_field)) validator_kwarg.append(validator) if validator_kwarg: @@ -281,7 +275,9 @@ def get_relation_kwargs(field_name, relation_info): if model_field.validators: kwargs['validators'] = model_field.validators if getattr(model_field, 'unique', False): - validator = UniqueValidator(queryset=model_field.model._default_manager) + validator = UniqueValidator( + queryset=model_field.model._default_manager, + message=get_unique_error_message(model_field)) kwargs['validators'] = kwargs.get('validators', []) + [validator] if to_many and not model_field.blank: kwargs['allow_empty'] = False @@ -300,3 +296,13 @@ def get_url_kwargs(model_field): return { 'view_name': get_detail_view_name(model_field) } + + +def get_unique_error_message(model_field): + unique_error_message = model_field.error_messages.get('unique', None) + if unique_error_message: + unique_error_message = unique_error_message % { + 'model_name': model_field.model._meta.verbose_name, + 'field_label': model_field.verbose_name + } + return unique_error_message diff --git a/tests/test_validators.py b/tests/test_validators.py index bccbe1514b..39490ac863 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -42,6 +42,12 @@ class Meta: fields = ('username', 'email') +class RelatedModelUserSerializer(serializers.ModelSerializer): + class Meta: + model = RelatedModel + fields = ('user',) + + class AnotherUniquenessModel(models.Model): code = models.IntegerField(unique=True) @@ -83,6 +89,13 @@ def test_is_not_unique(self): assert not serializer.is_valid() assert serializer.errors == {'username': ['uniqueness model with this username already exists.']} + def test_relation_is_not_unique(self): + RelatedModel.objects.create(user=self.instance) + data = {'user': self.instance.pk} + serializer = RelatedModelUserSerializer(data=data) + assert not serializer.is_valid() + assert serializer.errors == {'user': ['related model with this user already exists.']} + def test_is_unique(self): data = {'username': 'other'} serializer = UniquenessSerializer(data=data) From f8a03b096be27b4caac25dc119ccda81ad678d4f Mon Sep 17 00:00:00 2001 From: Patrick Daley Date: Tue, 7 Jun 2022 23:35:01 +1200 Subject: [PATCH 575/907] Remove pluralisation from basenames (#8517) HyperlinkIdentityFields in serializers reference 'snippet-highlight' and 'snippet-detail', router basenames updated to match. --- docs/tutorial/6-viewsets-and-routers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index 18066f0563..e12becbd06 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -112,8 +112,8 @@ Here's our re-wired `snippets/urls.py` file. # Create a router and register our viewsets with it. router = DefaultRouter() - router.register(r'snippets', views.SnippetViewSet,basename="snippets") - router.register(r'users', views.UserViewSet,basename="users") + router.register(r'snippets', views.SnippetViewSet,basename="snippet") + router.register(r'users', views.UserViewSet,basename="user") # The API URLs are now determined automatically by the router. urlpatterns = [ From e7af8d662bf837e4fee844b28606cda63c0d30a9 Mon Sep 17 00:00:00 2001 From: itsdkey Date: Wed, 8 Jun 2022 14:41:26 +0200 Subject: [PATCH 576/907] tests for #5127 (#7715) * tests for #5127 * Resolves #5127 --- tests/browsable_api/no_auth_urls.py | 3 ++- tests/browsable_api/serializers.py | 8 +++++++ tests/browsable_api/test_browsable_api.py | 27 +++++++++++++++++++++++ tests/browsable_api/views.py | 22 ++++++++++++++++++ tests/models.py | 5 +++++ 5 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/browsable_api/serializers.py diff --git a/tests/browsable_api/no_auth_urls.py b/tests/browsable_api/no_auth_urls.py index 65701c0653..33491ad926 100644 --- a/tests/browsable_api/no_auth_urls.py +++ b/tests/browsable_api/no_auth_urls.py @@ -1,7 +1,8 @@ from django.urls import path -from .views import MockView +from .views import BasicModelWithUsersViewSet, MockView urlpatterns = [ path('', MockView.as_view()), + path('basicviewset', BasicModelWithUsersViewSet.as_view({'get': 'list'})), ] diff --git a/tests/browsable_api/serializers.py b/tests/browsable_api/serializers.py new file mode 100644 index 0000000000..e8a1cdef85 --- /dev/null +++ b/tests/browsable_api/serializers.py @@ -0,0 +1,8 @@ +from rest_framework.serializers import ModelSerializer +from tests.models import BasicModelWithUsers + + +class BasicSerializer(ModelSerializer): + class Meta: + model = BasicModelWithUsers + fields = '__all__' diff --git a/tests/browsable_api/test_browsable_api.py b/tests/browsable_api/test_browsable_api.py index 17644c2ac7..a76d11fe35 100644 --- a/tests/browsable_api/test_browsable_api.py +++ b/tests/browsable_api/test_browsable_api.py @@ -1,8 +1,35 @@ from django.contrib.auth.models import User from django.test import TestCase, override_settings +from rest_framework.permissions import IsAuthenticated from rest_framework.test import APIClient +from .views import BasicModelWithUsersViewSet, OrganizationPermissions + + +@override_settings(ROOT_URLCONF='tests.browsable_api.no_auth_urls') +class AnonymousUserTests(TestCase): + """Tests correct handling of anonymous user request on endpoints with IsAuthenticated permission class.""" + + def setUp(self): + self.client = APIClient(enforce_csrf_checks=True) + + def tearDown(self): + self.client.logout() + + def test_get_raises_typeerror_when_anonymous_user_in_queryset_filter(self): + with self.assertRaises(TypeError): + self.client.get('/basicviewset') + + def test_get_returns_http_forbidden_when_anonymous_user(self): + old_permissions = BasicModelWithUsersViewSet.permission_classes + BasicModelWithUsersViewSet.permission_classes = [IsAuthenticated, OrganizationPermissions] + + response = self.client.get('/basicviewset') + + BasicModelWithUsersViewSet.permission_classes = old_permissions + self.assertEqual(response.status_code, 403) + @override_settings(ROOT_URLCONF='tests.browsable_api.auth_urls') class DropdownWithAuthTests(TestCase): diff --git a/tests/browsable_api/views.py b/tests/browsable_api/views.py index e1cf13a1ec..e73967bf8c 100644 --- a/tests/browsable_api/views.py +++ b/tests/browsable_api/views.py @@ -1,6 +1,16 @@ from rest_framework import authentication, renderers +from rest_framework.permissions import BasePermission from rest_framework.response import Response from rest_framework.views import APIView +from rest_framework.viewsets import ModelViewSet + +from ..models import BasicModelWithUsers +from .serializers import BasicSerializer + + +class OrganizationPermissions(BasePermission): + def has_object_permission(self, request, view, obj): + return request.user.is_staff or (request.user == obj.owner.organization_user.user) class MockView(APIView): @@ -9,3 +19,15 @@ class MockView(APIView): def get(self, request): return Response({'a': 1, 'b': 2, 'c': 3}) + + +class BasicModelWithUsersViewSet(ModelViewSet): + queryset = BasicModelWithUsers.objects.all() + serializer_class = BasicSerializer + permission_classes = [OrganizationPermissions] + # permission_classes = [IsAuthenticated, OrganizationPermissions] + renderer_classes = (renderers.BrowsableAPIRenderer, renderers.JSONRenderer) + + def get_queryset(self): + qs = super().get_queryset().filter(users=self.request.user) + return qs diff --git a/tests/models.py b/tests/models.py index afe649760a..666e9f0031 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,5 +1,6 @@ import uuid +from django.contrib.auth.models import User from django.db import models from django.utils.translation import gettext_lazy as _ @@ -33,6 +34,10 @@ class ManyToManySource(RESTFrameworkModel): targets = models.ManyToManyField(ManyToManyTarget, related_name='sources') +class BasicModelWithUsers(RESTFrameworkModel): + users = models.ManyToManyField(User) + + # ForeignKey class ForeignKeyTarget(RESTFrameworkModel): name = models.CharField(max_length=100) From 26830c3d2d45a60385d1166c37a031e2e75cf858 Mon Sep 17 00:00:00 2001 From: Marti Raudsepp Date: Wed, 8 Jun 2022 16:37:46 +0300 Subject: [PATCH 577/907] Fix QueryDict type error in test (#8475) QueryDict takes a `str` argument. Discovered while working on djangorestframework-stubs. --- tests/test_fields.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_fields.py b/tests/test_fields.py index 19f7345138..cbec79119a 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1865,9 +1865,9 @@ class TestMultipleChoiceField(FieldValues): def test_against_partial_and_full_updates(self): field = serializers.MultipleChoiceField(choices=(('a', 'a'), ('b', 'b'))) field.partial = False - assert field.get_value(QueryDict({})) == [] + assert field.get_value(QueryDict('')) == [] field.partial = True - assert field.get_value(QueryDict({})) == rest_framework.fields.empty + assert field.get_value(QueryDict('')) == rest_framework.fields.empty class TestEmptyMultipleChoiceField(FieldValues): From 5185cc934862a5ab13316c85402c12a6d744f94c Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Wed, 8 Jun 2022 15:46:19 +0200 Subject: [PATCH 578/907] Handle unset fields with 'many=True' (#7574) * Handle unset fields with 'many=True' The docs note: When serializing fields with dotted notation, it may be necessary to provide a `default` value if any object is not present or is empty during attribute traversal. However, this doesn't work for fields with 'many=True'. When using these, the default is simply ignored. The solution is simple: do in 'ManyRelatedField' what we were already doing for 'Field', namely, catch possible 'AttributeError' and 'KeyError' exceptions and return the default if there is one set. Signed-off-by: Stephen Finucane Closes: #7550 * Add test cases for #7550 Signed-off-by: Stephen Finucane --- rest_framework/relations.py | 27 +++++++++++++- tests/test_model_serializer.py | 67 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index c987007842..bdedd43b86 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -10,7 +10,7 @@ from django.utils.translation import gettext_lazy as _ from rest_framework.fields import ( - Field, empty, get_attribute, is_simple_callable, iter_options + Field, SkipField, empty, get_attribute, is_simple_callable, iter_options ) from rest_framework.reverse import reverse from rest_framework.settings import api_settings @@ -535,7 +535,30 @@ def get_attribute(self, instance): if hasattr(instance, 'pk') and instance.pk is None: return [] - relationship = get_attribute(instance, self.source_attrs) + try: + relationship = get_attribute(instance, self.source_attrs) + except (KeyError, AttributeError) as exc: + if self.default is not empty: + return self.get_default() + if self.allow_null: + return None + if not self.required: + raise SkipField() + msg = ( + 'Got {exc_type} when attempting to get a value for field ' + '`{field}` on serializer `{serializer}`.\nThe serializer ' + 'field might be named incorrectly and not match ' + 'any attribute or key on the `{instance}` instance.\n' + 'Original exception text was: {exc}.'.format( + exc_type=type(exc).__name__, + field=self.field_name, + serializer=self.parent.__class__.__name__, + instance=instance.__class__.__name__, + exc=exc + ) + ) + raise type(exc)(msg) + return relationship.all() if hasattr(relationship, 'all') else relationship def to_representation(self, iterable): diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index abb4830d14..419eae632b 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -1025,6 +1025,73 @@ class Meta: assert serializer.data == expected +class Issue7550FooModel(models.Model): + text = models.CharField(max_length=100) + bar = models.ForeignKey( + 'Issue7550BarModel', null=True, blank=True, on_delete=models.SET_NULL, + related_name='foos', related_query_name='foo') + + +class Issue7550BarModel(models.Model): + pass + + +class Issue7550TestCase(TestCase): + + def test_dotted_source(self): + + class _FooSerializer(serializers.ModelSerializer): + class Meta: + model = Issue7550FooModel + fields = ('id', 'text') + + class FooSerializer(serializers.ModelSerializer): + other_foos = _FooSerializer(source='bar.foos', many=True) + + class Meta: + model = Issue7550BarModel + fields = ('id', 'other_foos') + + bar = Issue7550BarModel.objects.create() + foo_a = Issue7550FooModel.objects.create(bar=bar, text='abc') + foo_b = Issue7550FooModel.objects.create(bar=bar, text='123') + + assert FooSerializer(foo_a).data == { + 'id': foo_a.id, + 'other_foos': [ + { + 'id': foo_a.id, + 'text': foo_a.text, + }, + { + 'id': foo_b.id, + 'text': foo_b.text, + }, + ], + } + + def test_dotted_source_with_default(self): + + class _FooSerializer(serializers.ModelSerializer): + class Meta: + model = Issue7550FooModel + fields = ('id', 'text') + + class FooSerializer(serializers.ModelSerializer): + other_foos = _FooSerializer(source='bar.foos', default=[], many=True) + + class Meta: + model = Issue7550FooModel + fields = ('id', 'other_foos') + + foo = Issue7550FooModel.objects.create(bar=None, text='abc') + + assert FooSerializer(foo).data == { + 'id': foo.id, + 'other_foos': [], + } + + class DecimalFieldModel(models.Model): decimal_field = models.DecimalField( max_digits=3, From 82475c232b531e075fe7666d808f9e68d052d35b Mon Sep 17 00:00:00 2001 From: Alessandro <9991341+alessandrosp@users.noreply.github.com> Date: Wed, 8 Jun 2022 23:03:00 +0900 Subject: [PATCH 579/907] Made relative URLs clickable as well. (#8464) --- rest_framework/templatetags/rest_framework.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index db0e9c95c3..ccd9430b4e 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -218,7 +218,7 @@ def format_value(value): return template.render(context) elif isinstance(value, str): if ( - (value.startswith('http:') or value.startswith('https:')) and not + (value.startswith('http:') or value.startswith('https:') or value.startswith('/')) and not re.search(r'\s', value) ): return mark_safe('{value}'.format(value=escape(value))) From 2506d0b4f2ac8bdbf35d33b3dd8a56f3e8d0da75 Mon Sep 17 00:00:00 2001 From: Burak Kadir Er Date: Thu, 9 Jun 2022 17:30:47 +0300 Subject: [PATCH 580/907] Update include and namespace URLs (#8520) --- docs/api-guide/routers.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/routers.md b/docs/api-guide/routers.md index 8d8594eeea..70c05fdde7 100644 --- a/docs/api-guide/routers.md +++ b/docs/api-guide/routers.md @@ -338,5 +338,5 @@ The [`DRF-extensions` package][drf-extensions] provides [routers][drf-extensions [drf-extensions-nested-viewsets]: https://chibisov.github.io/drf-extensions/docs/#nested-routes [drf-extensions-collection-level-controllers]: https://chibisov.github.io/drf-extensions/docs/#collection-level-controllers [drf-extensions-customizable-endpoint-names]: https://chibisov.github.io/drf-extensions/docs/#controller-endpoint-name -[url-namespace-docs]: https://docs.djangoproject.com/en/1.11/topics/http/urls/#url-namespaces -[include-api-reference]: https://docs.djangoproject.com/en/2.0/ref/urls/#include +[url-namespace-docs]: https://docs.djangoproject.com/en/4.0/topics/http/urls/#url-namespaces +[include-api-reference]: https://docs.djangoproject.com/en/4.0/ref/urls/#include From fa9d516ee24b2447a351af92e139d386b4f0d2b4 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 20 Jun 2022 10:44:27 +0100 Subject: [PATCH 581/907] Update docstring test for more recent pygments version (#8530) * Update docstring test for more recent pygments version * Drop unused import --- requirements/requirements-optionals.txt | 5 ++-- tests/test_description.py | 31 ++++--------------------- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index 75b9ab4d60..f3bb9b709d 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -3,8 +3,7 @@ coreapi==2.3.1 coreschema==0.0.4 django-filter>=2.4.0,<3.0 django-guardian>=2.4.0,<2.5 -markdown==3.3;python_version>="3.6" -markdown==3.2.2;python_version=="3.5" +markdown==3.3 psycopg2-binary>=2.8.5,<2.9 -pygments>=2.7.1,<2.8 +pygments==2.12 pyyaml>=5.3.1,<5.4 diff --git a/tests/test_description.py b/tests/test_description.py index 363ad6513e..ecc6b9776d 100644 --- a/tests/test_description.py +++ b/tests/test_description.py @@ -1,5 +1,3 @@ -import sys - import pytest from django.test import TestCase @@ -33,7 +31,7 @@ # If markdown is installed we also test it's working # (and that our wrapped forces '=' to h2 and '-' to h3) -MARKDOWN_BASE = """

    an example docstring

    +MARKDOWN_DOCSTRING = """

    an example docstring

    "csrfToken": "{{ csrf_token }}" } - + diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 533db13780..53c964f231 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -293,7 +293,7 @@

    {{ name }}

    "csrfToken": "{% if request %}{{ csrf_token }}{% endif %}" } - + diff --git a/rest_framework/templates/rest_framework/docs/error.html b/rest_framework/templates/rest_framework/docs/error.html index 694f88a150..8e67238b9e 100644 --- a/rest_framework/templates/rest_framework/docs/error.html +++ b/rest_framework/templates/rest_framework/docs/error.html @@ -66,6 +66,6 @@

    Overriding this template

    - + diff --git a/rest_framework/templates/rest_framework/docs/index.html b/rest_framework/templates/rest_framework/docs/index.html index dfd363772a..8f8536fbeb 100644 --- a/rest_framework/templates/rest_framework/docs/index.html +++ b/rest_framework/templates/rest_framework/docs/index.html @@ -38,7 +38,7 @@ {% include "rest_framework/docs/auth/basic.html" %} {% include "rest_framework/docs/auth/session.html" %} - + From 59430111bdecf9e2c7488d599c18a91d5ac89240 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Tue, 28 Mar 2023 16:08:27 +0600 Subject: [PATCH 699/907] Update tox with django 4.2rc1 (#8920) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 7027612e04..10b52063ed 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,7 @@ deps = django32: Django>=3.2,<4.0 django40: Django>=4.0,<4.1 django41: Django>=4.1,<4.2 - django42: Django>=4.2b1,<5.0 + django42: Django>=4.2rc1,<5.0 djangomain: https://github.com/django/django/archive/main.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 6b73acc1735631df1e666cc87eee0d14de6ae018 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Tue, 28 Mar 2023 16:35:44 +0600 Subject: [PATCH 700/907] Update requirements-packaging.txt (#8921) --- requirements/requirements-packaging.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements/requirements-packaging.txt b/requirements/requirements-packaging.txt index a9733185bc..81f22a35a1 100644 --- a/requirements/requirements-packaging.txt +++ b/requirements/requirements-packaging.txt @@ -1,8 +1,8 @@ # Wheel for PyPI installs. -wheel>=0.35.1,<0.36 +wheel>=0.36.2,<0.40.0 # Twine for secured PyPI uploads. -twine>=3.2.0,<3.3 +twine>=3.4.2,<4.0.2 # Transifex client for managing translation resources. transifex-client From b60fbf3374efcf0abfccb10597736329f891d4fc Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Mon, 3 Apr 2023 22:35:11 +0600 Subject: [PATCH 701/907] test django 4.2 stable release (#8932) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 10b52063ed..2b8733d7df 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,7 @@ deps = django32: Django>=3.2,<4.0 django40: Django>=4.0,<4.1 django41: Django>=4.1,<4.2 - django42: Django>=4.2rc1,<5.0 + django42: Django>=4.2,<5.0 djangomain: https://github.com/django/django/archive/main.tar.gz -rrequirements/requirements-testing.txt -rrequirements/requirements-optionals.txt From 3428cec194060f9896be90f00f2d316557acd00f Mon Sep 17 00:00:00 2001 From: Christian Franke Date: Tue, 4 Apr 2023 09:38:23 +0200 Subject: [PATCH 702/907] Use consistent spelling for "authorization" (#8929) Apart from a few exceptions, django-rest-framework uses the American English spelling "authorization"/"authorized". $ git grep -oi authorised | wc -l 2 $ git grep -oi authorized | wc -l 30 Replace the few occurences of the British English spelling with the American English one. --- docs/api-guide/permissions.md | 2 +- rest_framework/templates/rest_framework/docs/error.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index 27f7c5adb4..e70cc63be5 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -165,7 +165,7 @@ This permission is suitable if you want your API to only be accessible to a subs ## IsAuthenticatedOrReadOnly -The `IsAuthenticatedOrReadOnly` will allow authenticated users to perform any request. Requests for unauthorised users will only be permitted if the request method is one of the "safe" methods; `GET`, `HEAD` or `OPTIONS`. +The `IsAuthenticatedOrReadOnly` will allow authenticated users to perform any request. Requests for unauthorized users will only be permitted if the request method is one of the "safe" methods; `GET`, `HEAD` or `OPTIONS`. This permission is suitable if you want to your API to allow read permissions to anonymous users, and only allow write permissions to authenticated users. diff --git a/rest_framework/templates/rest_framework/docs/error.html b/rest_framework/templates/rest_framework/docs/error.html index 8e67238b9e..0c369e9e8e 100644 --- a/rest_framework/templates/rest_framework/docs/error.html +++ b/rest_framework/templates/rest_framework/docs/error.html @@ -30,7 +30,7 @@

    Additional Information

    Your response status code is: {{ response.status_code }}

    -

    401 Unauthorised.

    +

    401 Unauthorized.

    • Do you have SessionAuthentication enabled?
    • Are you logged in?
    • From ea03e95174f46003e7e917b623c5316247b8b316 Mon Sep 17 00:00:00 2001 From: Christian Franke Date: Tue, 4 Apr 2023 10:44:59 +0200 Subject: [PATCH 703/907] docs: Fix authentication / authorization mixup (#8930) `IsAuthenticatedOrReadOnly` authorizes users that are not authenticated for read-only access to resources. Therefore, they are unauthenticated users, not unauthorized users. --- docs/api-guide/permissions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/permissions.md b/docs/api-guide/permissions.md index e70cc63be5..5e0b6a153d 100644 --- a/docs/api-guide/permissions.md +++ b/docs/api-guide/permissions.md @@ -165,7 +165,7 @@ This permission is suitable if you want your API to only be accessible to a subs ## IsAuthenticatedOrReadOnly -The `IsAuthenticatedOrReadOnly` will allow authenticated users to perform any request. Requests for unauthorized users will only be permitted if the request method is one of the "safe" methods; `GET`, `HEAD` or `OPTIONS`. +The `IsAuthenticatedOrReadOnly` will allow authenticated users to perform any request. Requests for unauthenticated users will only be permitted if the request method is one of the "safe" methods; `GET`, `HEAD` or `OPTIONS`. This permission is suitable if you want to your API to allow read permissions to anonymous users, and only allow write permissions to authenticated users. From 959085c1455a7075a8c237a0283c2a6e35dfcd76 Mon Sep 17 00:00:00 2001 From: Arnab Kumar Shil Date: Sat, 8 Apr 2023 08:27:14 +0200 Subject: [PATCH 704/907] Handle Nested Relation in SlugRelatedField when many=False (#8922) * Update relations.py Currently if you define the slug field as a nested relationship in a `SlugRelatedField` while many=False, it will cause an attribute error. For example: For this code: ``` class SomeSerializer(serializers.ModelSerializer): some_field= serializers.SlugRelatedField(queryset=SomeClass.objects.all(), slug_field="foo__bar") ``` The POST request (or save operation) should work just fine, but if you use GET, then it will fail with Attribute error: > AttributeError: 'SomeClass' object has no attribute 'foo__bar' Thus I am handling nested relation here. Reference: https://stackoverflow.com/questions/75878103/drf-attributeerror-when-trying-to-creating-a-instance-with-slugrelatedfield-and/75882424#75882424 * Fixed test cases * code comment changes related to slugrelatedfield * changes based on pre-commit and removed comma which was added accidentally * fixed primary keys of the mock object * added more test cases based on review --------- Co-authored-by: Arnab Shil --- rest_framework/relations.py | 8 ++- tests/test_relations.py | 136 ++++++++++++++++++++++++++++++++++++ tests/utils.py | 5 +- 3 files changed, 147 insertions(+), 2 deletions(-) diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 62da685fb6..53ea2113b4 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -1,6 +1,7 @@ import contextlib import sys from collections import OrderedDict +from operator import attrgetter from urllib import parse from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist @@ -71,6 +72,7 @@ class PKOnlyObject: instance, but still want to return an object with a .pk attribute, in order to keep the same interface as a regular model instance. """ + def __init__(self, pk): self.pk = pk @@ -464,7 +466,11 @@ def to_internal_value(self, data): self.fail('invalid') def to_representation(self, obj): - return getattr(obj, self.slug_field) + slug = self.slug_field + if "__" in slug: + # handling nested relationship if defined + slug = slug.replace('__', '.') + return attrgetter(slug)(obj) class ManyRelatedField(Field): diff --git a/tests/test_relations.py b/tests/test_relations.py index 7a4db1c487..b9ab157896 100644 --- a/tests/test_relations.py +++ b/tests/test_relations.py @@ -342,6 +342,142 @@ def get_queryset(self): field.to_internal_value(self.instance.name) +class TestNestedSlugRelatedField(APISimpleTestCase): + def setUp(self): + self.queryset = MockQueryset([ + MockObject( + pk=1, name='foo', nested=MockObject( + pk=2, name='bar', nested=MockObject( + pk=7, name="foobar" + ) + ) + ), + MockObject( + pk=3, name='hello', nested=MockObject( + pk=4, name='world', nested=MockObject( + pk=8, name="helloworld" + ) + ) + ), + MockObject( + pk=5, name='harry', nested=MockObject( + pk=6, name='potter', nested=MockObject( + pk=9, name="harrypotter" + ) + ) + ) + ]) + self.instance = self.queryset.items[2] + self.field = serializers.SlugRelatedField( + slug_field='name', queryset=self.queryset + ) + self.nested_field = serializers.SlugRelatedField( + slug_field='nested__name', queryset=self.queryset + ) + + self.nested_nested_field = serializers.SlugRelatedField( + slug_field='nested__nested__name', queryset=self.queryset + ) + + # testing nested inside nested relations + def test_slug_related_nested_nested_lookup_exists(self): + instance = self.nested_nested_field.to_internal_value( + self.instance.nested.nested.name + ) + assert instance is self.instance + + def test_slug_related_nested_nested_lookup_does_not_exist(self): + with pytest.raises(serializers.ValidationError) as excinfo: + self.nested_nested_field.to_internal_value('doesnotexist') + msg = excinfo.value.detail[0] + assert msg == \ + 'Object with nested__nested__name=doesnotexist does not exist.' + + def test_slug_related_nested_nested_lookup_invalid_type(self): + with pytest.raises(serializers.ValidationError) as excinfo: + self.nested_nested_field.to_internal_value(BadType()) + msg = excinfo.value.detail[0] + assert msg == 'Invalid value.' + + def test_nested_nested_representation(self): + representation =\ + self.nested_nested_field.to_representation(self.instance) + assert representation == self.instance.nested.nested.name + + def test_nested_nested_overriding_get_queryset(self): + qs = self.queryset + + class NoQuerySetSlugRelatedField(serializers.SlugRelatedField): + def get_queryset(self): + return qs + + field = NoQuerySetSlugRelatedField(slug_field='nested__nested__name') + field.to_internal_value(self.instance.nested.nested.name) + + # testing nested relations + def test_slug_related_nested_lookup_exists(self): + instance = \ + self.nested_field.to_internal_value(self.instance.nested.name) + assert instance is self.instance + + def test_slug_related_nested_lookup_does_not_exist(self): + with pytest.raises(serializers.ValidationError) as excinfo: + self.nested_field.to_internal_value('doesnotexist') + msg = excinfo.value.detail[0] + assert msg == 'Object with nested__name=doesnotexist does not exist.' + + def test_slug_related_nested_lookup_invalid_type(self): + with pytest.raises(serializers.ValidationError) as excinfo: + self.nested_field.to_internal_value(BadType()) + msg = excinfo.value.detail[0] + assert msg == 'Invalid value.' + + def test_nested_representation(self): + representation = self.nested_field.to_representation(self.instance) + assert representation == self.instance.nested.name + + def test_nested_overriding_get_queryset(self): + qs = self.queryset + + class NoQuerySetSlugRelatedField(serializers.SlugRelatedField): + def get_queryset(self): + return qs + + field = NoQuerySetSlugRelatedField(slug_field='nested__name') + field.to_internal_value(self.instance.nested.name) + + # testing non-nested relations + def test_slug_related_lookup_exists(self): + instance = self.field.to_internal_value(self.instance.name) + assert instance is self.instance + + def test_slug_related_lookup_does_not_exist(self): + with pytest.raises(serializers.ValidationError) as excinfo: + self.field.to_internal_value('doesnotexist') + msg = excinfo.value.detail[0] + assert msg == 'Object with name=doesnotexist does not exist.' + + def test_slug_related_lookup_invalid_type(self): + with pytest.raises(serializers.ValidationError) as excinfo: + self.field.to_internal_value(BadType()) + msg = excinfo.value.detail[0] + assert msg == 'Invalid value.' + + def test_representation(self): + representation = self.field.to_representation(self.instance) + assert representation == self.instance.name + + def test_overriding_get_queryset(self): + qs = self.queryset + + class NoQuerySetSlugRelatedField(serializers.SlugRelatedField): + def get_queryset(self): + return qs + + field = NoQuerySetSlugRelatedField(slug_field='name') + field.to_internal_value(self.instance.name) + + class TestManyRelatedField(APISimpleTestCase): def setUp(self): self.instance = MockObject(pk=1, name='foo') diff --git a/tests/utils.py b/tests/utils.py index 06e5b9abe6..4ceb353099 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,3 +1,5 @@ +from operator import attrgetter + from django.core.exceptions import ObjectDoesNotExist from django.urls import NoReverseMatch @@ -26,7 +28,7 @@ def __getitem__(self, val): def get(self, **lookup): for item in self.items: if all([ - getattr(item, key, None) == value + attrgetter(key.replace('__', '.'))(item) == value for key, value in lookup.items() ]): return item @@ -39,6 +41,7 @@ class BadType: will raise a `TypeError`, as occurs in Django when making queryset lookups with an incorrect type for the lookup value. """ + def __eq__(self): raise TypeError() From 4842ad1b6ae9a2d08cb479c5d254aa3276ea2352 Mon Sep 17 00:00:00 2001 From: Nikita Reznikov <63803175+rnv812@users.noreply.github.com> Date: Sat, 8 Apr 2023 11:56:49 +0300 Subject: [PATCH 705/907] Add username search field for TokenAdmin (#8927) (#8934) * Add username search field for TokenAdmin (#8927) * Sort imports in a proper order (#8927) --- rest_framework/authtoken/admin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rest_framework/authtoken/admin.py b/rest_framework/authtoken/admin.py index e41eb00023..163328eb07 100644 --- a/rest_framework/authtoken/admin.py +++ b/rest_framework/authtoken/admin.py @@ -4,6 +4,7 @@ from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError from django.urls import reverse +from django.utils.translation import gettext_lazy as _ from rest_framework.authtoken.models import Token, TokenProxy @@ -23,6 +24,8 @@ def url_for_result(self, result): class TokenAdmin(admin.ModelAdmin): list_display = ('key', 'user', 'created') fields = ('user',) + search_fields = ('user__username',) + search_help_text = _('Username') ordering = ('-created',) actions = None # Actions not compatible with mapped IDs. autocomplete_fields = ("user",) From 62abf6ac1f20b48809c55f92d086d5f06d4c6c55 Mon Sep 17 00:00:00 2001 From: Maxwell Muoto Date: Sat, 8 Apr 2023 04:16:00 -0500 Subject: [PATCH 706/907] Use ZoneInfo as primary source of timezone data (#8924) * Use ZoneInfo as primary source of timezone data * Update tests/test_fields.py --------- Co-authored-by: Asif Saif Uddin --- rest_framework/fields.py | 8 ++++- rest_framework/utils/timezone.py | 25 ++++++++++++++++ setup.py | 2 +- tests/test_fields.py | 51 ++++++++++++++++++++++++-------- 4 files changed, 72 insertions(+), 14 deletions(-) create mode 100644 rest_framework/utils/timezone.py diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 613bd325a6..e41b56fb01 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -35,6 +35,7 @@ from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, json, representation from rest_framework.utils.formatting import lazy_format +from rest_framework.utils.timezone import valid_datetime from rest_framework.validators import ProhibitSurrogateCharactersValidator @@ -1154,7 +1155,12 @@ def enforce_timezone(self, value): except OverflowError: self.fail('overflow') try: - return timezone.make_aware(value, field_timezone) + dt = timezone.make_aware(value, field_timezone) + # When the resulting datetime is a ZoneInfo instance, it won't necessarily + # throw given an invalid datetime, so we need to specifically check. + if not valid_datetime(dt): + self.fail('make_aware', timezone=field_timezone) + return dt except InvalidTimeError: self.fail('make_aware', timezone=field_timezone) elif (field_timezone is None) and timezone.is_aware(value): diff --git a/rest_framework/utils/timezone.py b/rest_framework/utils/timezone.py new file mode 100644 index 0000000000..3257c8e274 --- /dev/null +++ b/rest_framework/utils/timezone.py @@ -0,0 +1,25 @@ +from datetime import datetime, timezone, tzinfo + + +def datetime_exists(dt): + """Check if a datetime exists. Taken from: https://pytz-deprecation-shim.readthedocs.io/en/latest/migration.html""" + # There are no non-existent times in UTC, and comparisons between + # aware time zones always compare absolute times; if a datetime is + # not equal to the same datetime represented in UTC, it is imaginary. + return dt.astimezone(timezone.utc) == dt + + +def datetime_ambiguous(dt: datetime): + """Check whether a datetime is ambiguous. Taken from: https://pytz-deprecation-shim.readthedocs.io/en/latest/migration.html""" + # If a datetime exists and its UTC offset changes in response to + # changing `fold`, it is ambiguous in the zone specified. + return datetime_exists(dt) and ( + dt.replace(fold=not dt.fold).utcoffset() != dt.utcoffset() + ) + + +def valid_datetime(dt): + """Returns True if the datetime is not ambiguous or imaginary, False otherwise.""" + if isinstance(dt.tzinfo, tzinfo) and not datetime_ambiguous(dt): + return True + return False diff --git a/setup.py b/setup.py index 9a5b272f3b..d9002fdb91 100755 --- a/setup.py +++ b/setup.py @@ -82,7 +82,7 @@ def get_version(package): author_email='tom@tomchristie.com', # SEE NOTE BELOW (*) packages=find_packages(exclude=['tests*']), include_package_data=True, - install_requires=["django>=3.0", "pytz"], + install_requires=["django>=3.0", "pytz", 'backports.zoneinfo;python_version<"3.9"'], python_requires=">=3.6", zip_safe=False, classifiers=[ diff --git a/tests/test_fields.py b/tests/test_fields.py index 56e2a45bad..5804d7b3b3 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -5,6 +5,7 @@ import sys import uuid from decimal import ROUND_DOWN, ROUND_UP, Decimal +from unittest.mock import patch import pytest import pytz @@ -21,6 +22,11 @@ ) from tests.models import UUIDForeignKeyTarget +if sys.version_info >= (3, 9): + from zoneinfo import ZoneInfo +else: + from backports.zoneinfo import ZoneInfo + utc = datetime.timezone.utc # Tests for helper functions. @@ -651,7 +657,7 @@ class FieldValues: """ Base class for testing valid and invalid input values. """ - def test_valid_inputs(self): + def test_valid_inputs(self, *args): """ Ensure that valid values return the expected validated data. """ @@ -659,7 +665,7 @@ def test_valid_inputs(self): assert self.field.run_validation(input_value) == expected_output, \ 'input value: {}'.format(repr(input_value)) - def test_invalid_inputs(self): + def test_invalid_inputs(self, *args): """ Ensure that invalid values raise the expected validation error. """ @@ -669,7 +675,7 @@ def test_invalid_inputs(self): assert exc_info.value.detail == expected_failure, \ 'input value: {}'.format(repr(input_value)) - def test_outputs(self): + def test_outputs(self, *args): for output_value, expected_output in get_items(self.outputs): assert self.field.to_representation(output_value) == expected_output, \ 'output value: {}'.format(repr(output_value)) @@ -1505,12 +1511,12 @@ class TestTZWithDateTimeField(FieldValues): @classmethod def setup_class(cls): # use class setup method, as class-level attribute will still be evaluated even if test is skipped - kolkata = pytz.timezone('Asia/Kolkata') + kolkata = ZoneInfo('Asia/Kolkata') cls.valid_inputs = { - '2016-12-19T10:00:00': kolkata.localize(datetime.datetime(2016, 12, 19, 10)), - '2016-12-19T10:00:00+05:30': kolkata.localize(datetime.datetime(2016, 12, 19, 10)), - datetime.datetime(2016, 12, 19, 10): kolkata.localize(datetime.datetime(2016, 12, 19, 10)), + '2016-12-19T10:00:00': datetime.datetime(2016, 12, 19, 10, tzinfo=kolkata), + '2016-12-19T10:00:00+05:30': datetime.datetime(2016, 12, 19, 10, tzinfo=kolkata), + datetime.datetime(2016, 12, 19, 10): datetime.datetime(2016, 12, 19, 10, tzinfo=kolkata), } cls.invalid_inputs = {} cls.outputs = { @@ -1529,7 +1535,7 @@ class TestDefaultTZDateTimeField(TestCase): @classmethod def setup_class(cls): cls.field = serializers.DateTimeField() - cls.kolkata = pytz.timezone('Asia/Kolkata') + cls.kolkata = ZoneInfo('Asia/Kolkata') def assertUTC(self, tzinfo): """ @@ -1551,18 +1557,17 @@ def test_current_timezone(self): self.assertUTC(self.field.default_timezone()) -@pytest.mark.skipif(pytz is None, reason='pytz not installed') @override_settings(TIME_ZONE='UTC', USE_TZ=True) class TestCustomTimezoneForDateTimeField(TestCase): @classmethod def setup_class(cls): - cls.kolkata = pytz.timezone('Asia/Kolkata') + cls.kolkata = ZoneInfo('Asia/Kolkata') cls.date_format = '%d/%m/%Y %H:%M' def test_should_render_date_time_in_default_timezone(self): field = serializers.DateTimeField(default_timezone=self.kolkata, format=self.date_format) - dt = datetime.datetime(2018, 2, 8, 14, 15, 16, tzinfo=pytz.utc) + dt = datetime.datetime(2018, 2, 8, 14, 15, 16, tzinfo=ZoneInfo("UTC")) with override(self.kolkata): rendered_date = field.to_representation(dt) @@ -1572,7 +1577,8 @@ def test_should_render_date_time_in_default_timezone(self): assert rendered_date == rendered_date_in_timezone -class TestNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues): +@pytest.mark.skipif(pytz is None, reason="As Django 4.0 has deprecated pytz, this test should eventually be able to get removed.") +class TestPytzNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues): """ Invalid values for `DateTimeField` with datetime in DST shift (non-existing or ambiguous) and timezone with DST. Timezone America/New_York has DST shift from 2017-03-12T02:00:00 to 2017-03-12T03:00:00 and @@ -1596,6 +1602,27 @@ def __str__(self): field = serializers.DateTimeField(default_timezone=MockTimezone()) +@patch('rest_framework.utils.timezone.datetime_ambiguous', return_value=True) +class TestNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues): + """ + Invalid values for `DateTimeField` with datetime in DST shift (non-existing or ambiguous) and timezone with DST. + Timezone America/New_York has DST shift from 2017-03-12T02:00:00 to 2017-03-12T03:00:00 and + from 2017-11-05T02:00:00 to 2017-11-05T01:00:00 in 2017. + """ + valid_inputs = {} + invalid_inputs = { + '2017-03-12T02:30:00': ['Invalid datetime for the timezone "America/New_York".'], + '2017-11-05T01:30:00': ['Invalid datetime for the timezone "America/New_York".'] + } + outputs = {} + + class MockZoneInfoTimezone(datetime.tzinfo): + def __str__(self): + return 'America/New_York' + + field = serializers.DateTimeField(default_timezone=MockZoneInfoTimezone()) + + class TestTimeField(FieldValues): """ Valid and invalid values for `TimeField`. From b1cec517ff33d633d3ebcf5794a5f0f0583fabe6 Mon Sep 17 00:00:00 2001 From: ddelange <14880945+ddelange@users.noreply.github.com> Date: Sat, 8 Apr 2023 12:42:28 +0200 Subject: [PATCH 707/907] Ensure CursorPagination respects nulls in the ordering field (#8912) * Ensure CursorPagination respects nulls in the ordering field * Lint * Fix pagination tests * Add test_ascending with nulls * Push tests for nulls * Test pass * Add comment * Fix test for django30 --- rest_framework/pagination.py | 16 +++-- tests/test_pagination.py | 134 ++++++++++++++++++++++++++++++++++- 2 files changed, 142 insertions(+), 8 deletions(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index f5c5b913b5..34d71f828c 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -10,6 +10,7 @@ from django.core.paginator import InvalidPage from django.core.paginator import Paginator as DjangoPaginator +from django.db.models import Q from django.template import loader from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ @@ -620,7 +621,7 @@ def paginate_queryset(self, queryset, request, view=None): queryset = queryset.order_by(*self.ordering) # If we have a cursor with a fixed position then filter by that. - if current_position is not None: + if str(current_position) != 'None': order = self.ordering[0] is_reversed = order.startswith('-') order_attr = order.lstrip('-') @@ -631,7 +632,12 @@ def paginate_queryset(self, queryset, request, view=None): else: kwargs = {order_attr + '__gt': current_position} - queryset = queryset.filter(**kwargs) + filter_query = Q(**kwargs) + # If some records contain a null for the ordering field, don't lose them. + # When reverse ordering, nulls will come last and need to be included. + if (reverse and not is_reversed) or is_reversed: + filter_query |= Q(**{order_attr + '__isnull': True}) + queryset = queryset.filter(filter_query) # If we have an offset cursor then offset the entire page by that amount. # We also always fetch an extra item in order to determine if there is a @@ -704,7 +710,7 @@ def get_next_link(self): # The item in this position and the item following it # have different positions. We can use this position as # our marker. - has_item_with_unique_position = True + has_item_with_unique_position = position is not None break # The item in this position has the same position as the item @@ -757,7 +763,7 @@ def get_previous_link(self): # The item in this position and the item following it # have different positions. We can use this position as # our marker. - has_item_with_unique_position = True + has_item_with_unique_position = position is not None break # The item in this position has the same position as the item @@ -883,7 +889,7 @@ def _get_position_from_instance(self, instance, ordering): attr = instance[field_name] else: attr = getattr(instance, field_name) - return str(attr) + return None if attr is None else str(attr) def get_paginated_response(self, data): return Response(OrderedDict([ diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 2812c4489e..8f9b20a0d4 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -951,17 +951,24 @@ class MockQuerySet: def __init__(self, items): self.items = items - def filter(self, created__gt=None, created__lt=None): + def filter(self, q): + q_args = dict(q.deconstruct()[1]) + if not q_args: + # django 3.0.x artifact + q_args = dict(q.deconstruct()[2]) + created__gt = q_args.get('created__gt') + created__lt = q_args.get('created__lt') + if created__gt is not None: return MockQuerySet([ item for item in self.items - if item.created > int(created__gt) + if item.created is None or item.created > int(created__gt) ]) assert created__lt is not None return MockQuerySet([ item for item in self.items - if item.created < int(created__lt) + if item.created is None or item.created < int(created__lt) ]) def order_by(self, *ordering): @@ -1080,6 +1087,127 @@ def get_pages(self, url): return (previous, current, next, previous_url, next_url) +class NullableCursorPaginationModel(models.Model): + created = models.IntegerField(null=True) + + +class TestCursorPaginationWithNulls(TestCase): + """ + Unit tests for `pagination.CursorPagination` with ordering on a nullable field. + """ + + def setUp(self): + class ExamplePagination(pagination.CursorPagination): + page_size = 1 + ordering = 'created' + + self.pagination = ExamplePagination() + data = [ + None, None, 3, 4 + ] + for idx in data: + NullableCursorPaginationModel.objects.create(created=idx) + + self.queryset = NullableCursorPaginationModel.objects.all() + + get_pages = TestCursorPagination.get_pages + + def test_ascending(self): + """Test paginating one row at a time, current should go 1, 2, 3, 4, 3, 2, 1.""" + (previous, current, next, previous_url, next_url) = self.get_pages('/') + + assert previous is None + assert current == [None] + assert next == [None] + + (previous, current, next, previous_url, next_url) = self.get_pages(next_url) + + assert previous == [None] + assert current == [None] + assert next == [3] + + (previous, current, next, previous_url, next_url) = self.get_pages(next_url) + + assert previous == [3] # [None] paging artifact documented at https://github.com/ddelange/django-rest-framework/blob/3.14.0/rest_framework/pagination.py#L789 + assert current == [3] + assert next == [4] + + (previous, current, next, previous_url, next_url) = self.get_pages(next_url) + + assert previous == [3] + assert current == [4] + assert next is None + assert next_url is None + + (previous, current, next, previous_url, next_url) = self.get_pages(previous_url) + + assert previous == [None] + assert current == [3] + assert next == [4] + + (previous, current, next, previous_url, next_url) = self.get_pages(previous_url) + + assert previous == [None] + assert current == [None] + assert next == [None] # [3] paging artifact documented at https://github.com/ddelange/django-rest-framework/blob/3.14.0/rest_framework/pagination.py#L731 + + (previous, current, next, previous_url, next_url) = self.get_pages(previous_url) + + assert previous is None + assert current == [None] + assert next == [None] + + def test_descending(self): + """Test paginating one row at a time, current should go 4, 3, 2, 1, 2, 3, 4.""" + self.pagination.ordering = ('-created',) + (previous, current, next, previous_url, next_url) = self.get_pages('/') + + assert previous is None + assert current == [4] + assert next == [3] + + (previous, current, next, previous_url, next_url) = self.get_pages(next_url) + + assert previous == [None] # [4] paging artifact + assert current == [3] + assert next == [None] + + (previous, current, next, previous_url, next_url) = self.get_pages(next_url) + + assert previous == [None] # [3] paging artifact + assert current == [None] + assert next == [None] + + (previous, current, next, previous_url, next_url) = self.get_pages(next_url) + + assert previous == [None] + assert current == [None] + assert next is None + assert next_url is None + + (previous, current, next, previous_url, next_url) = self.get_pages(previous_url) + + assert previous == [3] + assert current == [None] + assert next == [None] + + (previous, current, next, previous_url, next_url) = self.get_pages(previous_url) + + assert previous == [None] + assert current == [3] + assert next == [3] # [4] paging artifact documented at https://github.com/ddelange/django-rest-framework/blob/3.14.0/rest_framework/pagination.py#L731 + + # skip back artifact + (previous, current, next, previous_url, next_url) = self.get_pages(previous_url) + (previous, current, next, previous_url, next_url) = self.get_pages(previous_url) + + (previous, current, next, previous_url, next_url) = self.get_pages(previous_url) + + assert previous is None + assert current == [4] + assert next == [3] + + def test_get_displayed_page_numbers(): """ Test our contextual page display function. From 0d6ef034d2eed788f4fe6f9721148bf3874802ec Mon Sep 17 00:00:00 2001 From: Maxwell Muoto Date: Sun, 9 Apr 2023 03:53:47 -0500 Subject: [PATCH 708/907] Implement `__eq__` for validators (#8925) * Implement equality operator and add test coverage * Add documentation on implementation --- docs/api-guide/validators.md | 2 +- rest_framework/validators.py | 37 ++++++++++++++++++++++++++++++++++++ tests/test_validators.py | 11 +++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index bb8466a2c8..dac937d9bd 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -53,7 +53,7 @@ If we open up the Django shell using `manage.py shell` we can now The interesting bit here is the `reference` field. We can see that the uniqueness constraint is being explicitly enforced by a validator on the serializer field. -Because of this more explicit style REST framework includes a few validator classes that are not available in core Django. These classes are detailed below. +Because of this more explicit style REST framework includes a few validator classes that are not available in core Django. These classes are detailed below. REST framework validators, like their Django counterparts, implement the `__eq__` method, allowing you to compare instances for equality. --- diff --git a/rest_framework/validators.py b/rest_framework/validators.py index a5cb75a84a..07ad11b47b 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -79,6 +79,15 @@ def __repr__(self): smart_repr(self.queryset) ) + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return (self.message == other.message + and self.requires_context == other.requires_context + and self.queryset == other.queryset + and self.lookup == other.lookup + ) + class UniqueTogetherValidator: """ @@ -166,6 +175,16 @@ def __repr__(self): smart_repr(self.fields) ) + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return (self.message == other.message + and self.requires_context == other.requires_context + and self.missing_message == other.missing_message + and self.queryset == other.queryset + and self.fields == other.fields + ) + class ProhibitSurrogateCharactersValidator: message = _('Surrogate characters are not allowed: U+{code_point:X}.') @@ -177,6 +196,13 @@ def __call__(self, value): message = self.message.format(code_point=ord(surrogate_character)) raise ValidationError(message, code=self.code) + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return (self.message == other.message + and self.code == other.code + ) + class BaseUniqueForValidator: message = None @@ -230,6 +256,17 @@ def __call__(self, attrs, serializer): self.field: message }, code='unique') + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return (self.message == other.message + and self.missing_message == other.missing_message + and self.requires_context == other.requires_context + and self.queryset == other.queryset + and self.field == other.field + and self.date_field == other.date_field + ) + def __repr__(self): return '<%s(queryset=%s, field=%s, date_field=%s)>' % ( self.__class__.__name__, diff --git a/tests/test_validators.py b/tests/test_validators.py index 35fef6f26c..1cf42ed07e 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,4 +1,5 @@ import datetime +from unittest.mock import MagicMock import pytest from django.db import DataError, models @@ -787,3 +788,13 @@ def test_validator_raises_error_when_abstract_method_called(self): validator.filter_queryset( attrs=None, queryset=None, field_name='', date_field_name='' ) + + def test_equality_operator(self): + mock_queryset = MagicMock() + validator = BaseUniqueForValidator(queryset=mock_queryset, field='foo', + date_field='bar') + validator2 = BaseUniqueForValidator(queryset=mock_queryset, field='foo', + date_field='bar') + assert validator == validator2 + validator2.date_field = "bar2" + assert validator != validator2 From 684522807f370d6dca731d1d34bffeaa051fe505 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Thu, 13 Apr 2023 21:48:45 +0600 Subject: [PATCH 709/907] test codecov gha (#8946) --- .github/workflows/main.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f245f69644..e3435208b0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ on: jobs: tests: name: Python ${{ matrix.python-version }} - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: @@ -34,7 +34,7 @@ jobs: run: python -m pip install --upgrade pip setuptools virtualenv wheel - name: Install dependencies - run: python -m pip install --upgrade codecov tox + run: python -m pip install --upgrade tox - name: Install tox-py if: ${{ matrix.python-version == '3.6' }} @@ -54,5 +54,8 @@ jobs: tox -e base,dist,docs - name: Upload coverage - run: | - codecov -e TOXENV,DJANGO + - uses: codecov/codecov-action@v3.1.0 + with: + flags: unittests # optional + fail_ci_if_error: true # optional (default = false) + verbose: true # optional (default = false) From 38a74b42da10576857d6bf8bd82a73b15d12a7ed Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Sat, 15 Apr 2023 12:11:35 +0600 Subject: [PATCH 710/907] Revert "test codecov gha (#8946)" (#8947) This reverts commit 684522807f370d6dca731d1d34bffeaa051fe505. --- .github/workflows/main.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e3435208b0..f245f69644 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,7 +9,7 @@ on: jobs: tests: name: Python ${{ matrix.python-version }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 strategy: matrix: @@ -34,7 +34,7 @@ jobs: run: python -m pip install --upgrade pip setuptools virtualenv wheel - name: Install dependencies - run: python -m pip install --upgrade tox + run: python -m pip install --upgrade codecov tox - name: Install tox-py if: ${{ matrix.python-version == '3.6' }} @@ -54,8 +54,5 @@ jobs: tox -e base,dist,docs - name: Upload coverage - - uses: codecov/codecov-action@v3.1.0 - with: - flags: unittests # optional - fail_ci_if_error: true # optional (default = false) - verbose: true # optional (default = false) + run: | + codecov -e TOXENV,DJANGO From 1ce0853ac51635526dc1a285e6b83c9848002f0e Mon Sep 17 00:00:00 2001 From: Mahdi Rahimi <31624047+mahdirahimi1999@users.noreply.github.com> Date: Thu, 27 Apr 2023 07:54:13 +0330 Subject: [PATCH 711/907] Refactor get_field_info method to include max_digits and decimal_places attributes in SimpleMetadata class (#8943) * Refactor get_field_info method to include max_digits and decimal_places attributes in SimpleMetadata class * Add new test to check decimal_field_info_type * Update rest_framework/metadata.py --------- Co-authored-by: Mahdi Co-authored-by: Asif Saif Uddin --- rest_framework/metadata.py | 3 ++- tests/test_metadata.py | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py index 015587897d..c400b1b79e 100644 --- a/rest_framework/metadata.py +++ b/rest_framework/metadata.py @@ -124,7 +124,8 @@ def get_field_info(self, field): attrs = [ 'read_only', 'label', 'help_text', 'min_length', 'max_length', - 'min_value', 'max_value' + 'min_value', 'max_value', + 'max_digits', 'decimal_places' ] for attr in attrs: diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 4abc0fc07e..1bdc8697c4 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -324,6 +324,13 @@ def test_related_field_choices(self): ) assert 'choices' not in field_info + def test_decimal_field_info_type(self): + options = metadata.SimpleMetadata() + field_info = options.get_field_info(serializers.DecimalField(max_digits=18, decimal_places=4)) + assert field_info['type'] == 'decimal' + assert field_info['max_digits'] == 18 + assert field_info['decimal_places'] == 4 + class TestModelSerializerMetadata(TestCase): def test_read_only_primary_key_related_field(self): From 54307a4394820173f7bfeaed53a675c00563bf18 Mon Sep 17 00:00:00 2001 From: suayip uzulmez <17948971+realsuayip@users.noreply.github.com> Date: Sun, 30 Apr 2023 12:20:02 +0300 Subject: [PATCH 712/907] Replaced `OrderedDict` with `dict` (#8964) --- rest_framework/fields.py | 9 ++--- rest_framework/metadata.py | 26 ++++++------ rest_framework/pagination.py | 36 ++++++++--------- rest_framework/relations.py | 11 ++--- rest_framework/renderers.py | 3 +- rest_framework/routers.py | 6 +-- rest_framework/schemas/coreapi.py | 12 +++--- rest_framework/schemas/openapi.py | 3 +- rest_framework/serializers.py | 40 +++++++++---------- rest_framework/templatetags/rest_framework.py | 9 ++--- rest_framework/utils/model_meta.py | 21 ++++------ rest_framework/utils/serializer_helpers.py | 5 +-- rest_framework/viewsets.py | 3 +- tests/test_model_serializer.py | 5 +-- tests/test_renderers.py | 7 ++-- tests/test_viewsets.py | 33 ++++++++------- 16 files changed, 105 insertions(+), 124 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index e41b56fb01..1d77ce4abc 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -6,7 +6,6 @@ import inspect import re import uuid -from collections import OrderedDict from collections.abc import Mapping from django.conf import settings @@ -143,7 +142,7 @@ def to_choices_dict(choices): # choices = [1, 2, 3] # choices = [(1, 'First'), (2, 'Second'), (3, 'Third')] # choices = [('Category', ((1, 'First'), (2, 'Second'))), (3, 'Third')] - ret = OrderedDict() + ret = {} for choice in choices: if not isinstance(choice, (list, tuple)): # single choice @@ -166,7 +165,7 @@ def flatten_choices_dict(choices): flatten_choices_dict({1: '1st', 2: '2nd'}) -> {1: '1st', 2: '2nd'} flatten_choices_dict({'Group': {1: '1st', 2: '2nd'}}) -> {1: '1st', 2: '2nd'} """ - ret = OrderedDict() + ret = {} for key, value in choices.items(): if isinstance(value, dict): # grouped choices (category, sub choices) @@ -1649,7 +1648,7 @@ def to_representation(self, data): def run_child_validation(self, data): result = [] - errors = OrderedDict() + errors = {} for idx, item in enumerate(data): try: @@ -1713,7 +1712,7 @@ def to_representation(self, value): def run_child_validation(self, data): result = {} - errors = OrderedDict() + errors = {} for key, value in data.items(): key = str(key) diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py index c400b1b79e..364ca5b14d 100644 --- a/rest_framework/metadata.py +++ b/rest_framework/metadata.py @@ -6,8 +6,6 @@ Future implementations might use JSON schema or other definitions in order to return this information in a more standardized way. """ -from collections import OrderedDict - from django.core.exceptions import PermissionDenied from django.http import Http404 from django.utils.encoding import force_str @@ -59,11 +57,12 @@ class SimpleMetadata(BaseMetadata): }) def determine_metadata(self, request, view): - metadata = OrderedDict() - metadata['name'] = view.get_view_name() - metadata['description'] = view.get_view_description() - metadata['renders'] = [renderer.media_type for renderer in view.renderer_classes] - metadata['parses'] = [parser.media_type for parser in view.parser_classes] + metadata = { + "name": view.get_view_name(), + "description": view.get_view_description(), + "renders": [renderer.media_type for renderer in view.renderer_classes], + "parses": [parser.media_type for parser in view.parser_classes], + } if hasattr(view, 'get_serializer'): actions = self.determine_actions(request, view) if actions: @@ -106,20 +105,21 @@ def get_serializer_info(self, serializer): # If this is a `ListSerializer` then we want to examine the # underlying child serializer instance instead. serializer = serializer.child - return OrderedDict([ - (field_name, self.get_field_info(field)) + return { + field_name: self.get_field_info(field) for field_name, field in serializer.fields.items() if not isinstance(field, serializers.HiddenField) - ]) + } def get_field_info(self, field): """ Given an instance of a serializer field, return a dictionary of metadata about it. """ - field_info = OrderedDict() - field_info['type'] = self.label_lookup[field] - field_info['required'] = getattr(field, 'required', False) + field_info = { + "type": self.label_lookup[field], + "required": getattr(field, "required", False), + } attrs = [ 'read_only', 'label', 'help_text', diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 34d71f828c..68ab9c786b 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -5,7 +5,7 @@ import contextlib from base64 import b64decode, b64encode -from collections import OrderedDict, namedtuple +from collections import namedtuple from urllib import parse from django.core.paginator import InvalidPage @@ -225,12 +225,12 @@ def get_page_number(self, request, paginator): return page_number def get_paginated_response(self, data): - return Response(OrderedDict([ - ('count', self.page.paginator.count), - ('next', self.get_next_link()), - ('previous', self.get_previous_link()), - ('results', data) - ])) + return Response({ + 'count': self.page.paginator.count, + 'next': self.get_next_link(), + 'previous': self.get_previous_link(), + 'results': data, + }) def get_paginated_response_schema(self, schema): return { @@ -395,12 +395,12 @@ def paginate_queryset(self, queryset, request, view=None): return list(queryset[self.offset:self.offset + self.limit]) def get_paginated_response(self, data): - return Response(OrderedDict([ - ('count', self.count), - ('next', self.get_next_link()), - ('previous', self.get_previous_link()), - ('results', data) - ])) + return Response({ + 'count': self.count, + 'next': self.get_next_link(), + 'previous': self.get_previous_link(), + 'results': data + }) def get_paginated_response_schema(self, schema): return { @@ -892,11 +892,11 @@ def _get_position_from_instance(self, instance, ordering): return None if attr is None else str(attr) def get_paginated_response(self, data): - return Response(OrderedDict([ - ('next', self.get_next_link()), - ('previous', self.get_previous_link()), - ('results', data) - ])) + return Response({ + 'next': self.get_next_link(), + 'previous': self.get_previous_link(), + 'results': data, + }) def get_paginated_response_schema(self, schema): return { diff --git a/rest_framework/relations.py b/rest_framework/relations.py index 53ea2113b4..4409bce77c 100644 --- a/rest_framework/relations.py +++ b/rest_framework/relations.py @@ -1,6 +1,5 @@ import contextlib import sys -from collections import OrderedDict from operator import attrgetter from urllib import parse @@ -199,13 +198,9 @@ def get_choices(self, cutoff=None): if cutoff is not None: queryset = queryset[:cutoff] - return OrderedDict([ - ( - self.to_representation(item), - self.display_value(item) - ) - for item in queryset - ]) + return { + self.to_representation(item): self.display_value(item) for item in queryset + } @property def choices(self): diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 8de0a77a1a..8e8c3a9b3c 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -9,7 +9,6 @@ import base64 import contextlib -from collections import OrderedDict from urllib import parse from django import forms @@ -653,7 +652,7 @@ def get_context(self, data, accepted_media_type, renderer_context): raw_data_patch_form = self.get_raw_data_form(data, view, 'PATCH', request) raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form - response_headers = OrderedDict(sorted(response.items())) + response_headers = dict(sorted(response.items())) renderer_content_type = '' if renderer: renderer_content_type = '%s' % renderer.media_type diff --git a/rest_framework/routers.py b/rest_framework/routers.py index 722fc50a6d..fa5d16922b 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -14,7 +14,7 @@ urlpatterns = router.urls """ import itertools -from collections import OrderedDict, namedtuple +from collections import namedtuple from django.core.exceptions import ImproperlyConfigured from django.urls import NoReverseMatch, path, re_path @@ -321,7 +321,7 @@ class APIRootView(views.APIView): def get(self, request, *args, **kwargs): # Return a plain {"name": "hyperlink"} response. - ret = OrderedDict() + ret = {} namespace = request.resolver_match.namespace for key, url_name in self.api_root_dict.items(): if namespace: @@ -365,7 +365,7 @@ def get_api_root_view(self, api_urls=None): """ Return a basic root view. """ - api_root_dict = OrderedDict() + api_root_dict = {} list_name = self.routes[0].name for prefix, viewset, basename in self.registry: api_root_dict[prefix] = list_name.format(basename=basename) diff --git a/rest_framework/schemas/coreapi.py b/rest_framework/schemas/coreapi.py index 908518e9d7..0713e0cb80 100644 --- a/rest_framework/schemas/coreapi.py +++ b/rest_framework/schemas/coreapi.py @@ -1,5 +1,5 @@ import warnings -from collections import Counter, OrderedDict +from collections import Counter from urllib import parse from django.db import models @@ -54,7 +54,7 @@ def distribute_links(obj): """ -class LinkNode(OrderedDict): +class LinkNode(dict): def __init__(self): self.links = [] self.methods_counter = Counter() @@ -268,11 +268,11 @@ def field_to_schema(field): ) elif isinstance(field, serializers.Serializer): return coreschema.Object( - properties=OrderedDict([ - (key, field_to_schema(value)) + properties={ + key: field_to_schema(value) for key, value in field.fields.items() - ]), + }, title=title, description=description ) @@ -549,7 +549,7 @@ def update_fields(fields, update_with): if not update_with: return fields - by_name = OrderedDict((f.name, f) for f in fields) + by_name = {f.name: f for f in fields} for f in update_with: by_name[f.name] = f fields = list(by_name.values()) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index ea220c631f..d8707e1e1f 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -1,6 +1,5 @@ import re import warnings -from collections import OrderedDict from decimal import Decimal from operator import attrgetter from urllib.parse import urljoin @@ -340,7 +339,7 @@ def get_pagination_parameters(self, path, method): return paginator.get_schema_operation_parameters(view) def map_choicefield(self, field): - choices = list(OrderedDict.fromkeys(field.choices)) # preserve order and remove duplicates + choices = list(dict.fromkeys(field.choices)) # preserve order and remove duplicates if all(isinstance(choice, bool) for choice in choices): type = 'boolean' elif all(isinstance(choice, int) for choice in choices): diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index e27f8a47c6..01bebf5fca 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -15,7 +15,7 @@ import copy import inspect import traceback -from collections import OrderedDict, defaultdict +from collections import defaultdict from collections.abc import Mapping from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured @@ -308,7 +308,7 @@ def visit(name): for name, f in base._declared_fields.items() if name not in known ] - return OrderedDict(base_fields + fields) + return dict(base_fields + fields) def __new__(cls, name, bases, attrs): attrs['_declared_fields'] = cls._get_declared_fields(bases, attrs) @@ -393,20 +393,20 @@ def get_initial(self): if hasattr(self, 'initial_data'): # initial_data may not be a valid type if not isinstance(self.initial_data, Mapping): - return OrderedDict() + return {} - return OrderedDict([ - (field_name, field.get_value(self.initial_data)) + return { + field_name: field.get_value(self.initial_data) for field_name, field in self.fields.items() if (field.get_value(self.initial_data) is not empty) and not field.read_only - ]) + } - return OrderedDict([ - (field.field_name, field.get_initial()) + return { + field.field_name: field.get_initial() for field in self.fields.values() if not field.read_only - ]) + } def get_value(self, dictionary): # We override the default field access in order to support @@ -441,7 +441,7 @@ def _read_only_defaults(self): if (field.read_only) and (field.default != empty) and (field.source != '*') and ('.' not in field.source) ] - defaults = OrderedDict() + defaults = {} for field in fields: try: default = field.get_default() @@ -474,8 +474,8 @@ def to_internal_value(self, data): api_settings.NON_FIELD_ERRORS_KEY: [message] }, code='invalid') - ret = OrderedDict() - errors = OrderedDict() + ret = {} + errors = {} fields = self._writable_fields for field in fields: @@ -503,7 +503,7 @@ def to_representation(self, instance): """ Object instance -> Dict of primitive datatypes. """ - ret = OrderedDict() + ret = {} fields = self._readable_fields for field in fields: @@ -1061,7 +1061,7 @@ def get_fields(self): ) # Determine the fields that should be included on the serializer. - fields = OrderedDict() + fields = {} for field_name in field_names: # If the field is explicitly declared on the class then use that. @@ -1546,16 +1546,16 @@ def get_unique_together_validators(self): # which may map onto a model field. Any dotted field name lookups # cannot map to a field, and must be a traversal, so we're not # including those. - field_sources = OrderedDict( - (field.field_name, field.source) for field in self._writable_fields + field_sources = { + field.field_name: field.source for field in self._writable_fields if (field.source != '*') and ('.' not in field.source) - ) + } # Special Case: Add read_only fields with defaults. - field_sources.update(OrderedDict( - (field.field_name, field.source) for field in self.fields.values() + field_sources.update({ + field.field_name: field.source for field in self.fields.values() if (field.read_only) and (field.default != empty) and (field.source != '*') and ('.' not in field.source) - )) + }) # Invert so we can find the serializer field names that correspond to # the model field names in the unique_together sets. This also allows diff --git a/rest_framework/templatetags/rest_framework.py b/rest_framework/templatetags/rest_framework.py index ccd9430b4e..53916d3f28 100644 --- a/rest_framework/templatetags/rest_framework.py +++ b/rest_framework/templatetags/rest_framework.py @@ -1,5 +1,4 @@ import re -from collections import OrderedDict from django import template from django.template import loader @@ -49,10 +48,10 @@ def with_location(fields, location): @register.simple_tag def form_for_link(link): import coreschema - properties = OrderedDict([ - (field.name, field.schema or coreschema.String()) + properties = { + field.name: field.schema or coreschema.String() for field in link.fields - ]) + } required = [ field.name for field in link.fields @@ -272,7 +271,7 @@ def schema_links(section, sec_key=None): links.update(new_links) if sec_key is not None: - new_links = OrderedDict() + new_links = {} for link_key, link in links.items(): new_key = NESTED_FORMAT % (sec_key, link_key) new_links.update({new_key: link}) diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py index 4cc93b8ef5..bd5d9177c0 100644 --- a/rest_framework/utils/model_meta.py +++ b/rest_framework/utils/model_meta.py @@ -5,7 +5,7 @@ Usage: `get_field_info(model)` returns a `FieldInfo` instance. """ -from collections import OrderedDict, namedtuple +from collections import namedtuple FieldInfo = namedtuple('FieldResult', [ 'pk', # Model field instance @@ -58,7 +58,7 @@ def _get_pk(opts): def _get_fields(opts): - fields = OrderedDict() + fields = {} for field in [field for field in opts.fields if field.serialize and not field.remote_field]: fields[field.name] = field @@ -71,9 +71,9 @@ def _get_to_field(field): def _get_forward_relationships(opts): """ - Returns an `OrderedDict` of field names to `RelationInfo`. + Returns a dict of field names to `RelationInfo`. """ - forward_relations = OrderedDict() + forward_relations = {} for field in [field for field in opts.fields if field.serialize and field.remote_field]: forward_relations[field.name] = RelationInfo( model_field=field, @@ -103,9 +103,9 @@ def _get_forward_relationships(opts): def _get_reverse_relationships(opts): """ - Returns an `OrderedDict` of field names to `RelationInfo`. + Returns a dict of field names to `RelationInfo`. """ - reverse_relations = OrderedDict() + reverse_relations = {} all_related_objects = [r for r in opts.related_objects if not r.field.many_to_many] for relation in all_related_objects: accessor_name = relation.get_accessor_name() @@ -139,19 +139,14 @@ def _get_reverse_relationships(opts): def _merge_fields_and_pk(pk, fields): - fields_and_pk = OrderedDict() - fields_and_pk['pk'] = pk - fields_and_pk[pk.name] = pk + fields_and_pk = {'pk': pk, pk.name: pk} fields_and_pk.update(fields) return fields_and_pk def _merge_relationships(forward_relations, reverse_relations): - return OrderedDict( - list(forward_relations.items()) + - list(reverse_relations.items()) - ) + return {**forward_relations, **reverse_relations} def is_abstract_model(model): diff --git a/rest_framework/utils/serializer_helpers.py b/rest_framework/utils/serializer_helpers.py index 6ca794584b..0e59aa66a2 100644 --- a/rest_framework/utils/serializer_helpers.py +++ b/rest_framework/utils/serializer_helpers.py @@ -1,6 +1,5 @@ import contextlib import sys -from collections import OrderedDict from collections.abc import Mapping, MutableMapping from django.utils.encoding import force_str @@ -8,7 +7,7 @@ from rest_framework.utils import json -class ReturnDict(OrderedDict): +class ReturnDict(dict): """ Return object from `serializer.data` for the `Serializer` class. Includes a backlink to the serializer instance for renderers @@ -161,7 +160,7 @@ class BindingDict(MutableMapping): def __init__(self, serializer): self.serializer = serializer - self.fields = OrderedDict() + self.fields = {} def __setitem__(self, key, field): self.fields[key] = field diff --git a/rest_framework/viewsets.py b/rest_framework/viewsets.py index 1c56f61e86..2eba17b4a3 100644 --- a/rest_framework/viewsets.py +++ b/rest_framework/viewsets.py @@ -16,7 +16,6 @@ router.register(r'users', UserViewSet, 'user') urlpatterns = router.urls """ -from collections import OrderedDict from functools import update_wrapper from inspect import getmembers @@ -183,7 +182,7 @@ def get_extra_action_url_map(self): This method will noop if `detail` was not provided as a view initkwarg. """ - action_urls = OrderedDict() + action_urls = {} # exit early if `detail` has not been provided if self.detail is None: diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index 419eae632b..c5ac888f55 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -10,7 +10,6 @@ import json # noqa import sys import tempfile -from collections import OrderedDict import django import pytest @@ -762,7 +761,7 @@ class Meta: fields = '__all__' serializer = TestSerializer() - expected = OrderedDict([(1, 'Red Color'), (2, 'Yellow Color'), (3, 'Green Color')]) + expected = {1: 'Red Color', 2: 'Yellow Color', 3: 'Green Color'} self.assertEqual(serializer.fields['color'].choices, expected) def test_custom_display_value(self): @@ -778,7 +777,7 @@ class Meta: fields = '__all__' serializer = TestSerializer() - expected = OrderedDict([(1, 'My Red Color'), (2, 'My Yellow Color'), (3, 'My Green Color')]) + expected = {1: 'My Red Color', 2: 'My Yellow Color', 3: 'My Green Color'} self.assertEqual(serializer.fields['color'].choices, expected) diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 8271608e15..71d734c86c 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -1,5 +1,4 @@ import re -from collections import OrderedDict from collections.abc import MutableMapping import pytest @@ -457,12 +456,12 @@ def test_get_caching(self): class TestJSONIndentationStyles: def test_indented(self): renderer = JSONRenderer() - data = OrderedDict([('a', 1), ('b', 2)]) + data = {"a": 1, "b": 2} assert renderer.render(data) == b'{"a":1,"b":2}' def test_compact(self): renderer = JSONRenderer() - data = OrderedDict([('a', 1), ('b', 2)]) + data = {"a": 1, "b": 2} context = {'indent': 4} assert ( renderer.render(data, renderer_context=context) == @@ -472,7 +471,7 @@ def test_compact(self): def test_long_form(self): renderer = JSONRenderer() renderer.compact = False - data = OrderedDict([('a', 1), ('b', 2)]) + data = {"a": 1, "b": 2} assert renderer.render(data) == b'{"a": 1, "b": 2}' diff --git a/tests/test_viewsets.py b/tests/test_viewsets.py index 8842b0b1cd..8e439c86eb 100644 --- a/tests/test_viewsets.py +++ b/tests/test_viewsets.py @@ -1,4 +1,3 @@ -from collections import OrderedDict from functools import wraps import pytest @@ -261,11 +260,11 @@ def test_list_view(self): response = self.client.get('/api/actions/') view = response.view - expected = OrderedDict([ - ('Custom list action', 'http://testserver/api/actions/custom_list_action/'), - ('List action', 'http://testserver/api/actions/list_action/'), - ('Wrapped list action', 'http://testserver/api/actions/wrapped_list_action/'), - ]) + expected = { + 'Custom list action': 'http://testserver/api/actions/custom_list_action/', + 'List action': 'http://testserver/api/actions/list_action/', + 'Wrapped list action': 'http://testserver/api/actions/wrapped_list_action/', + } self.assertEqual(view.get_extra_action_url_map(), expected) @@ -273,28 +272,28 @@ def test_detail_view(self): response = self.client.get('/api/actions/1/') view = response.view - expected = OrderedDict([ - ('Custom detail action', 'http://testserver/api/actions/1/custom_detail_action/'), - ('Detail action', 'http://testserver/api/actions/1/detail_action/'), - ('Wrapped detail action', 'http://testserver/api/actions/1/wrapped_detail_action/'), + expected = { + 'Custom detail action': 'http://testserver/api/actions/1/custom_detail_action/', + 'Detail action': 'http://testserver/api/actions/1/detail_action/', + 'Wrapped detail action': 'http://testserver/api/actions/1/wrapped_detail_action/', # "Unresolvable detail action" excluded, since it's not resolvable - ]) + } self.assertEqual(view.get_extra_action_url_map(), expected) def test_uninitialized_view(self): - self.assertEqual(ActionViewSet().get_extra_action_url_map(), OrderedDict()) + self.assertEqual(ActionViewSet().get_extra_action_url_map(), {}) def test_action_names(self): # Action 'name' and 'suffix' kwargs should be respected response = self.client.get('/api/names/1/') view = response.view - expected = OrderedDict([ - ('Custom Name', 'http://testserver/api/names/1/named_action/'), - ('Action Names Custom Suffix', 'http://testserver/api/names/1/suffixed_action/'), - ('Unnamed action', 'http://testserver/api/names/1/unnamed_action/'), - ]) + expected = { + 'Custom Name': 'http://testserver/api/names/1/named_action/', + 'Action Names Custom Suffix': 'http://testserver/api/names/1/suffixed_action/', + 'Unnamed action': 'http://testserver/api/names/1/unnamed_action/', + } self.assertEqual(view.get_extra_action_url_map(), expected) From f1a11d41cbd1d16871e8d8426cbf290da57dfb6d Mon Sep 17 00:00:00 2001 From: fdomain Date: Tue, 2 May 2023 02:55:59 +0200 Subject: [PATCH 713/907] fix: fallback on CursorPagination ordering if unset on the view (#8954) * this commit fixes the usage of a CursorPagination combined with a view implementing an ordering filter, without a default ordering value. * former behavior was to fetch the ordering value from the filter, and raises an error if the value was None, preventing the fallback on the ordering set on the CursorPagination class itself. * we reversed the logic by getting first the value set on the class, and override it by the ordering filter if the parameter is present --- rest_framework/pagination.py | 37 +++++++++++++++++------------------- tests/test_pagination.py | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 68ab9c786b..af508bef6d 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -801,6 +801,10 @@ def get_ordering(self, request, queryset, view): """ Return a tuple of strings, that may be used in an `order_by` method. """ + # The default case is to check for an `ordering` attribute + # on this pagination instance. + ordering = self.ordering + ordering_filters = [ filter_cls for filter_cls in getattr(view, 'filter_backends', []) if hasattr(filter_cls, 'get_ordering') @@ -811,26 +815,19 @@ def get_ordering(self, request, queryset, view): # then we defer to that filter to determine the ordering. filter_cls = ordering_filters[0] filter_instance = filter_cls() - ordering = filter_instance.get_ordering(request, queryset, view) - assert ordering is not None, ( - 'Using cursor pagination, but filter class {filter_cls} ' - 'returned a `None` ordering.'.format( - filter_cls=filter_cls.__name__ - ) - ) - else: - # The default case is to check for an `ordering` attribute - # on this pagination instance. - ordering = self.ordering - assert ordering is not None, ( - 'Using cursor pagination, but no ordering attribute was declared ' - 'on the pagination class.' - ) - assert '__' not in ordering, ( - 'Cursor pagination does not support double underscore lookups ' - 'for orderings. Orderings should be an unchanging, unique or ' - 'nearly-unique field on the model, such as "-created" or "pk".' - ) + ordering_from_filter = filter_instance.get_ordering(request, queryset, view) + if ordering_from_filter: + ordering = ordering_from_filter + + assert ordering is not None, ( + 'Using cursor pagination, but no ordering attribute was declared ' + 'on the pagination class.' + ) + assert '__' not in ordering, ( + 'Cursor pagination does not support double underscore lookups ' + 'for orderings. Orderings should be an unchanging, unique or ' + 'nearly-unique field on the model, such as "-created" or "pk".' + ) assert isinstance(ordering, (str, list, tuple)), ( 'Invalid ordering. Expected string or tuple, but got {type}'.format( diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 8f9b20a0d4..d606986ab7 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -632,6 +632,24 @@ class MockView: ordering = self.pagination.get_ordering(request, [], MockView()) assert ordering == ('created',) + def test_use_with_ordering_filter_without_ordering_default_value(self): + class MockView: + filter_backends = (filters.OrderingFilter,) + ordering_fields = ['username', 'created'] + + request = Request(factory.get('/')) + ordering = self.pagination.get_ordering(request, [], MockView()) + # it gets the value of `ordering` provided by CursorPagination + assert ordering == ('created',) + + request = Request(factory.get('/', {'ordering': 'username'})) + ordering = self.pagination.get_ordering(request, [], MockView()) + assert ordering == ('username',) + + request = Request(factory.get('/', {'ordering': 'invalid'})) + ordering = self.pagination.get_ordering(request, [], MockView()) + assert ordering == ('created',) + def test_cursor_pagination(self): (previous, current, next, previous_url, next_url) = self.get_pages('/') From d14eb7555da4c8986c8a501836943f9fcbc99fb4 Mon Sep 17 00:00:00 2001 From: Mahdi Rahimi <31624047+mahdirahimi1999@users.noreply.github.com> Date: Tue, 2 May 2023 19:39:19 +0330 Subject: [PATCH 714/907] Refactor read function to use context manager for file handling (#8967) Co-authored-by: Mahdi --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d9002fdb91..533ffa97f0 100755 --- a/setup.py +++ b/setup.py @@ -37,7 +37,8 @@ def read(f): - return open(f, 'r', encoding='utf-8').read() + with open(f, 'r', encoding='utf-8') as file: + return file.read() def get_version(package): From e08e606c82afd0d5ec82b2c58badec11a4ce825e Mon Sep 17 00:00:00 2001 From: Saad Aleem Date: Wed, 3 May 2023 12:08:07 +0500 Subject: [PATCH 715/907] Fix mapping for choice values (#8968) * fix mapping for choice values * fix tests for ChoiceField IntegerChoices * fix imports * fix imports in tests * Check for multiple choice enums * fix formatting * add tests for text choices class --- rest_framework/fields.py | 13 ++++++++++- tests/test_fields.py | 50 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 1d77ce4abc..f4fd265394 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -16,6 +16,7 @@ MinValueValidator, ProhibitNullCharactersValidator, RegexValidator, URLValidator, ip_address_validators ) +from django.db.models import IntegerChoices, TextChoices from django.forms import FilePathField as DjangoFilePathField from django.forms import ImageField as DjangoImageField from django.utils import timezone @@ -1397,6 +1398,10 @@ def to_internal_value(self, data): if data == '' and self.allow_blank: return '' + if isinstance(data, (IntegerChoices, TextChoices)) and str(data) != \ + str(data.value): + data = data.value + try: return self.choice_strings_to_values[str(data)] except KeyError: @@ -1405,6 +1410,11 @@ def to_internal_value(self, data): def to_representation(self, value): if value in ('', None): return value + + if isinstance(value, (IntegerChoices, TextChoices)) and str(value) != \ + str(value.value): + value = value.value + return self.choice_strings_to_values.get(str(value), value) def iter_options(self): @@ -1428,7 +1438,8 @@ def _set_choices(self, choices): # Allows us to deal with eg. integer choices while supporting either # integer or string input, but still get the correct datatype out. self.choice_strings_to_values = { - str(key): key for key in self.choices + str(key.value) if isinstance(key, (IntegerChoices, TextChoices)) + and str(key) != str(key.value) else str(key): key for key in self.choices } choices = property(_get_choices, _set_choices) diff --git a/tests/test_fields.py b/tests/test_fields.py index 5804d7b3b3..bf25b71b8d 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -5,11 +5,13 @@ import sys import uuid from decimal import ROUND_DOWN, ROUND_UP, Decimal +from enum import auto from unittest.mock import patch import pytest import pytz from django.core.exceptions import ValidationError as DjangoValidationError +from django.db.models import IntegerChoices, TextChoices from django.http import QueryDict from django.test import TestCase, override_settings from django.utils.timezone import activate, deactivate, override @@ -1824,6 +1826,54 @@ def test_edit_choices(self): field.run_validation(2) assert exc_info.value.detail == ['"2" is not a valid choice.'] + def test_integer_choices(self): + class ChoiceCase(IntegerChoices): + first = auto() + second = auto() + # Enum validate + choices = [ + (ChoiceCase.first, "1"), + (ChoiceCase.second, "2") + ] + + field = serializers.ChoiceField(choices=choices) + assert field.run_validation(1) == 1 + assert field.run_validation(ChoiceCase.first) == 1 + assert field.run_validation("1") == 1 + + choices = [ + (ChoiceCase.first.value, "1"), + (ChoiceCase.second.value, "2") + ] + + field = serializers.ChoiceField(choices=choices) + assert field.run_validation(1) == 1 + assert field.run_validation(ChoiceCase.first) == 1 + assert field.run_validation("1") == 1 + + def test_text_choices(self): + class ChoiceCase(TextChoices): + first = auto() + second = auto() + # Enum validate + choices = [ + (ChoiceCase.first, "first"), + (ChoiceCase.second, "second") + ] + + field = serializers.ChoiceField(choices=choices) + assert field.run_validation(ChoiceCase.first) == "first" + assert field.run_validation("first") == "first" + + choices = [ + (ChoiceCase.first.value, "first"), + (ChoiceCase.second.value, "second") + ] + + field = serializers.ChoiceField(choices=choices) + assert field.run_validation(ChoiceCase.first) == "first" + assert field.run_validation("first") == "first" + class TestChoiceFieldWithType(FieldValues): """ From 99e8b4033efa44930ace40fb48a4d7bcd224f9fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ismael=20Jim=C3=A9nez=20S=C3=A1nchez?= Date: Tue, 9 May 2023 16:50:29 +0200 Subject: [PATCH 716/907] feat: enforce Decimal type in min_value and max_value arguments of DecimalField (#8972) * add warning when min_value and max_value are not decimal * remove redundant module name in log --------- Co-authored-by: ismaeljs <> --- rest_framework/fields.py | 8 ++++++++ tests/test_fields.py | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index f4fd265394..4f82e4a10e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -4,6 +4,7 @@ import decimal import functools import inspect +import logging import re import uuid from collections.abc import Mapping @@ -38,6 +39,8 @@ from rest_framework.utils.timezone import valid_datetime from rest_framework.validators import ProhibitSurrogateCharactersValidator +logger = logging.getLogger("rest_framework.fields") + class empty: """ @@ -990,6 +993,11 @@ def __init__(self, max_digits, decimal_places, coerce_to_string=None, max_value= self.max_value = max_value self.min_value = min_value + if self.max_value is not None and not isinstance(self.max_value, decimal.Decimal): + logger.warning("max_value in DecimalField should be Decimal type.") + if self.min_value is not None and not isinstance(self.min_value, decimal.Decimal): + logger.warning("min_value in DecimalField should be Decimal type.") + if self.max_digits is not None and self.decimal_places is not None: self.max_whole_digits = self.max_digits - self.decimal_places else: diff --git a/tests/test_fields.py b/tests/test_fields.py index bf25b71b8d..bcf3884412 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1216,6 +1216,17 @@ class TestMinMaxDecimalField(FieldValues): min_value=10, max_value=20 ) + def test_warning_when_not_decimal_types(self, caplog): + import logging + serializers.DecimalField( + max_digits=3, decimal_places=1, + min_value=10, max_value=20 + ) + assert caplog.record_tuples == [ + ("rest_framework.fields", logging.WARNING, "max_value in DecimalField should be Decimal type."), + ("rest_framework.fields", logging.WARNING, "min_value in DecimalField should be Decimal type.") + ] + class TestAllowEmptyStrDecimalFieldWithValidators(FieldValues): """ From 001d6ec2c907e638496fb4ef0cffa915bf0718fd Mon Sep 17 00:00:00 2001 From: Mathieu Dupuy Date: Sun, 14 May 2023 02:00:13 +0200 Subject: [PATCH 717/907] remove django 2.2 from docs index (#8982) --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index cab5511ac1..ad241c0a37 100644 --- a/docs/index.md +++ b/docs/index.md @@ -86,7 +86,7 @@ continued development by **[signing up for a paid plan][funding]**. REST framework requires the following: * Python (3.6, 3.7, 3.8, 3.9, 3.10, 3.11) -* Django (2.2, 3.0, 3.1, 3.2, 4.0, 4.1) +* Django (3.0, 3.1, 3.2, 4.0, 4.1) We **highly recommend** and only officially support the latest patch release of each Python and Django series. From 7bebe9772488fcf67949e1221e70af642ed0cb74 Mon Sep 17 00:00:00 2001 From: Mehraz Hossain Rumman <59512321+MehrazRumman@users.noreply.github.com> Date: Mon, 15 May 2023 21:02:17 +0600 Subject: [PATCH 718/907] Declared Django 4.2 support in README.md (#8985) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8375291dc..c2975a418f 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ There is a live example API for testing purposes, [available here][sandbox]. # Requirements * Python 3.6+ -* Django 4.1, 4.0, 3.2, 3.1, 3.0 +* Django 4.2, 4.1, 4.0, 3.2, 3.1, 3.0 We **highly recommend** and only officially support the latest patch release of each Python and Django series. From 332e9560ab0b3a1b8c0ab8f68e95c09bc2d8999f Mon Sep 17 00:00:00 2001 From: Dominik Bruhn Date: Wed, 17 May 2023 07:46:48 +0200 Subject: [PATCH 719/907] Fix Links in Documentation to Django `reverse` and `reverse_lazy` (#8986) * Fix Django Docs url in reverse.md Django URLs of the documentation of `reverse` and `reverse_lazy` were wrong. * Update reverse.md --- docs/api-guide/reverse.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/reverse.md b/docs/api-guide/reverse.md index 82dd168818..c3fa52f21f 100644 --- a/docs/api-guide/reverse.md +++ b/docs/api-guide/reverse.md @@ -54,5 +54,5 @@ As with the `reverse` function, you should **include the request as a keyword ar api_root = reverse_lazy('api-root', request=request) [cite]: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5 -[reverse]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse -[reverse-lazy]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse-lazy +[reverse]: https://docs.djangoproject.com/en/stable/ref/urlresolvers/#reverse +[reverse-lazy]: https://docs.djangoproject.com/en/stable/ref/urlresolvers/#reverse-lazy From a25aac7d6739c43a4997829a0ff79cce12e8c0de Mon Sep 17 00:00:00 2001 From: jornvanwier Date: Thu, 18 May 2023 05:46:40 +0200 Subject: [PATCH 720/907] fix URLPathVersioning reverse fallback (#7247) * fix URLPathVersioning reverse fallback * add test for URLPathVersioning reverse fallback * Update tests/test_versioning.py --------- Co-authored-by: Jorn van Wier Co-authored-by: Asif Saif Uddin --- rest_framework/versioning.py | 6 ++++-- tests/test_versioning.py | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py index 78cfc9dc81..c2764c7a40 100644 --- a/rest_framework/versioning.py +++ b/rest_framework/versioning.py @@ -81,8 +81,10 @@ def determine_version(self, request, *args, **kwargs): def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): if request.version is not None: - kwargs = {} if (kwargs is None) else kwargs - kwargs[self.version_param] = request.version + kwargs = { + self.version_param: request.version, + **(kwargs or {}) + } return super().reverse( viewname, args, kwargs, request, format, **extra diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 93f61d2be4..b216461840 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -152,6 +152,8 @@ class TestURLReversing(URLPatternsTestCase, APITestCase): path('v1/', include((included, 'v1'), namespace='v1')), path('another/', dummy_view, name='another'), re_path(r'^(?P[v1|v2]+)/another/$', dummy_view, name='another'), + re_path(r'^(?P.+)/unversioned/$', dummy_view, name='unversioned'), + ] def test_reverse_unversioned(self): @@ -198,6 +200,14 @@ def test_reverse_url_path_versioning(self): response = view(request) assert response.data == {'url': 'http://testserver/another/'} + # Test fallback when kwargs is not None + request = factory.get('/v1/endpoint/') + request.versioning_scheme = scheme() + request.version = 'v1' + + reversed_url = reverse('unversioned', request=request, kwargs={'foo': 'bar'}) + assert reversed_url == 'http://testserver/bar/unversioned/' + def test_reverse_namespace_versioning(self): class FakeResolverMatch(ResolverMatch): namespace = 'v1' From d252d22343b9c9688db77b59aa72dabd540bd252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Beaul=C3=A9?= Date: Wed, 24 May 2023 10:59:42 -0300 Subject: [PATCH 721/907] Make set_value a method within `Serializer` (#8001) * Make set_value a static method for Serializers As an alternative to #7671, let the method be overridden if needed. As the function is only used for serializers, it has a better place in the Serializer class. * Set `set_value` as an object (non-static) method * Add tests for set_value() These tests follow the examples given in the method. --- rest_framework/fields.py | 21 --------------------- rest_framework/serializers.py | 24 ++++++++++++++++++++++-- tests/test_serializer.py | 21 +++++++++++++++++++++ 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 4f82e4a10e..623e72e0ad 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -113,27 +113,6 @@ def get_attribute(instance, attrs): return instance -def set_value(dictionary, keys, value): - """ - Similar to Python's built in `dictionary[key] = value`, - but takes a list of nested keys instead of a single key. - - set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2} - set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2} - set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}} - """ - if not keys: - dictionary.update(value) - return - - for key in keys[:-1]: - if key not in dictionary: - dictionary[key] = {} - dictionary = dictionary[key] - - dictionary[keys[-1]] = value - - def to_choices_dict(choices): """ Convert choices into key/value dicts. diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 01bebf5fca..a3d68b03db 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -28,7 +28,7 @@ from rest_framework.compat import postgres_fields from rest_framework.exceptions import ErrorDetail, ValidationError -from rest_framework.fields import get_error_detail, set_value +from rest_framework.fields import get_error_detail from rest_framework.settings import api_settings from rest_framework.utils import html, model_meta, representation from rest_framework.utils.field_mapping import ( @@ -346,6 +346,26 @@ class Serializer(BaseSerializer, metaclass=SerializerMetaclass): 'invalid': _('Invalid data. Expected a dictionary, but got {datatype}.') } + def set_value(self, dictionary, keys, value): + """ + Similar to Python's built in `dictionary[key] = value`, + but takes a list of nested keys instead of a single key. + + set_value({'a': 1}, [], {'b': 2}) -> {'a': 1, 'b': 2} + set_value({'a': 1}, ['x'], 2) -> {'a': 1, 'x': 2} + set_value({'a': 1}, ['x', 'y'], 2) -> {'a': 1, 'x': {'y': 2}} + """ + if not keys: + dictionary.update(value) + return + + for key in keys[:-1]: + if key not in dictionary: + dictionary[key] = {} + dictionary = dictionary[key] + + dictionary[keys[-1]] = value + @cached_property def fields(self): """ @@ -492,7 +512,7 @@ def to_internal_value(self, data): except SkipField: pass else: - set_value(ret, field.source_attrs, validated_value) + self.set_value(ret, field.source_attrs, validated_value) if errors: raise ValidationError(errors) diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 1d9efaa434..10fa8afb94 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -762,3 +762,24 @@ class TestSerializer(serializers.Serializer): assert (s.data | {}).__class__ == s.data.__class__ assert ({} | s.data).__class__ == s.data.__class__ + + +class TestSetValueMethod: + # Serializer.set_value() modifies the first parameter in-place. + + s = serializers.Serializer() + + def test_no_keys(self): + ret = {'a': 1} + self.s.set_value(ret, [], {'b': 2}) + assert ret == {'a': 1, 'b': 2} + + def test_one_key(self): + ret = {'a': 1} + self.s.set_value(ret, ['x'], 2) + assert ret == {'a': 1, 'x': 2} + + def test_nested_key(self): + ret = {'a': 1} + self.s.set_value(ret, ['x', 'y'], 2) + assert ret == {'a': 1, 'x': {'y': 2}} From e2a4559c03247f69e85b2e0e7ede4e8b6201adc2 Mon Sep 17 00:00:00 2001 From: Saad Aleem Date: Mon, 29 May 2023 19:03:11 +0500 Subject: [PATCH 722/907] Fix validation for ListSerializer (#8979) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: Make the instance variable of child serializer point to the correct list object instead of the entire list when validating ListSerializer * fix formatting issues for list serializer validation fix * fix imports sorting for list serializer tests * remove django 2.2 from docs index (#8982) * Declared Django 4.2 support in README.md (#8985) * Fix Links in Documentation to Django `reverse` and `reverse_lazy` (#8986) * Fix Django Docs url in reverse.md Django URLs of the documentation of `reverse` and `reverse_lazy` were wrong. * Update reverse.md * fix URLPathVersioning reverse fallback (#7247) * fix URLPathVersioning reverse fallback * add test for URLPathVersioning reverse fallback * Update tests/test_versioning.py --------- Co-authored-by: Jorn van Wier Co-authored-by: Asif Saif Uddin * Make set_value a method within `Serializer` (#8001) * Make set_value a static method for Serializers As an alternative to #7671, let the method be overridden if needed. As the function is only used for serializers, it has a better place in the Serializer class. * Set `set_value` as an object (non-static) method * Add tests for set_value() These tests follow the examples given in the method. * fix: Make the instance variable of child serializer point to the correct list object instead of the entire list when validating ListSerializer * Make set_value a method within `Serializer` (#8001) * Make set_value a static method for Serializers As an alternative to #7671, let the method be overridden if needed. As the function is only used for serializers, it has a better place in the Serializer class. * Set `set_value` as an object (non-static) method * Add tests for set_value() These tests follow the examples given in the method. * fix: Make the instance variable of child serializer point to the correct list object instead of the entire list when validating ListSerializer * fix: Make the instance variable of child serializer point to the correct list object instead of the entire list when validating ListSerializer * fix formatting issues for list serializer validation fix * fix: Make the instance variable of child serializer point to the correct list object instead of the entire list when validating ListSerializer * fix formatting issues for list serializer validation fix * fix linting * Update rest_framework/serializers.py Co-authored-by: Sergei Shishov * Update rest_framework/serializers.py Co-authored-by: Sergei Shishov * fix: instance variable in list serializer, remove commented code --------- Co-authored-by: Mathieu Dupuy Co-authored-by: Mehraz Hossain Rumman <59512321+MehrazRumman@users.noreply.github.com> Co-authored-by: Dominik Bruhn Co-authored-by: jornvanwier Co-authored-by: Jorn van Wier Co-authored-by: Asif Saif Uddin Co-authored-by: Étienne Beaulé Co-authored-by: Sergei Shishov --- rest_framework/serializers.py | 14 +++++++- tests/test_serializer.py | 61 +++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index a3d68b03db..56fa918dc8 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -609,6 +609,12 @@ def __init__(self, *args, **kwargs): self.min_length = kwargs.pop('min_length', None) assert self.child is not None, '`child` is a required argument.' assert not inspect.isclass(self.child), '`child` has not been instantiated.' + + instance = kwargs.get('instance', []) + data = kwargs.get('data', []) + if instance and data: + assert len(data) == len(instance), 'Data and instance should have same length' + super().__init__(*args, **kwargs) self.child.bind(field_name='', parent=self) @@ -683,7 +689,13 @@ def to_internal_value(self, data): ret = [] errors = [] - for item in data: + for idx, item in enumerate(data): + if ( + hasattr(self, 'instance') + and self.instance + and len(self.instance) > idx + ): + self.child.instance = self.instance[idx] try: validated = self.child.run_validation(item) except ValidationError as exc: diff --git a/tests/test_serializer.py b/tests/test_serializer.py index 10fa8afb94..39d9238ef9 100644 --- a/tests/test_serializer.py +++ b/tests/test_serializer.py @@ -2,6 +2,7 @@ import pickle import re import sys +import unittest from collections import ChainMap from collections.abc import Mapping @@ -783,3 +784,63 @@ def test_nested_key(self): ret = {'a': 1} self.s.set_value(ret, ['x', 'y'], 2) assert ret == {'a': 1, 'x': {'y': 2}} + + +class MyClass(models.Model): + name = models.CharField(max_length=100) + value = models.CharField(max_length=100, blank=True) + + app_label = "test" + + @property + def is_valid(self): + return self.name == 'valid' + + +class MyClassSerializer(serializers.ModelSerializer): + class Meta: + model = MyClass + fields = ('id', 'name', 'value') + + def validate_value(self, value): + if value and not self.instance.is_valid: + raise serializers.ValidationError( + 'Status cannot be set for invalid instance') + return value + + +class TestMultipleObjectsValidation(unittest.TestCase): + def setUp(self): + self.objs = [ + MyClass(name='valid'), + MyClass(name='invalid'), + MyClass(name='other'), + ] + + def test_multiple_objects_are_validated_separately(self): + + serializer = MyClassSerializer( + data=[{'value': 'set', 'id': instance.id} for instance in + self.objs], + instance=self.objs, + many=True, + partial=True, + ) + + assert not serializer.is_valid() + assert serializer.errors == [ + {}, + {'value': ['Status cannot be set for invalid instance']}, + {'value': ['Status cannot be set for invalid instance']} + ] + + def test_exception_raised_when_data_and_instance_length_different(self): + + with self.assertRaises(AssertionError): + MyClassSerializer( + data=[{'value': 'set', 'id': instance.id} for instance in + self.objs], + instance=self.objs[:-1], + many=True, + partial=True, + ) From ff5f647df01d83f1bad32e37ed8d9181da21538c Mon Sep 17 00:00:00 2001 From: Vladimir Kasatkin Date: Wed, 31 May 2023 07:36:21 +0300 Subject: [PATCH 723/907] Fix example of `requires_context` attribute (#8952) --- docs/api-guide/validators.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index dac937d9bd..2a1e3e6b36 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -295,13 +295,14 @@ To write a class-based validator, use the `__call__` method. Class-based validat In some advanced cases you might want a validator to be passed the serializer field it is being used with as additional context. You can do so by setting -a `requires_context = True` attribute on the validator. The `__call__` method +a `requires_context = True` attribute on the validator class. The `__call__` method will then be called with the `serializer_field` or `serializer` as an additional argument. - requires_context = True + class MultipleOf: + requires_context = True - def __call__(self, value, serializer_field): - ... + def __call__(self, value, serializer_field): + ... [cite]: https://docs.djangoproject.com/en/stable/ref/validators/ From 03e2ecc9a5afcb33c9069efcb39b5a04a49d7774 Mon Sep 17 00:00:00 2001 From: Alasdair Nicol Date: Fri, 2 Jun 2023 01:29:11 +0100 Subject: [PATCH 724/907] Add NullBooleanField deprecation to docs (#8999) --- docs/community/3.12-announcement.md | 10 ++++++++++ docs/community/3.14-announcement.md | 10 ++++++++++ docs/community/release-notes.md | 1 + 3 files changed, 21 insertions(+) diff --git a/docs/community/3.12-announcement.md b/docs/community/3.12-announcement.md index 4a589e39c0..3bfeb65763 100644 --- a/docs/community/3.12-announcement.md +++ b/docs/community/3.12-announcement.md @@ -143,6 +143,16 @@ class PublisherSearchView(generics.ListAPIView): --- +## Deprecations + +### `serializers.NullBooleanField` + +`serializers.NullBooleanField` is now pending deprecation, and will be removed in 3.14. + +Instead use `serializers.BooleanField` field and set `allow_null=True` which does the same thing. + +--- + ## Funding REST framework is a *collaboratively funded project*. If you use diff --git a/docs/community/3.14-announcement.md b/docs/community/3.14-announcement.md index 0543d0d6d6..e7b10ede19 100644 --- a/docs/community/3.14-announcement.md +++ b/docs/community/3.14-announcement.md @@ -60,3 +60,13 @@ See Pull Request [#7522](https://github.com/encode/django-rest-framework/pull/75 ## Minor fixes and improvements There are a number of minor fixes and improvements in this release. See the [release notes](release-notes.md) page for a complete listing. + +--- + +## Deprecations + +### `serializers.NullBooleanField` + +`serializers.NullBooleanField` was moved to pending deprecation in 3.12, and deprecated in 3.13. It has now been removed from the core framework. + +Instead use `serializers.BooleanField` field and set `allow_null=True` which does the same thing. diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index 887cae3b47..fba7f63d62 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -157,6 +157,7 @@ Date: 28th September 2020 * Fix `PrimaryKeyRelatedField` and `HyperlinkedRelatedField` when source field is actually a property. [#7142] * `Token.generate_key` is now a class method. [#7502] * `@action` warns if method is wrapped in a decorator that does not preserve information using `@functools.wraps`. [#7098] +* Deprecate `serializers.NullBooleanField` in favour of `serializers.BooleanField` with `allow_null=True` [#7122] --- From 376a5cbbba3f8df9c9db8c03a7c8fa2a6e6c05f4 Mon Sep 17 00:00:00 2001 From: Mathieu Dupuy Date: Sun, 4 Jun 2023 07:24:07 +0200 Subject: [PATCH 725/907] remove dependency on pytz (#8984) * remove pytz * Revert "remove pytz" This reverts commit 393609dfaadbc4d82f760e6e4df8a6de6de24f9b. * remove pytz, more subtly --- rest_framework/fields.py | 12 +++++++++--- setup.py | 2 +- tests/test_fields.py | 22 ++++++++++++++-------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 623e72e0ad..7079e3332c 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -29,7 +29,11 @@ from django.utils.formats import localize_input, sanitize_separators from django.utils.ipv6 import clean_ipv6_address from django.utils.translation import gettext_lazy as _ -from pytz.exceptions import InvalidTimeError + +try: + import pytz +except ImportError: + pytz = None from rest_framework import ISO_8601 from rest_framework.exceptions import ErrorDetail, ValidationError @@ -1148,8 +1152,10 @@ def enforce_timezone(self, value): if not valid_datetime(dt): self.fail('make_aware', timezone=field_timezone) return dt - except InvalidTimeError: - self.fail('make_aware', timezone=field_timezone) + except Exception as e: + if pytz and isinstance(e, pytz.exceptions.InvalidTimeError): + self.fail('make_aware', timezone=field_timezone) + raise e elif (field_timezone is None) and timezone.is_aware(value): return timezone.make_naive(value, datetime.timezone.utc) return value diff --git a/setup.py b/setup.py index 533ffa97f0..6afd5e05e7 100755 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ def get_version(package): author_email='tom@tomchristie.com', # SEE NOTE BELOW (*) packages=find_packages(exclude=['tests*']), include_package_data=True, - install_requires=["django>=3.0", "pytz", 'backports.zoneinfo;python_version<"3.9"'], + install_requires=["django>=3.0", 'backports.zoneinfo;python_version<"3.9"'], python_requires=">=3.6", zip_safe=False, classifiers=[ diff --git a/tests/test_fields.py b/tests/test_fields.py index bcf3884412..50d9d77938 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -9,7 +9,12 @@ from unittest.mock import patch import pytest -import pytz + +try: + import pytz +except ImportError: + pytz = None + from django.core.exceptions import ValidationError as DjangoValidationError from django.db.models import IntegerChoices, TextChoices from django.http import QueryDict @@ -1604,15 +1609,16 @@ class TestPytzNaiveDayLightSavingTimeTimeZoneDateTimeField(FieldValues): } outputs = {} - class MockTimezone(pytz.BaseTzInfo): - @staticmethod - def localize(value, is_dst): - raise pytz.InvalidTimeError() + if pytz: + class MockTimezone(pytz.BaseTzInfo): + @staticmethod + def localize(value, is_dst): + raise pytz.InvalidTimeError() - def __str__(self): - return 'America/New_York' + def __str__(self): + return 'America/New_York' - field = serializers.DateTimeField(default_timezone=MockTimezone()) + field = serializers.DateTimeField(default_timezone=MockTimezone()) @patch('rest_framework.utils.timezone.datetime_ambiguous', return_value=True) From 02d9bfc2ddee0ddf1d6e9cb0e23a52b80fdb4242 Mon Sep 17 00:00:00 2001 From: Niyaz Date: Mon, 12 Jun 2023 16:28:28 +0300 Subject: [PATCH 726/907] Fixes `BrowsableAPIRenderer` for usage with `ListSerializer`. (#7530) Renders list of items in raw_data_form and does not renders form in template while using with `ListSerializer` (`many=True`). --- rest_framework/renderers.py | 6 ++++ tests/test_renderers.py | 59 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 8e8c3a9b3c..0a3b03729d 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -506,6 +506,9 @@ def get_rendered_html_form(self, data, view, method, request): return self.render_form_for_serializer(serializer) def render_form_for_serializer(self, serializer): + if isinstance(serializer, serializers.ListSerializer): + return None + if hasattr(serializer, 'initial_data'): serializer.is_valid() @@ -555,10 +558,13 @@ def get_raw_data_form(self, data, view, method, request): context['indent'] = 4 # strip HiddenField from output + is_list_serializer = isinstance(serializer, serializers.ListSerializer) + serializer = serializer.child if is_list_serializer else serializer data = serializer.data.copy() for name, field in serializer.fields.items(): if isinstance(field, serializers.HiddenField): data.pop(name, None) + data = [data] if is_list_serializer else data content = renderer.render(data, accepted, context) # Renders returns bytes, but CharField expects a str. content = content.decode() diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 71d734c86c..247737576f 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -633,6 +633,9 @@ def list_action(self, request): class AuthExampleViewSet(ExampleViewSet): permission_classes = [permissions.IsAuthenticated] + class SimpleSerializer(serializers.Serializer): + name = serializers.CharField() + router = SimpleRouter() router.register('examples', ExampleViewSet, basename='example') router.register('auth-examples', AuthExampleViewSet, basename='auth-example') @@ -640,6 +643,62 @@ class AuthExampleViewSet(ExampleViewSet): def setUp(self): self.renderer = BrowsableAPIRenderer() + self.renderer.accepted_media_type = '' + self.renderer.renderer_context = {} + + def test_render_form_for_serializer(self): + with self.subTest('Serializer'): + serializer = BrowsableAPIRendererTests.SimpleSerializer(data={'name': 'Name'}) + form = self.renderer.render_form_for_serializer(serializer) + assert isinstance(form, str), 'Must return form for serializer' + + with self.subTest('ListSerializer'): + list_serializer = BrowsableAPIRendererTests.SimpleSerializer(data=[{'name': 'Name'}], many=True) + form = self.renderer.render_form_for_serializer(list_serializer) + assert form is None, 'Must not return form for list serializer' + + def test_get_raw_data_form(self): + with self.subTest('Serializer'): + class DummyGenericViewsetLike(APIView): + def get_serializer(self, **kwargs): + return BrowsableAPIRendererTests.SimpleSerializer(**kwargs) + + def get(self, request): + response = Response() + response.view = self + return response + + post = get + + view = DummyGenericViewsetLike.as_view() + _request = APIRequestFactory().get('/') + request = Request(_request) + response = view(_request) + view = response.view + + raw_data_form = self.renderer.get_raw_data_form({'name': 'Name'}, view, 'POST', request) + assert raw_data_form['_content'].initial == '{\n "name": ""\n}' + + with self.subTest('ListSerializer'): + class DummyGenericViewsetLike(APIView): + def get_serializer(self, **kwargs): + return BrowsableAPIRendererTests.SimpleSerializer(many=True, **kwargs) # returns ListSerializer + + def get(self, request): + response = Response() + response.view = self + return response + + post = get + + view = DummyGenericViewsetLike.as_view() + _request = APIRequestFactory().get('/') + request = Request(_request) + response = view(_request) + view = response.view + + raw_data_form = self.renderer.get_raw_data_form([{'name': 'Name'}], view, 'POST', request) + assert raw_data_form['_content'].initial == '[\n {\n "name": ""\n }\n]' def test_get_description_returns_empty_string_for_401_and_403_statuses(self): assert self.renderer.get_description({}, status_code=401) == '' From a180bde0fd965915718b070932418cabc831cee1 Mon Sep 17 00:00:00 2001 From: Nancy Eckenthal Date: Mon, 12 Jun 2023 11:21:18 -0400 Subject: [PATCH 727/907] Permit mixed casing of string values for BooleanField validation (#8970) * be more permissive of mixed casing in validating strings for BooleanField values * undo unnecessary change * lint --- rest_framework/fields.py | 51 ++++++++++++++++++++++++---------------- tests/test_fields.py | 32 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 20 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 7079e3332c..4ce9c79c3e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -664,22 +664,27 @@ class BooleanField(Field): default_empty_html = False initial = False TRUE_VALUES = { - 't', 'T', - 'y', 'Y', 'yes', 'Yes', 'YES', - 'true', 'True', 'TRUE', - 'on', 'On', 'ON', - '1', 1, - True + 't', + 'y', + 'yes', + 'true', + 'on', + '1', + 1, + True, } FALSE_VALUES = { - 'f', 'F', - 'n', 'N', 'no', 'No', 'NO', - 'false', 'False', 'FALSE', - 'off', 'Off', 'OFF', - '0', 0, 0.0, - False + 'f', + 'n', + 'no', + 'false', + 'off', + '0', + 0, + 0.0, + False, } - NULL_VALUES = {'null', 'Null', 'NULL', '', None} + NULL_VALUES = {'null', '', None} def __init__(self, **kwargs): if kwargs.get('allow_null', False): @@ -687,22 +692,28 @@ def __init__(self, **kwargs): self.initial = None super().__init__(**kwargs) + @staticmethod + def _lower_if_str(value): + if isinstance(value, str): + return value.lower() + return value + def to_internal_value(self, data): with contextlib.suppress(TypeError): - if data in self.TRUE_VALUES: + if self._lower_if_str(data) in self.TRUE_VALUES: return True - elif data in self.FALSE_VALUES: + elif self._lower_if_str(data) in self.FALSE_VALUES: return False - elif data in self.NULL_VALUES and self.allow_null: + elif self._lower_if_str(data) in self.NULL_VALUES and self.allow_null: return None - self.fail('invalid', input=data) + self.fail("invalid", input=data) def to_representation(self, value): - if value in self.TRUE_VALUES: + if self._lower_if_str(value) in self.TRUE_VALUES: return True - elif value in self.FALSE_VALUES: + elif self._lower_if_str(value) in self.FALSE_VALUES: return False - if value in self.NULL_VALUES and self.allow_null: + if self._lower_if_str(value) in self.NULL_VALUES and self.allow_null: return None return bool(value) diff --git a/tests/test_fields.py b/tests/test_fields.py index 50d9d77938..03584431e5 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -695,8 +695,24 @@ class TestBooleanField(FieldValues): Valid and invalid values for `BooleanField`. """ valid_inputs = { + 'True': True, + 'TRUE': True, + 'tRuE': True, + 't': True, + 'T': True, 'true': True, + 'on': True, + 'ON': True, + 'oN': True, + 'False': False, + 'FALSE': False, + 'fALse': False, + 'f': False, + 'F': False, 'false': False, + 'off': False, + 'OFF': False, + 'oFf': False, '1': True, '0': False, 1: True, @@ -709,8 +725,24 @@ class TestBooleanField(FieldValues): None: ['This field may not be null.'] } outputs = { + 'True': True, + 'TRUE': True, + 'tRuE': True, + 't': True, + 'T': True, 'true': True, + 'on': True, + 'ON': True, + 'oN': True, + 'False': False, + 'FALSE': False, + 'fALse': False, + 'f': False, + 'F': False, 'false': False, + 'off': False, + 'OFF': False, + 'oFf': False, '1': True, '0': False, 1: True, From 833313496c8ebbdc3509d87895764c822bfc5dc1 Mon Sep 17 00:00:00 2001 From: Lenno Nagel Date: Tue, 13 Jun 2023 07:27:37 +0300 Subject: [PATCH 728/907] Removed usage of field.choices that triggered full table load (#8950) Removed the `{{ field.choices|yesno:",disabled" }}` block because this triggers the loading of full database table worth of objects just to determine whether the multi-select widget should be set as disabled or not. Since this "disabled" marking feature is not present in the normal select field, then I propose to remove it also from the multi-select. --- .../templates/rest_framework/horizontal/select_multiple.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/templates/rest_framework/horizontal/select_multiple.html b/rest_framework/templates/rest_framework/horizontal/select_multiple.html index 36ff9fd0dc..12e781cc65 100644 --- a/rest_framework/templates/rest_framework/horizontal/select_multiple.html +++ b/rest_framework/templates/rest_framework/horizontal/select_multiple.html @@ -11,7 +11,7 @@ {% endif %}
      - {% for select in field.iter_options %} {% if select.start_option_group %} From a16dbfd11018fa01ceaf6dee7df34ab0430282cf Mon Sep 17 00:00:00 2001 From: David Smith <39445562+smithdc1@users.noreply.github.com> Date: Tue, 13 Jun 2023 07:55:22 +0100 Subject: [PATCH 729/907] Added Deprecation Warnings for CoreAPI (#7519) * Added Deprecation Warnings for CoreAPI * Bumped removal to DRF315 * Update rest_framework/__init__.py * Update rest_framework/filters.py * Update rest_framework/filters.py * Update tests/schemas/test_coreapi.py * Update rest_framework/filters.py * Update rest_framework/filters.py * Update tests/schemas/test_coreapi.py * Update tests/schemas/test_coreapi.py * Update tests/schemas/test_coreapi.py * Update tests/schemas/test_coreapi.py * Update rest_framework/pagination.py * Update rest_framework/pagination.py * Update rest_framework/pagination.py * Update rest_framework/pagination.py * Update rest_framework/schemas/coreapi.py * Update rest_framework/schemas/coreapi.py * Update rest_framework/schemas/coreapi.py * Update rest_framework/schemas/coreapi.py * Update rest_framework/schemas/coreapi.py * Update tests/schemas/test_coreapi.py * Update setup.cfg * Update tests/schemas/test_coreapi.py * Update tests/schemas/test_coreapi.py * Update tests/schemas/test_coreapi.py * Update tests/schemas/test_coreapi.py * Update tests/schemas/test_coreapi.py * Update tests/schemas/test_coreapi.py * Update rest_framework/pagination.py --------- Co-authored-by: Asif Saif Uddin --- rest_framework/__init__.py | 4 +++ rest_framework/filters.py | 8 +++++ rest_framework/pagination.py | 11 +++++++ rest_framework/schemas/coreapi.py | 12 ++++++- setup.cfg | 2 ++ tests/schemas/test_coreapi.py | 55 +++++++++++++++++++++++++++++-- 6 files changed, 89 insertions(+), 3 deletions(-) diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index cc24ce46c5..da7b88dfa2 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -31,3 +31,7 @@ class RemovedInDRF315Warning(DeprecationWarning): pass + + +class RemovedInDRF317Warning(PendingDeprecationWarning): + pass diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 1ffd9edc02..17e6975eb4 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -3,6 +3,7 @@ returned by list views. """ import operator +import warnings from functools import reduce from django.core.exceptions import ImproperlyConfigured @@ -12,6 +13,7 @@ from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ +from rest_framework import RemovedInDRF317Warning from rest_framework.compat import coreapi, coreschema, distinct from rest_framework.settings import api_settings @@ -29,6 +31,8 @@ def filter_queryset(self, request, queryset, view): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' return [] @@ -146,6 +150,8 @@ def to_html(self, request, queryset, view): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' return [ coreapi.Field( @@ -306,6 +312,8 @@ def to_html(self, request, queryset, view): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' return [ coreapi.Field( diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index af508bef6d..ce87785472 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -4,6 +4,8 @@ """ import contextlib +import warnings + from base64 import b64decode, b64encode from collections import namedtuple from urllib import parse @@ -15,6 +17,7 @@ from django.utils.encoding import force_str from django.utils.translation import gettext_lazy as _ +from rest_framework import RemovedInDRF317Warning from rest_framework.compat import coreapi, coreschema from rest_framework.exceptions import NotFound from rest_framework.response import Response @@ -152,6 +155,8 @@ def get_results(self, data): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) return [] def get_schema_operation_parameters(self, view): @@ -311,6 +316,8 @@ def to_html(self): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' fields = [ coreapi.Field( @@ -525,6 +532,8 @@ def get_count(self, queryset): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' return [ coreapi.Field( @@ -930,6 +939,8 @@ def to_html(self): def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to use `get_schema_fields()`' + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) assert coreschema is not None, 'coreschema must be installed to use `get_schema_fields()`' fields = [ coreapi.Field( diff --git a/rest_framework/schemas/coreapi.py b/rest_framework/schemas/coreapi.py index 0713e0cb80..582aba196e 100644 --- a/rest_framework/schemas/coreapi.py +++ b/rest_framework/schemas/coreapi.py @@ -5,7 +5,7 @@ from django.db import models from django.utils.encoding import force_str -from rest_framework import exceptions, serializers +from rest_framework import RemovedInDRF317Warning, exceptions, serializers from rest_framework.compat import coreapi, coreschema, uritemplate from rest_framework.settings import api_settings @@ -118,6 +118,8 @@ class SchemaGenerator(BaseSchemaGenerator): def __init__(self, title=None, url=None, description=None, patterns=None, urlconf=None, version=None): assert coreapi, '`coreapi` must be installed for schema support.' + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) assert coreschema, '`coreschema` must be installed for schema support.' super().__init__(title, url, description, patterns, urlconf) @@ -351,6 +353,9 @@ def __init__(self, manual_fields=None): will be added to auto-generated fields, overwriting on `Field.name` """ super().__init__() + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) + if manual_fields is None: manual_fields = [] self._manual_fields = manual_fields @@ -592,6 +597,9 @@ def __init__(self, fields, description='', encoding=None): * `description`: String description for view. Optional. """ super().__init__() + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) + assert all(isinstance(f, coreapi.Field) for f in fields), "`fields` must be a list of coreapi.Field instances" self._fields = fields self._description = description @@ -613,4 +621,6 @@ def get_link(self, path, method, base_url): def is_enabled(): """Is CoreAPI Mode enabled?""" + if coreapi is not None: + warnings.warn('CoreAPI compatibility is deprecated and will be removed in DRF 3.17', RemovedInDRF317Warning) return issubclass(api_settings.DEFAULT_SCHEMA_CLASS, AutoSchema) diff --git a/setup.cfg b/setup.cfg index 294e9afdd6..487d99db91 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,6 +3,8 @@ license_files = LICENSE.md [tool:pytest] addopts=--tb=short --strict-markers -ra +testspath = tests +filterwarnings = ignore:CoreAPI compatibility is deprecated*:rest_framework.RemovedInDRF317Warning [flake8] ignore = E501,W503,W504 diff --git a/tests/schemas/test_coreapi.py b/tests/schemas/test_coreapi.py index eddc5243ec..98fd46f9fc 100644 --- a/tests/schemas/test_coreapi.py +++ b/tests/schemas/test_coreapi.py @@ -7,16 +7,24 @@ from django.urls import include, path from rest_framework import ( - filters, generics, pagination, permissions, serializers + RemovedInDRF317Warning, filters, generics, pagination, permissions, + serializers ) from rest_framework.compat import coreapi, coreschema from rest_framework.decorators import action, api_view, schema +from rest_framework.filters import ( + BaseFilterBackend, OrderingFilter, SearchFilter +) +from rest_framework.pagination import ( + BasePagination, CursorPagination, LimitOffsetPagination, + PageNumberPagination +) from rest_framework.request import Request from rest_framework.routers import DefaultRouter, SimpleRouter from rest_framework.schemas import ( AutoSchema, ManualSchema, SchemaGenerator, get_schema_view ) -from rest_framework.schemas.coreapi import field_to_schema +from rest_framework.schemas.coreapi import field_to_schema, is_enabled from rest_framework.schemas.generators import EndpointEnumerator from rest_framework.schemas.utils import is_list_view from rest_framework.test import APIClient, APIRequestFactory @@ -1433,3 +1441,46 @@ def test_schema_handles_exception(): response.render() assert response.status_code == 403 assert b"You do not have permission to perform this action." in response.content + + +@pytest.mark.skipif(not coreapi, reason='coreapi is not installed') +def test_coreapi_deprecation(): + with pytest.warns(RemovedInDRF317Warning): + SchemaGenerator() + + with pytest.warns(RemovedInDRF317Warning): + AutoSchema() + + with pytest.warns(RemovedInDRF317Warning): + ManualSchema({}) + + with pytest.warns(RemovedInDRF317Warning): + deprecated_filter = OrderingFilter() + deprecated_filter.get_schema_fields({}) + + with pytest.warns(RemovedInDRF317Warning): + deprecated_filter = BaseFilterBackend() + deprecated_filter.get_schema_fields({}) + + with pytest.warns(RemovedInDRF317Warning): + deprecated_filter = SearchFilter() + deprecated_filter.get_schema_fields({}) + + with pytest.warns(RemovedInDRF317Warning): + paginator = BasePagination() + paginator.get_schema_fields({}) + + with pytest.warns(RemovedInDRF317Warning): + paginator = PageNumberPagination() + paginator.get_schema_fields({}) + + with pytest.warns(RemovedInDRF317Warning): + paginator = LimitOffsetPagination() + paginator.get_schema_fields({}) + + with pytest.warns(RemovedInDRF317Warning): + paginator = CursorPagination() + paginator.get_schema_fields({}) + + with pytest.warns(RemovedInDRF317Warning): + is_enabled() From aed7761a8d7e1691a4f4bbf9c83a447dac44d92a Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Tue, 13 Jun 2023 15:01:29 +0600 Subject: [PATCH 730/907] Update copy right timeline --- rest_framework/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index da7b88dfa2..b9e3f9817c 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -13,7 +13,7 @@ __version__ = '3.14.0' __author__ = 'Tom Christie' __license__ = 'BSD 3-Clause' -__copyright__ = 'Copyright 2011-2019 Encode OSS Ltd' +__copyright__ = 'Copyright 2011-2023 Encode OSS Ltd' # Version synonym VERSION = __version__ From 71f87a586400074f1840276c5cf36fc7da1c2c4c Mon Sep 17 00:00:00 2001 From: Konstantin Kuchkov Date: Wed, 14 Jun 2023 06:24:09 -0700 Subject: [PATCH 731/907] Fix NamespaceVersioning ignoring DEFAULT_VERSION on non-None namespaces (#7278) * Fix the case where if the namespace is not None and there's no match, NamespaceVersioning always raises NotFound even if DEFAULT_VERSION is set or None is in ALLOWED_VERSIONS * Add test cases --- rest_framework/versioning.py | 19 ++++---- tests/test_versioning.py | 93 +++++++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 10 deletions(-) diff --git a/rest_framework/versioning.py b/rest_framework/versioning.py index c2764c7a40..a1c0ce4d7b 100644 --- a/rest_framework/versioning.py +++ b/rest_framework/versioning.py @@ -119,15 +119,16 @@ class NamespaceVersioning(BaseVersioning): def determine_version(self, request, *args, **kwargs): resolver_match = getattr(request, 'resolver_match', None) - if resolver_match is None or not resolver_match.namespace: - return self.default_version - - # Allow for possibly nested namespaces. - possible_versions = resolver_match.namespace.split(':') - for version in possible_versions: - if self.is_allowed_version(version): - return version - raise exceptions.NotFound(self.invalid_version_message) + if resolver_match is not None and resolver_match.namespace: + # Allow for possibly nested namespaces. + possible_versions = resolver_match.namespace.split(':') + for version in possible_versions: + if self.is_allowed_version(version): + return version + + if not self.is_allowed_version(self.default_version): + raise exceptions.NotFound(self.invalid_version_message) + return self.default_version def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra): if request.version is not None: diff --git a/tests/test_versioning.py b/tests/test_versioning.py index b216461840..1ccecae0bf 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -272,7 +272,7 @@ class FakeResolverMatch(ResolverMatch): assert response.status_code == status.HTTP_404_NOT_FOUND -class TestAllowedAndDefaultVersion: +class TestAcceptHeaderAllowedAndDefaultVersion: def test_missing_without_default(self): scheme = versioning.AcceptHeaderVersioning view = AllowedVersionsView.as_view(versioning_class=scheme) @@ -318,6 +318,97 @@ def test_missing_with_default_and_none_allowed(self): assert response.data == {'version': 'v2'} +class TestNamespaceAllowedAndDefaultVersion: + def test_no_namespace_without_default(self): + class FakeResolverMatch: + namespace = None + + scheme = versioning.NamespaceVersioning + view = AllowedVersionsView.as_view(versioning_class=scheme) + + request = factory.get('/endpoint/') + request.resolver_match = FakeResolverMatch + response = view(request) + assert response.status_code == status.HTTP_404_NOT_FOUND + + def test_no_namespace_with_default(self): + class FakeResolverMatch: + namespace = None + + scheme = versioning.NamespaceVersioning + view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme) + + request = factory.get('/endpoint/') + request.resolver_match = FakeResolverMatch + response = view(request) + assert response.status_code == status.HTTP_200_OK + assert response.data == {'version': 'v2'} + + def test_no_match_without_default(self): + class FakeResolverMatch: + namespace = 'no_match' + + scheme = versioning.NamespaceVersioning + view = AllowedVersionsView.as_view(versioning_class=scheme) + + request = factory.get('/endpoint/') + request.resolver_match = FakeResolverMatch + response = view(request) + assert response.status_code == status.HTTP_404_NOT_FOUND + + def test_no_match_with_default(self): + class FakeResolverMatch: + namespace = 'no_match' + + scheme = versioning.NamespaceVersioning + view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme) + + request = factory.get('/endpoint/') + request.resolver_match = FakeResolverMatch + response = view(request) + assert response.status_code == status.HTTP_200_OK + assert response.data == {'version': 'v2'} + + def test_with_default(self): + class FakeResolverMatch: + namespace = 'v1' + + scheme = versioning.NamespaceVersioning + view = AllowedAndDefaultVersionsView.as_view(versioning_class=scheme) + + request = factory.get('/endpoint/') + request.resolver_match = FakeResolverMatch + response = view(request) + assert response.status_code == status.HTTP_200_OK + assert response.data == {'version': 'v1'} + + def test_no_match_without_default_but_none_allowed(self): + class FakeResolverMatch: + namespace = 'no_match' + + scheme = versioning.NamespaceVersioning + view = AllowedWithNoneVersionsView.as_view(versioning_class=scheme) + + request = factory.get('/endpoint/') + request.resolver_match = FakeResolverMatch + response = view(request) + assert response.status_code == status.HTTP_200_OK + assert response.data == {'version': None} + + def test_no_match_with_default_and_none_allowed(self): + class FakeResolverMatch: + namespace = 'no_match' + + scheme = versioning.NamespaceVersioning + view = AllowedWithNoneAndDefaultVersionsView.as_view(versioning_class=scheme) + + request = factory.get('/endpoint/') + request.resolver_match = FakeResolverMatch + response = view(request) + assert response.status_code == status.HTTP_200_OK + assert response.data == {'version': 'v2'} + + class TestHyperlinkedRelatedField(URLPatternsTestCase, APITestCase): included = [ path('namespaced//', dummy_pk_view, name='namespaced'), From 9cfa4bd7cca19df0bc8e456d906c3ab7ce285cf4 Mon Sep 17 00:00:00 2001 From: rizwanshaikh Date: Sat, 17 Jun 2023 08:48:25 +0530 Subject: [PATCH 732/907] Fix OpenAPI Schema yaml rendering for timedelta (#9007) * fix OpenAPIRenderer for timedelta * added test for rendering openapi with timedelta * fix OpenAPIRenderer for timedelta * added test for rendering openapi with timedelta * Removed usage of field.choices that triggered full table load (#8950) Removed the `{{ field.choices|yesno:",disabled" }}` block because this triggers the loading of full database table worth of objects just to determine whether the multi-select widget should be set as disabled or not. Since this "disabled" marking feature is not present in the normal select field, then I propose to remove it also from the multi-select. * Added Deprecation Warnings for CoreAPI (#7519) * Added Deprecation Warnings for CoreAPI * Bumped removal to DRF315 * Update rest_framework/__init__.py * Update rest_framework/filters.py * Update rest_framework/filters.py * Update tests/schemas/test_coreapi.py * Update rest_framework/filters.py * Update rest_framework/filters.py * Update tests/schemas/test_coreapi.py * Update tests/schemas/test_coreapi.py * Update setup.cfg * Update rest_framework/pagination.py --------- Co-authored-by: Asif Saif Uddin * Update copy right timeline * Fix NamespaceVersioning ignoring DEFAULT_VERSION on non-None namespaces (#7278) * Fix the case where if the namespace is not None and there's no match, NamespaceVersioning always raises NotFound even if DEFAULT_VERSION is set or None is in ALLOWED_VERSIONS * Add test cases * fix OpenAPIRenderer for timedelta * added test for rendering openapi with timedelta * added testcase for rendering yaml with minvalidator for duration field (timedelta) --------- Co-authored-by: Rizwan Shaikh Co-authored-by: Lenno Nagel Co-authored-by: David Smith <39445562+smithdc1@users.noreply.github.com> Co-authored-by: Asif Saif Uddin Co-authored-by: Konstantin Kuchkov --- rest_framework/renderers.py | 2 ++ rest_framework/utils/encoders.py | 11 +++++++++++ tests/schemas/test_openapi.py | 25 +++++++++++++++++++++++++ tests/schemas/views.py | 5 +++++ 4 files changed, 43 insertions(+) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 0a3b03729d..db1fdd128b 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -9,6 +9,7 @@ import base64 import contextlib +import datetime from urllib import parse from django import forms @@ -1062,6 +1063,7 @@ class Dumper(yaml.Dumper): def ignore_aliases(self, data): return True Dumper.add_representer(SafeString, Dumper.represent_str) + Dumper.add_representer(datetime.timedelta, encoders.CustomScalar.represent_timedelta) return yaml.dump(data, default_flow_style=False, sort_keys=False, Dumper=Dumper).encode('utf-8') diff --git a/rest_framework/utils/encoders.py b/rest_framework/utils/encoders.py index 35a89eb090..aa45422861 100644 --- a/rest_framework/utils/encoders.py +++ b/rest_framework/utils/encoders.py @@ -65,3 +65,14 @@ def default(self, obj): elif hasattr(obj, '__iter__'): return tuple(item for item in obj) return super().default(obj) + + +class CustomScalar: + """ + CustomScalar that knows how to encode timedelta that renderer + can understand. + """ + @classmethod + def represent_timedelta(cls, dumper, data): + value = str(data.total_seconds()) + return dumper.represent_scalar('tag:yaml.org,2002:str', value) diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 0ea6d1ff92..1eb5b84b71 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -1162,6 +1162,31 @@ def test_schema_rendering_to_json(self): assert b'"openapi": "' in ret assert b'"default": "0.0"' in ret + def test_schema_rendering_to_yaml(self): + patterns = [ + path('example/', views.ExampleGenericAPIView.as_view()), + ] + generator = SchemaGenerator(patterns=patterns) + + request = create_request('/') + schema = generator.get_schema(request=request) + ret = OpenAPIRenderer().render(schema) + assert b"openapi: " in ret + assert b"default: '0.0'" in ret + + def test_schema_rendering_timedelta_to_yaml_with_validator(self): + + patterns = [ + path('example/', views.ExampleValidatedAPIView.as_view()), + ] + generator = SchemaGenerator(patterns=patterns) + + request = create_request('/') + schema = generator.get_schema(request=request) + ret = OpenAPIRenderer().render(schema) + assert b"openapi: " in ret + assert b"duration:\n type: string\n minimum: \'10.0\'\n" in ret + def test_schema_with_no_paths(self): patterns = [] generator = SchemaGenerator(patterns=patterns) diff --git a/tests/schemas/views.py b/tests/schemas/views.py index f1ed0bd4e3..c08208bf26 100644 --- a/tests/schemas/views.py +++ b/tests/schemas/views.py @@ -134,6 +134,11 @@ class ExampleValidatedSerializer(serializers.Serializer): ip4 = serializers.IPAddressField(protocol='ipv4') ip6 = serializers.IPAddressField(protocol='ipv6') ip = serializers.IPAddressField() + duration = serializers.DurationField( + validators=( + MinValueValidator(timedelta(seconds=10)), + ) + ) class ExampleValidatedAPIView(generics.GenericAPIView): From 8b7e6f2e3405d6e71488db53f2dda515d5a336c6 Mon Sep 17 00:00:00 2001 From: Samiul Sk Date: Tue, 20 Jun 2023 17:13:33 +0530 Subject: [PATCH 733/907] Update pre-commit.yml (#9012) --- .github/workflows/pre-commit.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 9c29ed0564..36d3564939 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -8,17 +8,17 @@ on: jobs: pre-commit: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: actions/setup-python@v2 + - uses: actions/setup-python@v4 with: - python-version: 3.9 + python-version: "3.10" - - uses: pre-commit/action@v2.0.0 + - uses: pre-commit/action@v3.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} From 8dd4250d0234ddf6c6a19a806639678ef7786468 Mon Sep 17 00:00:00 2001 From: Samiul Sk Date: Wed, 21 Jun 2023 10:35:44 +0530 Subject: [PATCH 734/907] remove unnecessary line which was causing isort error (#9014) --- rest_framework/pagination.py | 1 - 1 file changed, 1 deletion(-) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index ce87785472..7303890b03 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -5,7 +5,6 @@ import contextlib import warnings - from base64 import b64decode, b64encode from collections import namedtuple from urllib import parse From 4f7e9ed3bbb0fb3567884ef4e3d340bc2f77bc68 Mon Sep 17 00:00:00 2001 From: Andrew <54764942+drewg3r@users.noreply.github.com> Date: Sun, 2 Jul 2023 09:57:20 +0200 Subject: [PATCH 735/907] Fix SearchFilter renders field with invalid value (#9023) Co-authored-by: Andrii Tarasenko --- rest_framework/filters.py | 4 +--- tests/test_filters.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 17e6975eb4..c48504957c 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -139,11 +139,9 @@ def to_html(self, request, queryset, view): if not getattr(view, 'search_fields', None): return '' - term = self.get_search_terms(request) - term = term[0] if term else '' context = { 'param': self.search_param, - 'term': term + 'term': request.query_params.get(self.search_param, ''), } template = loader.get_template(self.template) return template.render(context) diff --git a/tests/test_filters.py b/tests/test_filters.py index 37ae4c7cf3..2a22e30f97 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -225,6 +225,23 @@ def as_sql(self, compiler, connection): {'id': 2, 'title': 'zz', 'text': 'bcd'}, ] + def test_search_field_with_multiple_words(self): + class SearchListView(generics.ListAPIView): + queryset = SearchFilterModel.objects.all() + serializer_class = SearchFilterSerializer + filter_backends = (filters.SearchFilter,) + search_fields = ('title', 'text') + + search_query = 'foo bar,baz' + view = SearchListView() + request = factory.get('/', {'search': search_query}) + request = view.initialize_request(request) + + rendered_search_field = filters.SearchFilter().to_html( + request=request, queryset=view.queryset, view=view + ) + assert search_query in rendered_search_field + class AttributeModel(models.Model): label = models.CharField(max_length=32) From 66d86d0177673b90723664e3fde2919df66f2b60 Mon Sep 17 00:00:00 2001 From: Burson Date: Thu, 13 Jul 2023 20:50:49 +0800 Subject: [PATCH 736/907] Fix choices in ChoiceField to support IntEnum (#8955) Python support Enum in version 3.4, but changed __str__ to int.__str__ until version 3.11 to better support the replacement of existing constants use-case. [https://docs.python.org/3/library/enum.html#enum.IntEnum](https://docs.python.org/3/library/enum.html#enum.IntEnum) rest_frame work support Python 3.6+, this commit will support the Enum in choices of Field. --- rest_framework/fields.py | 17 ++++++----------- tests/test_fields.py | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 4ce9c79c3e..0b56fa7fb6 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -8,6 +8,7 @@ import re import uuid from collections.abc import Mapping +from enum import Enum from django.conf import settings from django.core.exceptions import ObjectDoesNotExist @@ -17,7 +18,6 @@ MinValueValidator, ProhibitNullCharactersValidator, RegexValidator, URLValidator, ip_address_validators ) -from django.db.models import IntegerChoices, TextChoices from django.forms import FilePathField as DjangoFilePathField from django.forms import ImageField as DjangoImageField from django.utils import timezone @@ -1401,11 +1401,8 @@ def __init__(self, choices, **kwargs): def to_internal_value(self, data): if data == '' and self.allow_blank: return '' - - if isinstance(data, (IntegerChoices, TextChoices)) and str(data) != \ - str(data.value): + if isinstance(data, Enum) and str(data) != str(data.value): data = data.value - try: return self.choice_strings_to_values[str(data)] except KeyError: @@ -1414,11 +1411,8 @@ def to_internal_value(self, data): def to_representation(self, value): if value in ('', None): return value - - if isinstance(value, (IntegerChoices, TextChoices)) and str(value) != \ - str(value.value): + if isinstance(value, Enum) and str(value) != str(value.value): value = value.value - return self.choice_strings_to_values.get(str(value), value) def iter_options(self): @@ -1442,8 +1436,7 @@ def _set_choices(self, choices): # Allows us to deal with eg. integer choices while supporting either # integer or string input, but still get the correct datatype out. self.choice_strings_to_values = { - str(key.value) if isinstance(key, (IntegerChoices, TextChoices)) - and str(key) != str(key.value) else str(key): key for key in self.choices + str(key.value) if isinstance(key, Enum) and str(key) != str(key.value) else str(key): key for key in self.choices } choices = property(_get_choices, _set_choices) @@ -1829,6 +1822,7 @@ class HiddenField(Field): constraint on a pair of fields, as we need some way to include the date in the validated data. """ + def __init__(self, **kwargs): assert 'default' in kwargs, 'default is a required argument.' kwargs['write_only'] = True @@ -1858,6 +1852,7 @@ class ExampleSerializer(Serializer): def get_extra_info(self, obj): return ... # Calculate some data to return. """ + def __init__(self, method_name=None, **kwargs): self.method_name = method_name kwargs['source'] = '*' diff --git a/tests/test_fields.py b/tests/test_fields.py index 03584431e5..7006d473c2 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1875,6 +1875,31 @@ def test_edit_choices(self): field.run_validation(2) assert exc_info.value.detail == ['"2" is not a valid choice.'] + def test_enum_integer_choices(self): + from enum import IntEnum + + class ChoiceCase(IntEnum): + first = auto() + second = auto() + # Enum validate + choices = [ + (ChoiceCase.first, "1"), + (ChoiceCase.second, "2") + ] + field = serializers.ChoiceField(choices=choices) + assert field.run_validation(1) == 1 + assert field.run_validation(ChoiceCase.first) == 1 + assert field.run_validation("1") == 1 + # Enum.value validate + choices = [ + (ChoiceCase.first.value, "1"), + (ChoiceCase.second.value, "2") + ] + field = serializers.ChoiceField(choices=choices) + assert field.run_validation(1) == 1 + assert field.run_validation(ChoiceCase.first) == 1 + assert field.run_validation("1") == 1 + def test_integer_choices(self): class ChoiceCase(IntegerChoices): first = auto() From 7658ffd71d4ad07ddada20b2b9538b889ec02403 Mon Sep 17 00:00:00 2001 From: Sergey Klyuykov Date: Sat, 15 Jul 2023 02:26:56 -0700 Subject: [PATCH 737/907] Fix: Pagination response schemas. (#9049) --- rest_framework/pagination.py | 3 +++ tests/test_pagination.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 7303890b03..2b20e76af5 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -239,6 +239,7 @@ def get_paginated_response(self, data): def get_paginated_response_schema(self, schema): return { 'type': 'object', + 'required': ['count', 'results'], 'properties': { 'count': { 'type': 'integer', @@ -411,6 +412,7 @@ def get_paginated_response(self, data): def get_paginated_response_schema(self, schema): return { 'type': 'object', + 'required': ['count', 'results'], 'properties': { 'count': { 'type': 'integer', @@ -906,6 +908,7 @@ def get_paginated_response(self, data): def get_paginated_response_schema(self, schema): return { 'type': 'object', + 'required': ['results'], 'properties': { 'next': { 'type': 'string', diff --git a/tests/test_pagination.py b/tests/test_pagination.py index d606986ab7..090eb0d813 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -274,6 +274,7 @@ def test_get_paginated_response_schema(self): assert self.pagination.get_paginated_response_schema(unpaginated_schema) == { 'type': 'object', + 'required': ['count', 'results'], 'properties': { 'count': { 'type': 'integer', @@ -585,6 +586,7 @@ def test_get_paginated_response_schema(self): assert self.pagination.get_paginated_response_schema(unpaginated_schema) == { 'type': 'object', + 'required': ['count', 'results'], 'properties': { 'count': { 'type': 'integer', @@ -937,6 +939,7 @@ def test_get_paginated_response_schema(self): assert self.pagination.get_paginated_response_schema(unpaginated_schema) == { 'type': 'object', + 'required': ['results'], 'properties': { 'next': { 'type': 'string', From 5c3b6e496c9892463f48f6b50cf9a0f1d2c29e78 Mon Sep 17 00:00:00 2001 From: Amin Aminian <47900904+aminiun@users.noreply.github.com> Date: Tue, 25 Jul 2023 09:51:25 +0200 Subject: [PATCH 738/907] class name added to unkown field error (#9019) --- rest_framework/serializers.py | 4 ++-- tests/test_model_serializer.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 56fa918dc8..6ee75fbc1f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -1372,8 +1372,8 @@ def build_unknown_field(self, field_name, model_class): Raise an error on any unknown fields. """ raise ImproperlyConfigured( - 'Field name `%s` is not valid for model `%s`.' % - (field_name, model_class.__name__) + 'Field name `%s` is not valid for model `%s` in `%s.%s`.' % + (field_name, model_class.__name__, self.__class__.__module__, self.__class__.__name__) ) def include_extra_kwargs(self, kwargs, extra_kwargs): diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index c5ac888f55..e2d4bbc30c 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -315,7 +315,8 @@ class Meta: model = RegularFieldsModel fields = ('auto_field', 'invalid') - expected = 'Field name `invalid` is not valid for model `RegularFieldsModel`.' + expected = 'Field name `invalid` is not valid for model `RegularFieldsModel` ' \ + 'in `tests.test_model_serializer.TestSerializer`.' with self.assertRaisesMessage(ImproperlyConfigured, expected): TestSerializer().fields From b99df0cf780adc3d65362a4425f9bb6d85410bcb Mon Sep 17 00:00:00 2001 From: Devid Date: Tue, 25 Jul 2023 14:01:23 +0100 Subject: [PATCH 739/907] Align SearchFilter behaviour to django.contrib.admin search (#9017) * Use subquery to remove duplicates in SearchFilter * Align SearchFilter behaviour to django.contrib.admin * Add compatibility with older django/python versions * Allow search to split also by comma after smart split * Use generator to build search conditions to reduce iterations * Improve search documentation * Update docs/api-guide/filtering.md --------- Co-authored-by: Asif Saif Uddin --- docs/api-guide/filtering.md | 18 ++++---- rest_framework/compat.py | 8 ---- rest_framework/filters.py | 83 +++++++++++++++++++++++++++---------- tests/test_filters.py | 77 ++++++++++++++++++++++++++++++++-- 4 files changed, 146 insertions(+), 40 deletions(-) diff --git a/docs/api-guide/filtering.md b/docs/api-guide/filtering.md index 47ea8592df..ff5f3c7759 100644 --- a/docs/api-guide/filtering.md +++ b/docs/api-guide/filtering.md @@ -213,19 +213,23 @@ This will allow the client to filter the items in the list by making queries suc You can also perform a related lookup on a ForeignKey or ManyToManyField with the lookup API double-underscore notation: search_fields = ['username', 'email', 'profile__profession'] - + For [JSONField][JSONField] and [HStoreField][HStoreField] fields you can filter based on nested values within the data structure using the same double-underscore notation: search_fields = ['data__breed', 'data__owner__other_pets__0__name'] -By default, searches will use case-insensitive partial matches. The search parameter may contain multiple search terms, which should be whitespace and/or comma separated. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched. +By default, searches will use case-insensitive partial matches. The search parameter may contain multiple search terms, which should be whitespace and/or comma separated. If multiple search terms are used then objects will be returned in the list only if all the provided terms are matched. Searches may contain _quoted phrases_ with spaces, each phrase is considered as a single search term. + -The search behavior may be restricted by prepending various characters to the `search_fields`. +The search behavior may be specified by prefixing field names in `search_fields` with one of the following characters (which is equivalent to adding `__` to the field): -* '^' Starts-with search. -* '=' Exact matches. -* '@' Full-text search. (Currently only supported Django's [PostgreSQL backend][postgres-search].) -* '$' Regex search. +| Prefix | Lookup | | +| ------ | --------------| ------------------ | +| `^` | `istartswith` | Starts-with search.| +| `=` | `iexact` | Exact matches. | +| `$` | `iregex` | Regex search. | +| `@` | `search` | Full-text search (Currently only supported Django's [PostgreSQL backend][postgres-search]). | +| None | `icontains` | Contains search (Default). | For example: diff --git a/rest_framework/compat.py b/rest_framework/compat.py index ac5cbc572a..7e80704e11 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -3,7 +3,6 @@ versions of Django/Python, and compatibility wrappers around optional packages. """ import django -from django.conf import settings from django.views.generic import View @@ -14,13 +13,6 @@ def unicode_http_header(value): return value -def distinct(queryset, base): - if settings.DATABASES[queryset.db]["ENGINE"] == "django.db.backends.oracle": - # distinct analogue for Oracle users - return base.filter(pk__in=set(queryset.values_list('pk', flat=True))) - return queryset.distinct() - - # django.contrib.postgres requires psycopg2 try: from django.contrib.postgres import fields as postgres_fields diff --git a/rest_framework/filters.py b/rest_framework/filters.py index c48504957c..065e72f94a 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -6,18 +6,35 @@ import warnings from functools import reduce -from django.core.exceptions import ImproperlyConfigured +from django.core.exceptions import FieldDoesNotExist, ImproperlyConfigured from django.db import models from django.db.models.constants import LOOKUP_SEP from django.template import loader from django.utils.encoding import force_str +from django.utils.text import smart_split, unescape_string_literal from django.utils.translation import gettext_lazy as _ from rest_framework import RemovedInDRF317Warning -from rest_framework.compat import coreapi, coreschema, distinct +from rest_framework.compat import coreapi, coreschema +from rest_framework.fields import CharField from rest_framework.settings import api_settings +def search_smart_split(search_terms): + """generator that first splits string by spaces, leaving quoted phrases togheter, + then it splits non-quoted phrases by commas. + """ + for term in smart_split(search_terms): + # trim commas to avoid bad matching for quoted phrases + term = term.strip(',') + if term.startswith(('"', "'")) and term[0] == term[-1]: + # quoted phrases are kept togheter without any other split + yield unescape_string_literal(term) + else: + # non-quoted tokens are split by comma, keeping only non-empty ones + yield from (sub_term.strip() for sub_term in term.split(',') if sub_term) + + class BaseFilterBackend: """ A base class from which all filter backend classes should inherit. @@ -64,18 +81,41 @@ def get_search_fields(self, view, request): def get_search_terms(self, request): """ Search terms are set by a ?search=... query parameter, - and may be comma and/or whitespace delimited. + and may be whitespace delimited. """ - params = request.query_params.get(self.search_param, '') - params = params.replace('\x00', '') # strip null characters - params = params.replace(',', ' ') - return params.split() + value = request.query_params.get(self.search_param, '') + field = CharField(trim_whitespace=False, allow_blank=True) + return field.run_validation(value) - def construct_search(self, field_name): + def construct_search(self, field_name, queryset): lookup = self.lookup_prefixes.get(field_name[0]) if lookup: field_name = field_name[1:] else: + # Use field_name if it includes a lookup. + opts = queryset.model._meta + lookup_fields = field_name.split(LOOKUP_SEP) + # Go through the fields, following all relations. + prev_field = None + for path_part in lookup_fields: + if path_part == "pk": + path_part = opts.pk.name + try: + field = opts.get_field(path_part) + except FieldDoesNotExist: + # Use valid query lookups. + if prev_field and prev_field.get_lookup(path_part): + return field_name + else: + prev_field = field + if hasattr(field, "path_infos"): + # Update opts to follow the relation. + opts = field.path_infos[-1].to_opts + # django < 4.1 + elif hasattr(field, 'get_path_info'): + # Update opts to follow the relation. + opts = field.get_path_info()[-1].to_opts + # Otherwise, use the field with icontains. lookup = 'icontains' return LOOKUP_SEP.join([field_name, lookup]) @@ -113,26 +153,27 @@ def filter_queryset(self, request, queryset, view): return queryset orm_lookups = [ - self.construct_search(str(search_field)) + self.construct_search(str(search_field), queryset) for search_field in search_fields ] base = queryset - conditions = [] - for search_term in search_terms: - queries = [ - models.Q(**{orm_lookup: search_term}) - for orm_lookup in orm_lookups - ] - conditions.append(reduce(operator.or_, queries)) + # generator which for each term builds the corresponding search + conditions = ( + reduce( + operator.or_, + (models.Q(**{orm_lookup: term}) for orm_lookup in orm_lookups) + ) for term in search_smart_split(search_terms) + ) queryset = queryset.filter(reduce(operator.and_, conditions)) + # Remove duplicates from results, if necessary if self.must_call_distinct(queryset, search_fields): - # Filtering against a many-to-many field requires us to - # call queryset.distinct() in order to avoid duplicate items - # in the resulting queryset. - # We try to avoid this if possible, for performance reasons. - queryset = distinct(queryset, base) + # inspired by django.contrib.admin + # this is more accurate than .distinct form M2M relationship + # also is cross-database + queryset = queryset.filter(pk=models.OuterRef('pk')) + queryset = base.filter(models.Exists(queryset)) return queryset def to_html(self, request, queryset, view): diff --git a/tests/test_filters.py b/tests/test_filters.py index 2a22e30f97..6db0c3deb2 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -6,16 +6,36 @@ from django.db import models from django.db.models import CharField, Transform from django.db.models.functions import Concat, Upper -from django.test import TestCase +from django.test import SimpleTestCase, TestCase from django.test.utils import override_settings from rest_framework import filters, generics, serializers from rest_framework.compat import coreschema +from rest_framework.exceptions import ValidationError from rest_framework.test import APIRequestFactory factory = APIRequestFactory() +class SearchSplitTests(SimpleTestCase): + + def test_keep_quoted_togheter_regardless_of_commas(self): + assert ['hello, world'] == list(filters.search_smart_split('"hello, world"')) + + def test_strips_commas_around_quoted(self): + assert ['hello, world'] == list(filters.search_smart_split(',,"hello, world"')) + assert ['hello, world'] == list(filters.search_smart_split(',,"hello, world",,')) + assert ['hello, world'] == list(filters.search_smart_split('"hello, world",,')) + + def test_splits_by_comma(self): + assert ['hello', 'world'] == list(filters.search_smart_split(',,hello, world')) + assert ['hello', 'world'] == list(filters.search_smart_split(',,hello, world,,')) + assert ['hello', 'world'] == list(filters.search_smart_split('hello, world,,')) + + def test_splits_quotes_followed_by_comma_and_sentence(self): + assert ['"hello', 'world"', 'found'] == list(filters.search_smart_split('"hello, world",found')) + + class BaseFilterTests(TestCase): def setUp(self): self.original_coreapi = filters.coreapi @@ -50,7 +70,8 @@ class Meta: class SearchFilterTests(TestCase): - def setUp(self): + @classmethod + def setUpTestData(cls): # Sequence of title/text is: # # z abc @@ -66,6 +87,9 @@ def setUp(self): ) SearchFilterModel(title=title, text=text).save() + SearchFilterModel(title='A title', text='The long text').save() + SearchFilterModel(title='The title', text='The "text').save() + def test_search(self): class SearchListView(generics.ListAPIView): queryset = SearchFilterModel.objects.all() @@ -186,9 +210,21 @@ def test_search_field_with_null_characters(self): request = factory.get('/?search=\0as%00d\x00f') request = view.initialize_request(request) - terms = filters.SearchFilter().get_search_terms(request) + with self.assertRaises(ValidationError): + filters.SearchFilter().get_search_terms(request) - assert terms == ['asdf'] + def test_search_field_with_custom_lookup(self): + class SearchListView(generics.ListAPIView): + queryset = SearchFilterModel.objects.all() + serializer_class = SearchFilterSerializer + filter_backends = (filters.SearchFilter,) + search_fields = ('text__iendswith',) + view = SearchListView.as_view() + request = factory.get('/', {'search': 'c'}) + response = view(request) + assert response.data == [ + {'id': 1, 'title': 'z', 'text': 'abc'}, + ] def test_search_field_with_additional_transforms(self): from django.test.utils import register_lookup @@ -242,6 +278,32 @@ class SearchListView(generics.ListAPIView): ) assert search_query in rendered_search_field + def test_search_field_with_escapes(self): + class SearchListView(generics.ListAPIView): + queryset = SearchFilterModel.objects.all() + serializer_class = SearchFilterSerializer + filter_backends = (filters.SearchFilter,) + search_fields = ('title', 'text',) + view = SearchListView.as_view() + request = factory.get('/', {'search': '"\\\"text"'}) + response = view(request) + assert response.data == [ + {'id': 12, 'title': 'The title', 'text': 'The "text'}, + ] + + def test_search_field_with_quotes(self): + class SearchListView(generics.ListAPIView): + queryset = SearchFilterModel.objects.all() + serializer_class = SearchFilterSerializer + filter_backends = (filters.SearchFilter,) + search_fields = ('title', 'text',) + view = SearchListView.as_view() + request = factory.get('/', {'search': '"long text"'}) + response = view(request) + assert response.data == [ + {'id': 11, 'title': 'A title', 'text': 'The long text'}, + ] + class AttributeModel(models.Model): label = models.CharField(max_length=32) @@ -284,6 +346,13 @@ def test_must_call_distinct_restores_meta_for_each_field(self): ["%sattribute__label" % prefix, "%stitle" % prefix] ) + def test_custom_lookup_to_related_model(self): + # In this test case the attribute of the fk model comes first in the + # list of search fields. + filter_ = filters.SearchFilter() + assert 'attribute__label__icontains' == filter_.construct_search('attribute__label', SearchFilterModelFk._meta) + assert 'attribute__label__iendswith' == filter_.construct_search('attribute__label__iendswith', SearchFilterModelFk._meta) + class SearchFilterModelM2M(models.Model): title = models.CharField(max_length=20) From 589b5dca9e7613f7742af8baed6ed870476dd23b Mon Sep 17 00:00:00 2001 From: Pierre Chiquet Date: Wed, 26 Jul 2023 06:27:49 +0200 Subject: [PATCH 740/907] Allow to override child.run_validation call in ListSerializer (#8035) * Separated run_child_validation method in ListSerializer * fix typo * Add test_update_allow_custom_child_validation --------- Co-authored-by: Pierre Chiquet --- rest_framework/serializers.py | 13 +++++++- tests/test_serializer_lists.py | 57 +++++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 6ee75fbc1f..77c181b6cc 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -653,6 +653,17 @@ def run_validation(self, data=empty): return value + def run_child_validation(self, data): + """ + Run validation on child serializer. + You may need to override this method to support multiple updates. For example: + + self.child.instance = self.instance.get(pk=data['id']) + self.child.initial_data = data + return super().run_child_validation(data) + """ + return self.child.run_validation(data) + def to_internal_value(self, data): """ List of dicts of native values <- List of dicts of primitive datatypes. @@ -697,7 +708,7 @@ def to_internal_value(self, data): ): self.child.instance = self.instance[idx] try: - validated = self.child.run_validation(item) + validated = self.run_child_validation(item) except ValidationError as exc: errors.append(exc.detail) else: diff --git a/tests/test_serializer_lists.py b/tests/test_serializer_lists.py index 10463d29ab..4070de7a51 100644 --- a/tests/test_serializer_lists.py +++ b/tests/test_serializer_lists.py @@ -153,6 +153,61 @@ def test_validate_html_input(self): assert serializer.is_valid() assert serializer.validated_data == expected_output + def test_update_allow_custom_child_validation(self): + """ + Update a list of objects thanks custom run_child_validation implementation. + """ + + class TestUpdateSerializer(serializers.Serializer): + integer = serializers.IntegerField() + boolean = serializers.BooleanField() + + def update(self, instance, validated_data): + instance._data.update(validated_data) + return instance + + def validate(self, data): + # self.instance is set to current BasicObject instance + assert isinstance(self.instance, BasicObject) + # self.initial_data is current dictionary + assert isinstance(self.initial_data, dict) + assert self.initial_data["pk"] == self.instance.pk + return super().validate(data) + + class ListUpdateSerializer(serializers.ListSerializer): + child = TestUpdateSerializer() + + def run_child_validation(self, data): + # find related instance in self.instance list + child_instance = next(o for o in self.instance if o.pk == data["pk"]) + # set instance and initial_data for child serializer + self.child.instance = child_instance + self.child.initial_data = data + return super().run_child_validation(data) + + def update(self, instance, validated_data): + return [ + self.child.update(instance, attrs) + for instance, attrs in zip(self.instance, validated_data) + ] + + instance = [ + BasicObject(pk=1, integer=11, private_field="a"), + BasicObject(pk=2, integer=22, private_field="b"), + ] + input_data = [ + {"pk": 1, "integer": "123", "boolean": "true"}, + {"pk": 2, "integer": "456", "boolean": "false"}, + ] + expected_output = [ + BasicObject(pk=1, integer=123, boolean=True, private_field="a"), + BasicObject(pk=2, integer=456, boolean=False, private_field="b"), + ] + serializer = ListUpdateSerializer(instance, data=input_data) + assert serializer.is_valid() + updated_instances = serializer.save() + assert updated_instances == expected_output + class TestNestedListSerializer: """ @@ -481,7 +536,7 @@ class Serializer(serializers.Serializer): assert serializer.validated_data == {} assert serializer.errors == {} - def test_udate_as_field_allow_empty_true(self): + def test_update_as_field_allow_empty_true(self): class ListSerializer(serializers.Serializer): update_field = serializers.IntegerField() store_field = serializers.IntegerField() From 7a9db57eaf829367601fbaac5f1811d1fcb712fb Mon Sep 17 00:00:00 2001 From: rizwanshaikh Date: Mon, 7 Aug 2023 12:05:54 +0530 Subject: [PATCH 741/907] Propagate 'default' from model_field to serializer field. (#9030) * Propagate 'default' from model_field to serializer field Fix #7469. Co-authored-by: Nikhil Benesch * updated field default on serializer according to openapi generation and added that to options action response * added notes regarding default value propagation from model to serializer field * updated note * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md * Update docs/api-guide/fields.md --------- Co-authored-by: John Parton Co-authored-by: Nikhil Benesch Co-authored-by: Rizwan Shaikh Co-authored-by: Asif Saif Uddin --- docs/api-guide/fields.md | 8 ++ rest_framework/metadata.py | 4 + rest_framework/utils/field_mapping.py | 4 + tests/test_metadata.py | 129 ++++++++++++++++++++++++++ tests/test_model_serializer.py | 6 +- 5 files changed, 148 insertions(+), 3 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 4ac3e3c31a..7c27162187 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -68,6 +68,14 @@ When serializing the instance, default will be used if the object attribute or d Note that setting a `default` value implies that the field is not required. Including both the `default` and `required` keyword arguments is invalid and will raise an error. +Notes regarding default value propagation from model to serializer: + +All the default values from model will pass as default to the serializer and the options method. + +If the default is callable then it will be propagated to & evaluated every time in the serializer but not in options method. + +If the value for given field is not given then default value will be present in the serializer and available in serializer's methods. Specified validation on given field will be evaluated on default value as that field will be present in the serializer. + ### `allow_null` Normally an error will be raised if `None` is passed to a serializer field. Set this keyword argument to `True` if `None` should be considered a valid value. diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py index 364ca5b14d..fd0f4e163d 100644 --- a/rest_framework/metadata.py +++ b/rest_framework/metadata.py @@ -11,6 +11,7 @@ from django.utils.encoding import force_str from rest_framework import exceptions, serializers +from rest_framework.fields import empty from rest_framework.request import clone_request from rest_framework.utils.field_mapping import ClassLookupDict @@ -149,4 +150,7 @@ def get_field_info(self, field): for choice_value, choice_name in field.choices.items() ] + if getattr(field, 'default', None) and field.default != empty and not callable(field.default): + field_info['default'] = field.default + return field_info diff --git a/rest_framework/utils/field_mapping.py b/rest_framework/utils/field_mapping.py index fc63f96fe0..30bb65e0cf 100644 --- a/rest_framework/utils/field_mapping.py +++ b/rest_framework/utils/field_mapping.py @@ -9,6 +9,7 @@ from django.utils.text import capfirst from rest_framework.compat import postgres_fields +from rest_framework.fields import empty from rest_framework.validators import UniqueValidator NUMERIC_FIELD_TYPES = ( @@ -127,6 +128,9 @@ def get_field_kwargs(field_name, model_field): kwargs['read_only'] = True return kwargs + if model_field.default is not None and model_field.default != empty and not callable(model_field.default): + kwargs['default'] = model_field.default + if model_field.has_default() or model_field.blank or model_field.null: kwargs['required'] = False diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 1bdc8697c4..387b9ecded 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -184,6 +184,135 @@ def get_serializer(self): assert response.status_code == status.HTTP_200_OK assert response.data == expected + def test_actions_with_default(self): + """ + On generic views OPTIONS should return an 'actions' key with metadata + on the fields with default that may be supplied to PUT and POST requests. + """ + class NestedField(serializers.Serializer): + a = serializers.IntegerField(default=2) + b = serializers.IntegerField() + + class ExampleSerializer(serializers.Serializer): + choice_field = serializers.ChoiceField(['red', 'green', 'blue'], default='red') + integer_field = serializers.IntegerField( + min_value=1, max_value=1000, default=1 + ) + char_field = serializers.CharField( + min_length=3, max_length=40, default="example" + ) + list_field = serializers.ListField( + child=serializers.ListField( + child=serializers.IntegerField(default=1) + ) + ) + nested_field = NestedField() + uuid_field = serializers.UUIDField(label="UUID field") + + class ExampleView(views.APIView): + """Example view.""" + def post(self, request): + pass + + def get_serializer(self): + return ExampleSerializer() + + view = ExampleView.as_view() + response = view(request=request) + expected = { + 'name': 'Example', + 'description': 'Example view.', + 'renders': [ + 'application/json', + 'text/html' + ], + 'parses': [ + 'application/json', + 'application/x-www-form-urlencoded', + 'multipart/form-data' + ], + 'actions': { + 'POST': { + 'choice_field': { + 'type': 'choice', + 'required': False, + 'read_only': False, + 'label': 'Choice field', + "choices": [ + {'value': 'red', 'display_name': 'red'}, + {'value': 'green', 'display_name': 'green'}, + {'value': 'blue', 'display_name': 'blue'} + ], + 'default': 'red' + }, + 'integer_field': { + 'type': 'integer', + 'required': False, + 'read_only': False, + 'label': 'Integer field', + 'min_value': 1, + 'max_value': 1000, + 'default': 1 + }, + 'char_field': { + 'type': 'string', + 'required': False, + 'read_only': False, + 'label': 'Char field', + 'min_length': 3, + 'max_length': 40, + 'default': 'example' + }, + 'list_field': { + 'type': 'list', + 'required': True, + 'read_only': False, + 'label': 'List field', + 'child': { + 'type': 'list', + 'required': True, + 'read_only': False, + 'child': { + 'type': 'integer', + 'required': False, + 'read_only': False, + 'default': 1 + } + } + }, + 'nested_field': { + 'type': 'nested object', + 'required': True, + 'read_only': False, + 'label': 'Nested field', + 'children': { + 'a': { + 'type': 'integer', + 'required': False, + 'read_only': False, + 'label': 'A', + 'default': 2 + }, + 'b': { + 'type': 'integer', + 'required': True, + 'read_only': False, + 'label': 'B' + } + } + }, + 'uuid_field': { + 'type': 'string', + 'required': True, + 'read_only': False, + 'label': 'UUID field' + } + } + } + } + assert response.status_code == status.HTTP_200_OK + assert response.data == expected + def test_global_permissions(self): """ If a user does not have global permissions on an action, then any diff --git a/tests/test_model_serializer.py b/tests/test_model_serializer.py index e2d4bbc30c..20d0319fcb 100644 --- a/tests/test_model_serializer.py +++ b/tests/test_model_serializer.py @@ -173,7 +173,7 @@ class Meta: TestSerializer(): auto_field = IntegerField(read_only=True) big_integer_field = IntegerField() - boolean_field = BooleanField(required=False) + boolean_field = BooleanField(default=False, required=False) char_field = CharField(max_length=100) comma_separated_integer_field = CharField(max_length=100, validators=[]) date_field = DateField() @@ -182,7 +182,7 @@ class Meta: email_field = EmailField(max_length=100) float_field = FloatField() integer_field = IntegerField() - null_boolean_field = BooleanField(allow_null=True, required=False) + null_boolean_field = BooleanField(allow_null=True, default=False, required=False) positive_integer_field = IntegerField() positive_small_integer_field = IntegerField() slug_field = SlugField(allow_unicode=False, max_length=100) @@ -210,7 +210,7 @@ class Meta: length_limit_field = CharField(max_length=12, min_length=3) blank_field = CharField(allow_blank=True, max_length=10, required=False) null_field = IntegerField(allow_null=True, required=False) - default_field = IntegerField(required=False) + default_field = IntegerField(default=0, required=False) descriptive_field = IntegerField(help_text='Some help text', label='A label') choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green'))) text_choices_field = ChoiceField(choices=(('red', 'Red'), ('blue', 'Blue'), ('green', 'Green'))) From da9288878b27479bd8b031b530337a346a82b104 Mon Sep 17 00:00:00 2001 From: Denis Orehovsky Date: Sun, 13 Aug 2023 08:36:19 +0400 Subject: [PATCH 742/907] Partial serializer should not have required fields (#7563) --- rest_framework/schemas/openapi.py | 2 +- tests/schemas/test_openapi.py | 50 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index d8707e1e1f..ad19f6df85 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -525,7 +525,7 @@ def map_serializer(self, serializer): if isinstance(field, serializers.HiddenField): continue - if field.required: + if field.required and not serializer.partial: required.append(self.get_field_name(field)) schema = self.map_field(field) diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 1eb5b84b71..6645cac728 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -403,6 +403,56 @@ class View(generics.GenericAPIView): assert list(schema['properties']['nested']['properties'].keys()) == ['number'] assert schema['properties']['nested']['required'] == ['number'] + def test_response_body_partial_serializer(self): + path = '/' + method = 'GET' + + class ItemSerializer(serializers.Serializer): + text = serializers.CharField() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.partial = True + + class View(generics.GenericAPIView): + serializer_class = ItemSerializer + + view = create_view( + View, + method, + create_request(path), + ) + inspector = AutoSchema() + inspector.view = view + + responses = inspector.get_responses(path, method) + assert responses == { + '200': { + 'description': '', + 'content': { + 'application/json': { + 'schema': { + 'type': 'array', + 'items': { + '$ref': '#/components/schemas/Item' + }, + }, + }, + }, + }, + } + components = inspector.get_components(path, method) + assert components == { + 'Item': { + 'type': 'object', + 'properties': { + 'text': { + 'type': 'string', + }, + }, + } + } + def test_list_response_body_generation(self): """Test that an array schema is returned for list views.""" path = '/' From 6ebe6f26003df8f21fec83336da00565a0ee8f5b Mon Sep 17 00:00:00 2001 From: rexes-aka <107841205+rexes-aka@users.noreply.github.com> Date: Tue, 15 Aug 2023 13:56:42 +0900 Subject: [PATCH 743/907] Update exceptions.md (#9071) --- docs/api-guide/exceptions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index d914924d0f..33590046bc 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -101,7 +101,7 @@ Note that the exception handler will only be called for responses generated by r The **base class** for all exceptions raised inside an `APIView` class or `@api_view`. -To provide a custom exception, subclass `APIException` and set the `.status_code`, `.default_detail`, and `default_code` attributes on the class. +To provide a custom exception, subclass `APIException` and set the `.status_code`, `.default_detail`, and `.default_code` attributes on the class. For example, if your API relies on a third party service that may sometimes be unreachable, you might want to implement an exception for the "503 Service Unavailable" HTTP response code. You could do this like so: From a2430a8cce67ca4d511abb20dfb783344fa4bad4 Mon Sep 17 00:00:00 2001 From: Harald Nezbeda Date: Tue, 15 Aug 2023 07:17:08 +0200 Subject: [PATCH 744/907] Add docs validation to release process (#6967) * Add ci stage to test for broken links in documentation * Add docs validation to release process * Update .github/workflows/main.yml * Update .github/workflows/main.yml --------- Co-authored-by: Ryan P Kilby Co-authored-by: Asif Saif Uddin --- .github/workflows/main.yml | 24 +++++++++++++++++++++ docs/community/project-management.md | 3 +++ requirements/requirements-documentation.txt | 3 +++ 3 files changed, 30 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f245f69644..5ffca54f93 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,3 +56,27 @@ jobs: - name: Upload coverage run: | codecov -e TOXENV,DJANGO + + test-docs: + name: Test documentation links + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: pip install -r requirements/requirements-documentation.txt + + # Start mkdocs server and wait for it to be ready + - run: mkdocs serve & + - run: WAIT_TIME=0 && until nc -vzw 2 localhost 8000 || [ $WAIT_TIME -eq 5 ]; do sleep $(( WAIT_TIME++ )); done + - run: if [ $WAIT_TIME == 5 ]; then echo cannot start mkdocs server on http://localhost:8000; exit 1; fi + + - name: Check links + continue-on-error: true + run: pylinkvalidate.py -P http://localhost:8000/ + + - run: echo "Done" diff --git a/docs/community/project-management.md b/docs/community/project-management.md index 293c65e246..92132ae7e6 100644 --- a/docs/community/project-management.md +++ b/docs/community/project-management.md @@ -112,6 +112,9 @@ The following template should be used for the description of the issue, and serv - [ ] `docs` Python & Django versions - [ ] Update the translations from [transifex](https://www.django-rest-framework.org/topics/project-management/#translations). - [ ] Ensure the pull request increments the version to `*.*.*` in [`restframework/__init__.py`](https://github.com/encode/django-rest-framework/blob/master/rest_framework/__init__.py). + - [ ] Ensure documentation validates + - Build and serve docs `mkdocs serve` + - Validate links `pylinkvalidate.py -P http://127.0.0.1:8000` - [ ] Confirm with @tomchristie that release is finalized and ready to go. - [ ] Ensure that release date is included in pull request. - [ ] Merge the release pull request. diff --git a/requirements/requirements-documentation.txt b/requirements/requirements-documentation.txt index cf2dc26e88..bce03abc5b 100644 --- a/requirements/requirements-documentation.txt +++ b/requirements/requirements-documentation.txt @@ -1,3 +1,6 @@ # MkDocs to build our documentation. mkdocs>=1.1.2,<1.2 jinja2>=2.10,<3.1.0 # contextfilter has been renamed + +# pylinkvalidator to check for broken links in documentation. +pylinkvalidator==0.3 From 2843b925f90f078a789bcd20b0f8dab360c48008 Mon Sep 17 00:00:00 2001 From: Denis Surkov Date: Tue, 15 Aug 2023 07:20:05 +0200 Subject: [PATCH 745/907] Add note for HiddenField behavior (#9055) * Add note for HiddenField behavior * Update docs/api-guide/fields.md * Update docs/api-guide/validators.md --------- Co-authored-by: Asif Saif Uddin --- docs/api-guide/fields.md | 6 ++++++ docs/api-guide/validators.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 7c27162187..0766fe3ef6 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -558,6 +558,12 @@ The `HiddenField` class is usually only needed if you have some validation that For further examples on `HiddenField` see the [validators](validators.md) documentation. +--- + +**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request). This behavior might change in future, follow updates on [github discussion](https://github.com/encode/django-rest-framework/discussions/8259). + +--- + ## ModelField A generic field that can be tied to any arbitrary model field. The `ModelField` class delegates the task of serialization/deserialization to its associated model field. This field can be used to create serializer fields for custom model fields, without having to create a new custom serializer field. diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index 2a1e3e6b36..2a93e4cdca 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -164,6 +164,12 @@ If you want the date field to be entirely hidden from the user, then use `Hidden --- +--- + +**Note:** `HiddenField()` does not appear in `partial=True` serializer (when making `PATCH` request). This behavior might change in future, follow updates on [github discussion](https://github.com/encode/django-rest-framework/discussions/8259). + +--- + # Advanced field defaults Validators that are applied across multiple fields in the serializer can sometimes require a field input that should not be provided by the API client, but that *is* available as input to the validator. From a47adbcd0f7c0a6aba565462954d2bd8d4d52a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Kr=C3=A1l?= Date: Tue, 15 Aug 2023 07:50:02 +0200 Subject: [PATCH 746/907] Document support for http.HTTPMethod in the @action decorator added in Python 3.11. (#9067) * Implement tests for HTTPMethod from Python 3.11 * Update documentation to mention HTTPMethod support in @action --- docs/api-guide/viewsets.md | 7 +++++++ tests/test_decorators.py | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 5d5491a83f..39db18bca1 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -178,6 +178,13 @@ The `action` decorator will route `GET` requests by default, but may also accept def unset_password(self, request, pk=None): ... +Argument `methods` also supports HTTP methods defined as [HTTPMethod](https://docs.python.org/3/library/http.html#http.HTTPMethod). Example below is identical to the one above: + + from http import HTTPMethod + + @action(detail=True, methods=[HTTPMethod.POST, HTTPMethod.DELETE]) + def unset_password(self, request, pk=None): + ... The decorator allows you to override any viewset-level configuration such as `permission_classes`, `serializer_class`, `filter_backends`...: diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 99ba13e60c..8d0805cbba 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,3 +1,5 @@ +import sys + import pytest from django.test import TestCase @@ -187,6 +189,20 @@ def test_action(request): assert str(excinfo.value) == "@action() missing required argument: 'detail'" + @pytest.mark.skipif(sys.version_info < (3, 11), reason="HTTPMethod was added in Python 3.11") + def test_method_mapping_http_method(self): + from http import HTTPMethod + + method_names = [getattr(HTTPMethod, name.upper()) for name in APIView.http_method_names] + + @action(detail=False, methods=method_names) + def test_action(): + raise NotImplementedError + + expected_mapping = {name: test_action.__name__ for name in APIView.http_method_names} + + assert test_action.mapping == expected_mapping + def test_method_mapping_http_methods(self): # All HTTP methods should be mappable @action(detail=False, methods=[]) From 9e05aa59622fc3b859b7bc0b3abb81573a83bed7 Mon Sep 17 00:00:00 2001 From: Ori Roza Date: Tue, 15 Aug 2023 08:58:06 +0300 Subject: [PATCH 747/907] Update third-party-packages.md (#9026) added https://github.com/Ori-Roza/drf-api-action to third packages docs --- docs/community/third-party-packages.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 2f418a3131..2304dfe45f 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -150,6 +150,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [fast-drf] - A model based library for making API development faster and easier. * [django-requestlogs] - Providing middleware and other helpers for audit logging for REST framework. * [drf-standardized-errors][drf-standardized-errors] - DRF exception handler to standardize error responses for all API endpoints. +* [drf-api-action][drf-api-action] - uses the power of DRF also as a library functions [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework @@ -241,3 +242,4 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [fast-drf]: https://github.com/iashraful/fast-drf [django-requestlogs]: https://github.com/Raekkeri/django-requestlogs [drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors +[drf-api-action]: https://github.com/Ori-Roza/drf-api-action From 5c07060ce0dcbdad1e38be8a509f5c23b6e9fc63 Mon Sep 17 00:00:00 2001 From: Devid Date: Wed, 16 Aug 2023 15:11:50 +0100 Subject: [PATCH 748/907] Use str as default path converter (#9066) --- rest_framework/routers.py | 2 +- tests/test_routers.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/rest_framework/routers.py b/rest_framework/routers.py index fa5d16922b..2b9478e905 100644 --- a/rest_framework/routers.py +++ b/rest_framework/routers.py @@ -144,7 +144,7 @@ def __init__(self, trailing_slash=True, use_regex_path=True): self._url_conf = re_path else: self._base_pattern = '<{lookup_value}:{lookup_prefix}{lookup_url_kwarg}>' - self._default_value_pattern = 'path' + self._default_value_pattern = 'str' self._url_conf = path # remove regex characters from routes _routes = [] diff --git a/tests/test_routers.py b/tests/test_routers.py index b4bde5c312..887f601d54 100644 --- a/tests/test_routers.py +++ b/tests/test_routers.py @@ -99,6 +99,13 @@ def url_path_detail(self, request, *args, **kwargs): kwarg = self.kwargs.get('kwarg', '') return Response({'pk': pk, 'kwarg': kwarg}) + @action(detail=True, url_path='detail//detail/') + def url_path_detail_multiple_params(self, request, *args, **kwargs): + pk = self.kwargs.get('pk', '') + kwarg = self.kwargs.get('kwarg', '') + param = self.kwargs.get('param', '') + return Response({'pk': pk, 'kwarg': kwarg, 'param': param}) + notes_router = SimpleRouter() notes_router.register(r'notes', NoteViewSet) @@ -561,6 +568,18 @@ def test_detail_extra_action(self): assert response.status_code == 200 assert json.loads(response.content.decode()) == {'pk': pk, 'kwarg': kwarg} + def test_detail_extra_other_action(self): + # this to assure that ambiguous patterns are interpreted correctly + # using the `path` converters this URL is recognized to match the pattern + # of `UrlPathViewSet.url_path_detail` when it should match + # `UrlPathViewSet.url_path_detail_multiple_params` + pk = '1' + kwarg = 1234 + param = 2 + response = self.client.get('/path/1/detail/1234/detail/2/') + assert response.status_code == 200 + assert json.loads(response.content.decode()) == {'pk': pk, 'kwarg': kwarg, 'param': param} + def test_defaultrouter_root(self): response = self.client.get('/default/') assert response.status_code == 200 From 40eccb0d6cdb769876dbb79724c5871b4f04163d Mon Sep 17 00:00:00 2001 From: Harald Nezbeda Date: Thu, 17 Aug 2023 12:21:03 +0200 Subject: [PATCH 749/907] Fixes documentation link test (#9076) --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5ffca54f93..fe9610ce31 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -65,7 +65,7 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: '3.10' + python-version: '3.9' - name: Install dependencies run: pip install -r requirements/requirements-documentation.txt From 5a01a4c8a900a98ca0186b742d03d02c55846632 Mon Sep 17 00:00:00 2001 From: Daniel <107224353+daniel-web-developer@users.noreply.github.com> Date: Thu, 31 Aug 2023 10:19:25 +0200 Subject: [PATCH 750/907] Update CONTRIBUTING.md (#9095) Removed apostrophe from "it's". --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d567d45a87..fb01f8bf76 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to REST framework -At this point in it's lifespan we consider Django REST framework to be essentially feature-complete. We may accept pull requests that track the continued development of Django versions, but would prefer not to accept new features or code formatting changes. +At this point in its lifespan we consider Django REST framework to be essentially feature-complete. We may accept pull requests that track the continued development of Django versions, but would prefer not to accept new features or code formatting changes. Apart from minor documentation changes, the [GitHub discussions page](https://github.com/encode/django-rest-framework/discussions) should generally be your starting point. Please only raise an issue or pull request if you've been recommended to do so after discussion. From b6e3a22f7cfc38cb3fd3f2e3352f6276d811b391 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Sat, 9 Sep 2023 13:51:05 +0600 Subject: [PATCH 751/907] added jQuery 3.7.1 support & dropped previous version (#9094) * added jQuery 3.7.1 support & dropped previous version * added jQuery 3.7.1 support & dropped previous version --- rest_framework/static/rest_framework/js/jquery-3.5.1.min.js | 2 -- rest_framework/static/rest_framework/js/jquery-3.6.4.min.js | 2 -- rest_framework/static/rest_framework/js/jquery-3.7.1.min.js | 2 ++ rest_framework/templates/rest_framework/admin.html | 2 +- rest_framework/templates/rest_framework/base.html | 2 +- rest_framework/templates/rest_framework/docs/error.html | 2 +- rest_framework/templates/rest_framework/docs/index.html | 2 +- 7 files changed, 6 insertions(+), 8 deletions(-) delete mode 100644 rest_framework/static/rest_framework/js/jquery-3.5.1.min.js delete mode 100644 rest_framework/static/rest_framework/js/jquery-3.6.4.min.js create mode 100644 rest_framework/static/rest_framework/js/jquery-3.7.1.min.js diff --git a/rest_framework/static/rest_framework/js/jquery-3.5.1.min.js b/rest_framework/static/rest_framework/js/jquery-3.5.1.min.js deleted file mode 100644 index b0614034ad..0000000000 --- a/rest_framework/static/rest_framework/js/jquery-3.5.1.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.5.1 | (c) JS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||j,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,j=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function qe(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function Le(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function He(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
      ",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=$e(y.pixelPosition,function(e,t){if(t)return t=Be(e,n),Me.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&v(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!y||!y.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ve(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace(B,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ye(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ve(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.cssHas=ce(function(){try{return C.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],y=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&y.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||y.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||y.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||y.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||y.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||y.push(".#.+[+~]"),e.querySelectorAll("\\\f"),y.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&y.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&y.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&y.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),y.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),d.cssHas||y.push(":has"),y=y.length&&new RegExp(y.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),v=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType&&e.documentElement||e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&v(p,e)?-1:t==C||t.ownerDocument==p&&v(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!y||!y.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),v.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",v.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",v.option=!!ce.lastChild;var ge={thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};function ye(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var Ut,Xt=[],Vt=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Xt.pop()||S.expando+"_"+Ct.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Vt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Vt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Vt,"$1"+r):!1!==e.jsonp&&(e.url+=(Et.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Xt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),v.createHTMLDocument=((Ut=E.implementation.createHTMLDocument("").body).innerHTML="
      ",2===Ut.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(v.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return B(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=_e(v.pixelPosition,function(e,t){if(t)return t=Be(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return B(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
      "],col:[2,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
      ",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0{{ error_title }} "csrfToken": "{{ csrf_token }}" } - + diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index 53c964f231..686dd831ff 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -293,7 +293,7 @@

      {{ name }}

      "csrfToken": "{% if request %}{{ csrf_token }}{% endif %}" } - + diff --git a/rest_framework/templates/rest_framework/docs/error.html b/rest_framework/templates/rest_framework/docs/error.html index 0c369e9e8e..91c8a34a5b 100644 --- a/rest_framework/templates/rest_framework/docs/error.html +++ b/rest_framework/templates/rest_framework/docs/error.html @@ -66,6 +66,6 @@

      Overriding this template

      - + diff --git a/rest_framework/templates/rest_framework/docs/index.html b/rest_framework/templates/rest_framework/docs/index.html index 8f8536fbeb..7fd07f7683 100644 --- a/rest_framework/templates/rest_framework/docs/index.html +++ b/rest_framework/templates/rest_framework/docs/index.html @@ -38,7 +38,7 @@ {% include "rest_framework/docs/auth/basic.html" %} {% include "rest_framework/docs/auth/session.html" %} - + From 4bbfa8d4556b5847e91ba95f457cc862b7a0f027 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Sat, 9 Sep 2023 13:55:32 +0600 Subject: [PATCH 752/907] Updated Checkout action version & Ubuntu OS version (#9097) * Updated Checkout action version & Ubuntu OS version * Update .github/workflows/main.yml --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fe9610ce31..48b6e7202b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,7 @@ jobs: - '3.11' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 with: From 605cc4f7367f58002056453d9befd3c1918f6a38 Mon Sep 17 00:00:00 2001 From: Marcelo Galigniana Date: Fri, 29 Sep 2023 11:06:16 +0200 Subject: [PATCH 753/907] Updated links to CSRF/AJAX Django documentation (#9118) --- docs/api-guide/authentication.md | 2 +- docs/topics/ajax-csrf-cors.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index dc55a1e8f1..d6e6293fd9 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -454,7 +454,7 @@ More information can be found in the [Documentation](https://django-rest-durin.r [basicauth]: https://tools.ietf.org/html/rfc2617 [permission]: permissions.md [throttling]: throttling.md -[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax +[csrf-ajax]: https://docs.djangoproject.com/en/stable/howto/csrf/#using-csrf-protection-with-ajax [mod_wsgi_official]: https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIPassAuthorization.html [django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html [django-rest-framework-oauth]: https://jpadilla.github.io/django-rest-framework-oauth/ diff --git a/docs/topics/ajax-csrf-cors.md b/docs/topics/ajax-csrf-cors.md index 094ecc4a41..678fa00e71 100644 --- a/docs/topics/ajax-csrf-cors.md +++ b/docs/topics/ajax-csrf-cors.md @@ -35,7 +35,7 @@ The best way to deal with CORS in REST framework is to add the required response [cite]: https://blog.codinghorror.com/preventing-csrf-and-xsrf-attacks/ [csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF) -[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax +[csrf-ajax]: https://docs.djangoproject.com/en/stable/howto/csrf/#using-csrf-protection-with-ajax [cors]: https://www.w3.org/TR/cors/ [adamchainz]: https://github.com/adamchainz [django-cors-headers]: https://github.com/adamchainz/django-cors-headers From d32346bae55f3e4718a185fb60e9f7a28e389c85 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Wed, 4 Oct 2023 19:03:10 +0200 Subject: [PATCH 754/907] Fix type name of `FieldInfo` namedtuple (#9124) * Fix type name of `FieldInfo` namedtuple * Add test --- rest_framework/utils/model_meta.py | 2 +- tests/test_utils.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py index bd5d9177c0..bb0b77f58e 100644 --- a/rest_framework/utils/model_meta.py +++ b/rest_framework/utils/model_meta.py @@ -7,7 +7,7 @@ """ from collections import namedtuple -FieldInfo = namedtuple('FieldResult', [ +FieldInfo = namedtuple('FieldInfo', [ 'pk', # Model field instance 'fields', # Dict of field name -> model field instance 'forward_relations', # Dict of field name -> RelationInfo diff --git a/tests/test_utils.py b/tests/test_utils.py index c72f680fe4..6c5f5ddf2d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -9,6 +9,7 @@ from rest_framework.utils import json from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework.utils.formatting import lazy_format +from rest_framework.utils.model_meta import FieldInfo, RelationInfo from rest_framework.utils.urls import remove_query_param, replace_query_param from rest_framework.views import APIView from rest_framework.viewsets import ModelViewSet @@ -267,3 +268,9 @@ def test_it_formats_lazily(self): assert message.format.call_count == 1 str(formatted) assert message.format.call_count == 1 + + +class ModelMetaNamedTupleNames(TestCase): + def test_named_tuple_names(self): + assert FieldInfo.__name__ == 'FieldInfo' + assert RelationInfo.__name__ == 'RelationInfo' From 42961892834cb8730cd4ec4f7306caa4f8b44abb Mon Sep 17 00:00:00 2001 From: Terence Honles Date: Thu, 5 Oct 2023 08:33:53 +0200 Subject: [PATCH 755/907] fix dist test by moving --no-pkgroot to runtests.py (#9129) This change fixes the dist test by moving the --no-pkgroot option from pytest to the runtests script. The current "filterwarnings" setting for pytest includes rest_framework, which causes an early import of the module. As a result the current --no-pkgroot behavior fails with an assertion error. Trying to remove the module from sys.modules will cause the warning filter to not apply, so this change moves this code before pytest parses the config and loads the "filterwarnings". --- runtests.py | 13 +++++++++++++ tests/conftest.py | 19 +++++-------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/runtests.py b/runtests.py index 98f34c0673..e2838a0e29 100755 --- a/runtests.py +++ b/runtests.py @@ -1,4 +1,5 @@ #! /usr/bin/env python3 +import os import sys import pytest @@ -34,6 +35,18 @@ def is_class(string): '--cov-report', 'xml', ] + pytest_args + try: + pytest_args.remove('--no-pkgroot') + except ValueError: + pass + else: + sys.path.pop(0) + + # import rest_framework before pytest re-adds the package root directory. + import rest_framework + package_dir = os.path.join(os.getcwd(), 'rest_framework') + assert not rest_framework.__file__.startswith(package_dir) + if first_arg.startswith('-'): # `runtests.py [flags]` pytest_args = ['tests'] + pytest_args diff --git a/tests/conftest.py b/tests/conftest.py index 79cabd5e1a..b67475d8a7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,15 +1,10 @@ import os -import sys import django from django.core import management def pytest_addoption(parser): - parser.addoption('--no-pkgroot', action='store_true', default=False, - help='Remove package root directory from sys.path, ensuring that ' - 'rest_framework is imported from the installed site-packages. ' - 'Used for testing the distribution.') parser.addoption('--staticfiles', action='store_true', default=False, help='Run tests with static files collection, using manifest ' 'staticfiles storage. Used for testing the distribution.') @@ -87,19 +82,15 @@ def pytest_configure(config): 'guardian', ) - if config.getoption('--no-pkgroot'): - sys.path.pop(0) - - # import rest_framework before pytest re-adds the package root directory. - import rest_framework - package_dir = os.path.join(os.getcwd(), 'rest_framework') - assert not rest_framework.__file__.startswith(package_dir) - # Manifest storage will raise an exception if static files are not present (ie, a packaging failure). if config.getoption('--staticfiles'): import rest_framework settings.STATIC_ROOT = os.path.join(os.path.dirname(rest_framework.__file__), 'static-root') - settings.STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' + backend = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage' + if django.VERSION < (4, 2): + settings.STATICFILES_STORAGE = backend + else: + settings.STORAGES['staticfiles']['BACKEND'] = backend django.setup() From d181511155a041cae9b848a4579ada1d50456328 Mon Sep 17 00:00:00 2001 From: Michael Herman Date: Thu, 5 Oct 2023 04:06:07 -0500 Subject: [PATCH 756/907] add courses section to community resources (#8845) --- docs/community/tutorials-and-resources.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/community/tutorials-and-resources.md b/docs/community/tutorials-and-resources.md index 23faf79128..f283e0e4cc 100644 --- a/docs/community/tutorials-and-resources.md +++ b/docs/community/tutorials-and-resources.md @@ -19,6 +19,10 @@ There are a wide range of resources available for learning and using Django REST
      +## Courses + +* [Developing RESTful APIs with Django REST Framework][developing-restful-apis-with-django-rest-framework] + ## Tutorials * [Beginner's Guide to the Django REST Framework][beginners-guide-to-the-django-rest-framework] @@ -130,3 +134,4 @@ Want your Django REST Framework talk/tutorial/article to be added to our website [pycon-us-2017]: https://www.youtube.com/watch?v=Rk6MHZdust4 [django-rest-react-valentinog]: https://www.valentinog.com/blog/tutorial-api-django-rest-react/ [doordash-implementing-rest-apis]: https://doordash.engineering/2013/10/07/implementing-rest-apis-with-embedded-privacy/ +[developing-restful-apis-with-django-rest-framework]: https://testdriven.io/courses/django-rest-framework/ From 8da6b696ab7d9bc3f31c672b72638e52bbd6b53f Mon Sep 17 00:00:00 2001 From: Marko <83985775+MarkoM-dot@users.noreply.github.com> Date: Sat, 7 Oct 2023 10:55:25 +0200 Subject: [PATCH 757/907] docs: update django requirement to 4.2 (#9128) --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index ad241c0a37..81a5563133 100644 --- a/docs/index.md +++ b/docs/index.md @@ -86,7 +86,7 @@ continued development by **[signing up for a paid plan][funding]**. REST framework requires the following: * Python (3.6, 3.7, 3.8, 3.9, 3.10, 3.11) -* Django (3.0, 3.1, 3.2, 4.0, 4.1) +* Django (3.0, 3.1, 3.2, 4.0, 4.1, 4.2) We **highly recommend** and only officially support the latest patch release of each Python and Django series. From 18b02ce00e4c911719297149406cb3c0f054cf22 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Sun, 8 Oct 2023 09:30:10 +0200 Subject: [PATCH 758/907] Add missing `DEFAULT_VERSIONING_CLASS` entry to API docs (#9126) --- docs/api-guide/settings.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index d42000260b..47e2ce993f 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -163,6 +163,12 @@ The string that should used for any versioning parameters, such as in the media Default: `'version'` +#### DEFAULT_VERSIONING_CLASS + +The default versioning scheme to use. + +Default: `None` + --- ## Authentication settings From e794e5e5e43d6838d9ffb8eb0a505b5f531b261f Mon Sep 17 00:00:00 2001 From: "J.V. Zammit" Date: Fri, 13 Oct 2023 13:44:45 +0200 Subject: [PATCH 759/907] blacken-docs (#8906) * blacken-docs: Manual fixes for command to pass without errors * blacken-docs: Adds blacken-docs step to precommit hook. * blacken-docs: Adds changes made by command itself. * blacken-docs: Modifies blacken-docs step to only process files under "docs" directory * blacken-docs: Updates pre-commit config file to exclude all directories other than "docs" to be compatible with "--all-files" flag. * blacken-docs: Adds commas at the end to make it look identical to pre-black version --- .pre-commit-config.yaml | 7 +++++++ docs/api-guide/caching.md | 16 ++++++++-------- docs/api-guide/schemas.md | 26 ++++++++++++++++---------- docs/api-guide/viewsets.md | 25 +++++++++++++------------ docs/community/3.10-announcement.md | 13 +++++++------ docs/community/3.11-announcement.md | 9 +++++---- docs/community/3.12-announcement.md | 12 +++++++----- docs/community/3.9-announcement.md | 11 ++++------- docs/community/release-notes.md | 6 +++++- docs/topics/documenting-your-api.md | 23 +++++++++++++++-------- 10 files changed, 87 insertions(+), 61 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 83fe0b714b..64ec37770f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,3 +18,10 @@ repos: - id: flake8 additional_dependencies: - flake8-tidy-imports +- repo: https://github.com/adamchainz/blacken-docs + rev: 1.13.0 + hooks: + - id: blacken-docs + exclude: ^(?!docs).*$ + additional_dependencies: + - black==23.1.0 diff --git a/docs/api-guide/caching.md b/docs/api-guide/caching.md index ab4f82cd2f..503acb09e6 100644 --- a/docs/api-guide/caching.md +++ b/docs/api-guide/caching.md @@ -28,33 +28,33 @@ from rest_framework import viewsets class UserViewSet(viewsets.ViewSet): # With cookie: cache requested url for each user for 2 hours - @method_decorator(cache_page(60*60*2)) + @method_decorator(cache_page(60 * 60 * 2)) @method_decorator(vary_on_cookie) def list(self, request, format=None): content = { - 'user_feed': request.user.get_user_feed() + "user_feed": request.user.get_user_feed(), } return Response(content) class ProfileView(APIView): # With auth: cache requested url for each user for 2 hours - @method_decorator(cache_page(60*60*2)) - @method_decorator(vary_on_headers("Authorization",)) + @method_decorator(cache_page(60 * 60 * 2)) + @method_decorator(vary_on_headers("Authorization")) def get(self, request, format=None): content = { - 'user_feed': request.user.get_user_feed() + "user_feed": request.user.get_user_feed(), } return Response(content) class PostView(APIView): # Cache page for the requested url - @method_decorator(cache_page(60*60*2)) + @method_decorator(cache_page(60 * 60 * 2)) def get(self, request, format=None): content = { - 'title': 'Post title', - 'body': 'Post content' + "title": "Post title", + "body": "Post content", } return Response(content) ``` diff --git a/docs/api-guide/schemas.md b/docs/api-guide/schemas.md index 3f0b155306..7af98dbf5e 100644 --- a/docs/api-guide/schemas.md +++ b/docs/api-guide/schemas.md @@ -94,11 +94,13 @@ urlpatterns = [ # Use the `get_schema_view()` helper to add a `SchemaView` to project URLs. # * `title` and `description` parameters are passed to `SchemaGenerator`. # * Provide view name for use with `reverse()`. - path('openapi', get_schema_view( - title="Your Project", - description="API for all things …", - version="1.0.0" - ), name='openapi-schema'), + path( + "openapi", + get_schema_view( + title="Your Project", description="API for all things …", version="1.0.0" + ), + name="openapi-schema", + ), # ... ] ``` @@ -259,11 +261,13 @@ class CustomSchema(AutoSchema): """ AutoSchema subclass using schema_extra_info on the view. """ + ... + class CustomView(APIView): schema = CustomSchema() - schema_extra_info = ... some extra info ... + schema_extra_info = ... # some extra info ``` Here, the `AutoSchema` subclass goes looking for `schema_extra_info` on the @@ -278,10 +282,13 @@ class BaseSchema(AutoSchema): """ AutoSchema subclass that knows how to use extra_info. """ + ... + class CustomSchema(BaseSchema): - extra_info = ... some extra info ... + extra_info = ... # some extra info + class CustomView(APIView): schema = CustomSchema() @@ -302,10 +309,9 @@ class CustomSchema(BaseSchema): self.extra_info = kwargs.pop("extra_info") super().__init__(**kwargs) + class CustomView(APIView): - schema = CustomSchema( - extra_info=... some extra info ... - ) + schema = CustomSchema(extra_info=...) # some extra info ``` This saves you having to create a custom subclass per-view for a commonly used option. diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 39db18bca1..41ba1743c3 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -201,15 +201,16 @@ To view all extra actions, call the `.get_extra_actions()` method. Extra actions can map additional HTTP methods to separate `ViewSet` methods. For example, the above password set/unset methods could be consolidated into a single route. Note that additional mappings do not accept arguments. ```python - @action(detail=True, methods=['put'], name='Change Password') - def password(self, request, pk=None): - """Update the user's password.""" - ... - - @password.mapping.delete - def delete_password(self, request, pk=None): - """Delete the user's password.""" - ... +@action(detail=True, methods=["put"], name="Change Password") +def password(self, request, pk=None): + """Update the user's password.""" + ... + + +@password.mapping.delete +def delete_password(self, request, pk=None): + """Delete the user's password.""" + ... ``` ## Reversing action URLs @@ -220,14 +221,14 @@ Note that the `basename` is provided by the router during `ViewSet` registration Using the example from the previous section: -```python ->>> view.reverse_action('set-password', args=['1']) +```pycon +>>> view.reverse_action("set-password", args=["1"]) 'http://localhost:8000/api/users/1/set_password' ``` Alternatively, you can use the `url_name` attribute set by the `@action` decorator. -```python +```pycon >>> view.reverse_action(view.set_password.url_name, args=['1']) 'http://localhost:8000/api/users/1/set_password' ``` diff --git a/docs/community/3.10-announcement.md b/docs/community/3.10-announcement.md index 23b6330a7b..a2135fd20f 100644 --- a/docs/community/3.10-announcement.md +++ b/docs/community/3.10-announcement.md @@ -41,8 +41,8 @@ update your REST framework settings to include `DEFAULT_SCHEMA_CLASS` explicitly ```python REST_FRAMEWORK = { - ... - 'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema' + ...: ..., + "DEFAULT_SCHEMA_CLASS": "rest_framework.schemas.coreapi.AutoSchema", } ``` @@ -74,10 +74,11 @@ urlpatterns = [ # Use the `get_schema_view()` helper to add a `SchemaView` to project URLs. # * `title` and `description` parameters are passed to `SchemaGenerator`. # * Provide view name for use with `reverse()`. - path('openapi', get_schema_view( - title="Your Project", - description="API for all things …" - ), name='openapi-schema'), + path( + "openapi", + get_schema_view(title="Your Project", description="API for all things …"), + name="openapi-schema", + ), # ... ] ``` diff --git a/docs/community/3.11-announcement.md b/docs/community/3.11-announcement.md index 83dd636d19..5a1f2c8e30 100644 --- a/docs/community/3.11-announcement.md +++ b/docs/community/3.11-announcement.md @@ -43,10 +43,11 @@ be extracted from the class docstring: ```python class DocStringExampleListView(APIView): -""" -get: A description of my GET operation. -post: A description of my POST operation. -""" + """ + get: A description of my GET operation. + post: A description of my POST operation. + """ + permission_classes = [permissions.IsAuthenticatedOrReadOnly] def get(self, request, *args, **kwargs): diff --git a/docs/community/3.12-announcement.md b/docs/community/3.12-announcement.md index 3bfeb65763..b192f7290a 100644 --- a/docs/community/3.12-announcement.md +++ b/docs/community/3.12-announcement.md @@ -41,7 +41,7 @@ The tags used for a particular view may also be overridden... ```python class MyOrders(APIView): - schema = AutoSchema(tags=['users', 'orders']) + schema = AutoSchema(tags=["users", "orders"]) ... ``` @@ -68,7 +68,7 @@ may be overridden if needed](https://www.django-rest-framework.org/api-guide/sch ```python class MyOrders(APIView): - schema = AutoSchema(component_name="OrderDetails") + schema = AutoSchema(component_name="OrderDetails") ``` ## More Public API @@ -118,10 +118,11 @@ class SitesSearchView(generics.ListAPIView): by a search against the site name or location. (Location searches are matched against the region and country names.) """ + queryset = Sites.objects.all() serializer_class = SitesSerializer filter_backends = [filters.SearchFilter] - search_fields = ['site_name', 'location__region', 'location__country'] + search_fields = ["site_name", "location__region", "location__country"] ``` ### Searches against annotate fields @@ -135,10 +136,11 @@ class PublisherSearchView(generics.ListAPIView): Search for publishers, optionally filtering the search against the average rating of all their books. """ - queryset = Publisher.objects.annotate(avg_rating=Avg('book__rating')) + + queryset = Publisher.objects.annotate(avg_rating=Avg("book__rating")) serializer_class = PublisherSerializer filter_backends = [filters.SearchFilter] - search_fields = ['avg_rating'] + search_fields = ["avg_rating"] ``` --- diff --git a/docs/community/3.9-announcement.md b/docs/community/3.9-announcement.md index d673fdd183..6bc5e3cc39 100644 --- a/docs/community/3.9-announcement.md +++ b/docs/community/3.9-announcement.md @@ -65,15 +65,12 @@ from rest_framework.renderers import JSONOpenAPIRenderer from django.urls import path schema_view = get_schema_view( - title='Server Monitoring API', - url='https://www.example.org/api/', - renderer_classes=[JSONOpenAPIRenderer] + title="Server Monitoring API", + url="https://www.example.org/api/", + renderer_classes=[JSONOpenAPIRenderer], ) -urlpatterns = [ - path('schema.json', schema_view), - ... -] +urlpatterns = [path("schema.json", schema_view), ...] ``` And here's how you can use the `generateschema` management command: diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index fba7f63d62..d8eadeb32e 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -306,7 +306,11 @@ Be sure to upgrade to Python 3 before upgrading to Django REST Framework 3.10. class NullableCharField(serializers.CharField): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.validators = [v for v in self.validators if not isinstance(v, ProhibitNullCharactersValidator)] + self.validators = [ + v + for v in self.validators + if not isinstance(v, ProhibitNullCharactersValidator) + ] ``` * Add `OpenAPIRenderer` and `generate_schema` management command. [#6229][gh6229] * Add OpenAPIRenderer by default, and add schema docs. [#6233][gh6233] diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md index b85d6310ad..edb989290d 100644 --- a/docs/topics/documenting-your-api.md +++ b/docs/topics/documenting-your-api.md @@ -96,10 +96,14 @@ urlpatterns = [ # ... # Route TemplateView to serve Swagger UI template. # * Provide `extra_context` with view name of `SchemaView`. - path('swagger-ui/', TemplateView.as_view( - template_name='swagger-ui.html', - extra_context={'schema_url':'openapi-schema'} - ), name='swagger-ui'), + path( + "swagger-ui/", + TemplateView.as_view( + template_name="swagger-ui.html", + extra_context={"schema_url": "openapi-schema"}, + ), + name="swagger-ui", + ), ] ``` @@ -145,10 +149,13 @@ urlpatterns = [ # ... # Route TemplateView to serve the ReDoc template. # * Provide `extra_context` with view name of `SchemaView`. - path('redoc/', TemplateView.as_view( - template_name='redoc.html', - extra_context={'schema_url':'openapi-schema'} - ), name='redoc'), + path( + "redoc/", + TemplateView.as_view( + template_name="redoc.html", extra_context={"schema_url": "openapi-schema"} + ), + name="redoc", + ), ] ``` From 2c59206b3ee4772ccfe981fb6ad52e40d59a91e7 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Tue, 17 Oct 2023 15:40:12 +0600 Subject: [PATCH 760/907] Update requirements-documentation.txt (#9141) --- requirements/requirements-documentation.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-documentation.txt b/requirements/requirements-documentation.txt index bce03abc5b..25f5121f2e 100644 --- a/requirements/requirements-documentation.txt +++ b/requirements/requirements-documentation.txt @@ -1,5 +1,5 @@ # MkDocs to build our documentation. -mkdocs>=1.1.2,<1.2 +mkdocs==1.2.4 jinja2>=2.10,<3.1.0 # contextfilter has been renamed # pylinkvalidator to check for broken links in documentation. From f56b85b7dd7e4f786e0769bba6b7609d4507da83 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Tue, 17 Oct 2023 16:13:10 +0600 Subject: [PATCH 761/907] Update pygments>=2.12.0,<2.14.0 in requirements-optionals (#9142) * Update pygments>=2.12.0,<2.16.1 in requirements-optionals * Update requirements/requirements-optionals.txt * Update requirements/requirements-optionals.txt * Update requirements/requirements-optionals.txt --- requirements/requirements-optionals.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index 0379f8c383..dadfba5bd6 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -6,5 +6,5 @@ django-guardian>=2.4.0,<2.5 inflection==0.5.1 markdown==3.3 psycopg2-binary>=2.9.5,<2.10 -pygments==2.12 +pygments>=2.12.0,<2.14.0 pyyaml>=5.3.1,<5.4 From 1db19f4b2d1500894448634852e02f73043382e4 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Sun, 5 Nov 2023 11:59:10 +0600 Subject: [PATCH 762/907] Update pytest version in requirements-testing.txt (#9158) --- requirements/requirements-testing.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-testing.txt b/requirements/requirements-testing.txt index 0f6d7ff87d..2b39316a00 100644 --- a/requirements/requirements-testing.txt +++ b/requirements/requirements-testing.txt @@ -1,5 +1,5 @@ # Pytest for running the tests. -pytest>=6.2.0,<8.0 +pytest>=7.0.1,<8.0 pytest-cov>=4.0.0,<5.0 pytest-django>=4.5.2,<5.0 importlib-metadata<5.0 From 15f619ade0c94e38873f19b188bad0bc347b2d2d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 29 Nov 2023 14:13:36 +0000 Subject: [PATCH 763/907] Add Svix to sponsors (#9173) --- README.md | 5 ++++- docs/img/premium/svix-premium.png | Bin 0 -> 32055 bytes docs/index.md | 3 ++- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 docs/img/premium/svix-premium.png diff --git a/README.md b/README.md index c2975a418f..60c62ebf54 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,9 @@ The initial aim is to provide a single full-time position on REST framework. [![][posthog-img]][posthog-url] [![][cryptapi-img]][cryptapi-url] [![][fezto-img]][fezto-url] +[![][svix-img]][svix-url] -Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Spacinov][spacinov-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], [CryptAPI][cryptapi-url], and [FEZTO][fezto-url]. +Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry][sentry-url], [Stream][stream-url], [Spacinov][spacinov-url], [Retool][retool-url], [bit.io][bitio-url], [PostHog][posthog-url], [CryptAPI][cryptapi-url], [FEZTO][fezto-url], and [Svix][svix-url]. --- @@ -200,6 +201,7 @@ Please see the [security policy][security-policy]. [posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png [cryptapi-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png [fezto-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/fezto-readme.png +[svix-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/svix-readme.png [sentry-url]: https://getsentry.com/welcome/ [stream-url]: https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage @@ -209,6 +211,7 @@ Please see the [security policy][security-policy]. [posthog-url]: https://posthog.com?utm_source=drf&utm_medium=sponsorship&utm_campaign=open-source-sponsorship [cryptapi-url]: https://cryptapi.io [fezto-url]: https://www.fezto.xyz/?utm_source=DjangoRESTFramework +[svix-url]: https://www.svix.com/?utm_source=django-REST&utm_medium=sponsorship [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 diff --git a/docs/img/premium/svix-premium.png b/docs/img/premium/svix-premium.png new file mode 100644 index 0000000000000000000000000000000000000000..68ff063879bec44644ffea214c86200c476976af GIT binary patch literal 32055 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 literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md index 81a5563133..60eb65744d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -74,10 +74,11 @@ continued development by **[signing up for a paid plan][funding]**.
    • PostHog
    • CryptAPI
    • FEZTO
    • +
    • Svix
    -*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [Spacinov](https://www.spacinov.com/), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [CryptAPI](https://cryptapi.io), and [FEZTO](https://www.fezto.xyz/?utm_source=DjangoRESTFramework).* +*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage), [Spacinov](https://www.spacinov.com/), [Retool](https://retool.com/?utm_source=djangorest&utm_medium=sponsorship), [bit.io](https://bit.io/jobs?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [PostHog](https://posthog.com?utm_source=DRF&utm_medium=sponsor&utm_campaign=DRF_sponsorship), [CryptAPI](https://cryptapi.io), [FEZTO](https://www.fezto.xyz/?utm_source=DjangoRESTFramework), and [Svix](https://www.svix.com/?utm_source=django-REST&utm_medium=sponsorship).* --- From 06022e788e1d23775af6044f02d8a2d577e30b50 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 29 Nov 2023 14:15:53 +0000 Subject: [PATCH 764/907] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 60c62ebf54..524edb8c35 100644 --- a/README.md +++ b/README.md @@ -201,7 +201,7 @@ Please see the [security policy][security-policy]. [posthog-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/posthog-readme.png [cryptapi-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/cryptapi-readme.png [fezto-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/fezto-readme.png -[svix-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/svix-readme.png +[svix-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/svix-premium.png [sentry-url]: https://getsentry.com/welcome/ [stream-url]: https://getstream.io/?utm_source=DjangoRESTFramework&utm_medium=Webpage_Logo_Ad&utm_content=Developer&utm_campaign=DjangoRESTFramework_Jan2022_HomePage From 0abb84fa3955956fc6156cc2c51222cd4ceae783 Mon Sep 17 00:00:00 2001 From: Krishna2864 <75547302+Krishna2864@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:37:36 +0530 Subject: [PATCH 765/907] removed hyperlink for web browsable API on homepage (#9174) --- README.md | 2 +- docs/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 524edb8c35..078ac07116 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Django REST framework is a powerful and flexible toolkit for building Web APIs. Some reasons you might want to use REST framework: -* The [Web browsable API][sandbox] is a huge usability win for your developers. +* The Web browsable API is a huge usability win for your developers. * [Authentication policies][authentication] including optional packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section]. * [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources. * Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers]. diff --git a/docs/index.md b/docs/index.md index 60eb65744d..a7f1444a32 100644 --- a/docs/index.md +++ b/docs/index.md @@ -48,7 +48,7 @@ Django REST framework is a powerful and flexible toolkit for building Web APIs. Some reasons you might want to use REST framework: -* The [Web browsable API][sandbox] is a huge usability win for your developers. +* The Web browsable API is a huge usability win for your developers. * [Authentication policies][authentication] including packages for [OAuth1a][oauth1-section] and [OAuth2][oauth2-section]. * [Serialization][serializers] that supports both [ORM][modelserializer-section] and [non-ORM][serializer-section] data sources. * Customizable all the way down - just use [regular function-based views][functionview-section] if you don't need the [more][generic-views] [powerful][viewsets] [features][routers]. From 0f39e0124d358b0098261f070175fa8e0359b739 Mon Sep 17 00:00:00 2001 From: Asif Saif Uddin Date: Wed, 6 Dec 2023 22:44:47 +0600 Subject: [PATCH 766/907] Update requirements-optionals.txt (#9181) * Update requirements-optionals.txt * Update requirements-optionals.txt --- requirements/requirements-optionals.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-optionals.txt b/requirements/requirements-optionals.txt index dadfba5bd6..e54100f522 100644 --- a/requirements/requirements-optionals.txt +++ b/requirements/requirements-optionals.txt @@ -4,7 +4,7 @@ coreschema==0.0.4 django-filter django-guardian>=2.4.0,<2.5 inflection==0.5.1 -markdown==3.3 +markdown>=3.3.7 psycopg2-binary>=2.9.5,<2.10 pygments>=2.12.0,<2.14.0 pyyaml>=5.3.1,<5.4 From 4c231d5b97bcc1769ba5a6f41c16f011200300b8 Mon Sep 17 00:00:00 2001 From: HGZahn <44118429+HGZahn@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:36:49 +0100 Subject: [PATCH 767/907] Clarifying the file to edit (#9189) --- docs/tutorial/6-viewsets-and-routers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index f9b6c5e9a4..6fa2111e7b 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -10,7 +10,7 @@ A `ViewSet` class is only bound to a set of method handlers at the last moment, Let's take our current set of views, and refactor them into view sets. -First of all let's refactor our `UserList` and `UserDetail` classes into a single `UserViewSet` class. We can remove the two view classes, and replace them with a single ViewSet class: +First of all let's refactor our `UserList` and `UserDetail` classes into a single `UserViewSet` class. In the `snippets/views.py` file, we can remove the two view classes and replace them with a single ViewSet class: from rest_framework import viewsets From 530baa23cdf9c3fb44595c69f49684469a758374 Mon Sep 17 00:00:00 2001 From: Yousef Abu Shanab <93343012+youzarsiph@users.noreply.github.com> Date: Thu, 4 Jan 2024 04:42:54 -0800 Subject: [PATCH 768/907] Add 2 customization packages, rest-framework-redesign and rest-framework-material (#9088) --- docs/community/third-party-packages.md | 7 +++++++ docs/img/rfm.png | Bin 0 -> 137450 bytes docs/img/rfr.png | Bin 0 -> 145437 bytes docs/topics/browsable-api.md | 25 +++++++++++++++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 docs/img/rfm.png create mode 100644 docs/img/rfr.png diff --git a/docs/community/third-party-packages.md b/docs/community/third-party-packages.md index 2304dfe45f..20c62f8d6f 100644 --- a/docs/community/third-party-packages.md +++ b/docs/community/third-party-packages.md @@ -152,6 +152,11 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [drf-standardized-errors][drf-standardized-errors] - DRF exception handler to standardize error responses for all API endpoints. * [drf-api-action][drf-api-action] - uses the power of DRF also as a library functions +### Customization + +* [rest-framework-redesign][rest-framework-redesign] - A package for customizing the API using Bootstrap 5. +* [rest-framework-material][rest-framework-material] - Material design for Django REST Framework API. + [cite]: http://www.software-ecosystems.com/Software_Ecosystems/Ecosystems.html [cookiecutter]: https://github.com/jpadilla/cookiecutter-django-rest-framework [new-repo]: https://github.com/new @@ -243,3 +248,5 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-requestlogs]: https://github.com/Raekkeri/django-requestlogs [drf-standardized-errors]: https://github.com/ghazi-git/drf-standardized-errors [drf-api-action]: https://github.com/Ori-Roza/drf-api-action +[rest-framework-redesign]: https://github.com/youzarsiph/rest-framework-redesign +[rest-framework-material]: https://github.com/youzarsiph/rest-framework-material diff --git a/docs/img/rfm.png b/docs/img/rfm.png new file mode 100644 index 0000000000000000000000000000000000000000..7c82621af715eca1530d8a244a5b20180bddbb79 GIT binary patch literal 137450 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

    Y2;Ep^b>( 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 Ak^lez literal 0 HcmV?d00001 diff --git a/docs/img/rfr.png b/docs/img/rfr.png new file mode 100644 index 0000000000000000000000000000000000000000..1854511d0da63b3c192323716ad2187453b9fd23 GIT binary patch literal 145437 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≥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 literal 0 HcmV?d00001 diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md index 2cdf2a884c..39473cdd87 100644 --- a/docs/topics/browsable-api.md +++ b/docs/topics/browsable-api.md @@ -65,6 +65,27 @@ For more specific CSS tweaks than simply overriding the default bootstrap theme --- +### Third party packages for customization + +You can use a third party package for customization, rather than doing it by yourself. Here is 2 packages for customizing the API: + +* [rest-framework-redesign][rest-framework-redesign] - A package for customizing the API using Bootstrap 5. Modern and sleek design, it comes with the support for dark mode. +* [rest-framework-material][rest-framework-material] - Material design for Django REST Framework. + +--- + +![Django REST Framework Redesign][rfr] + +*Screenshot of the rest-framework-redesign* + +--- + +![Django REST Framework Material][rfm] + +*Screenshot of the rest-framework-material* + +--- + ### Blocks All of the blocks available in the browsable API base template that can be used in your `api.html`. @@ -162,3 +183,7 @@ There are [a variety of packages for autocomplete widgets][autocomplete-packages [bcomponentsnav]: https://getbootstrap.com/2.3.2/components.html#navbar [autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/ [django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light +[rest-framework-redesign]: https://github.com/youzarsiph/rest-framework-redesign +[rest-framework-material]: https://github.com/youzarsiph/rest-framework-material +[rfr]: ../img/rfr.png +[rfm]: ../img/rfm.png From f4175b4b079ba939bdb917a64d921610f32bd536 Mon Sep 17 00:00:00 2001 From: Matt George <102607949+geomat0101@users.noreply.github.com> Date: Thu, 4 Jan 2024 08:10:53 -0500 Subject: [PATCH 769/907] Update quickstart.md (#9185) add .order_by() to eliminate Pagination UnorderedObjectListWarning --- docs/tutorial/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 09f249f6e1..9a9da40437 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -105,7 +105,7 @@ Right, we'd better write some views then. Open `tutorial/quickstart/views.py` a """ API endpoint that allows groups to be viewed or edited. """ - queryset = Group.objects.all() + queryset = Group.objects.all().order_by('name') serializer_class = GroupSerializer permission_classes = [permissions.IsAuthenticated] From 047bec1288040b9f387e4977bb6cb3fd00d8ea40 Mon Sep 17 00:00:00 2001 From: Code Apprentice Date: Thu, 4 Jan 2024 07:16:57 -0700 Subject: [PATCH 770/907] Add documentation to show how to add authentication for the browseable api (#9137) --- docs/topics/browsable-api.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md index 39473cdd87..67bc978908 100644 --- a/docs/topics/browsable-api.md +++ b/docs/topics/browsable-api.md @@ -15,6 +15,18 @@ If you include fully-qualified URLs in your resource output, they will be 'urliz By default, the API will return the format specified by the headers, which in the case of the browser is HTML. The format can be specified using `?format=` in the request, so you can look at the raw JSON response in a browser by adding `?format=json` to the URL. There are helpful extensions for viewing JSON in [Firefox][ffjsonview] and [Chrome][chromejsonview]. +## Authentication + +To quickly add authentication to the browesable api, add a routes named `"login"` and `"logout"` under the namespace `"rest_framework"`. DRF provides default routes for this which you can add to your urlconf: + +```python +urlpatterns = [ + // ... + url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) +] +``` + + ## Customizing The browsable API is built with [Twitter's Bootstrap][bootstrap] (v 3.4.1), making it easy to customize the look-and-feel. From 21bb21b65bee2c3f75ac7d083f89f45791e96aef Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Thu, 11 Jan 2024 20:34:46 +0800 Subject: [PATCH 771/907] Fix use of ip_address_validators for Django 5.1+ (#9180) * Fix use of ip_address_validators for Django 5.0+ * Change affected django version to 5.1 --- rest_framework/compat.py | 15 +++++++++++++++ rest_framework/fields.py | 5 +++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/rest_framework/compat.py b/rest_framework/compat.py index 7e80704e11..472b8ad244 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -169,6 +169,21 @@ def parse_header_parameters(line): } +if django.VERSION >= (5, 1): + # Django 5.1+: use the stock ip_address_validators function + # Note: Before Django 5.1, ip_address_validators returns a tuple containing + # 1) the list of validators and 2) the error message. Starting from + # Django 5.1 ip_address_validators only returns the list of validators + from django.core.validators import ip_address_validators +else: + # Django <= 5.1: create a compatibility shim for ip_address_validators + from django.core.validators import \ + ip_address_validators as _ip_address_validators + + def ip_address_validators(protocol, unpack_ipv4): + return _ip_address_validators(protocol, unpack_ipv4)[0] + + # `separators` argument to `json.dumps()` differs between 2.x and 3.x # See: https://bugs.python.org/issue22767 SHORT_SEPARATORS = (',', ':') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 0b56fa7fb6..fda656507b 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -16,7 +16,7 @@ from django.core.validators import ( EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator, MinValueValidator, ProhibitNullCharactersValidator, RegexValidator, - URLValidator, ip_address_validators + URLValidator ) from django.forms import FilePathField as DjangoFilePathField from django.forms import ImageField as DjangoImageField @@ -36,6 +36,7 @@ pytz = None from rest_framework import ISO_8601 +from rest_framework.compat import ip_address_validators from rest_framework.exceptions import ErrorDetail, ValidationError from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, json, representation @@ -866,7 +867,7 @@ def __init__(self, protocol='both', **kwargs): self.protocol = protocol.lower() self.unpack_ipv4 = (self.protocol == 'both') super().__init__(**kwargs) - validators, error_message = ip_address_validators(protocol, self.unpack_ipv4) + validators = ip_address_validators(protocol, self.unpack_ipv4) self.validators.extend(validators) def to_internal_value(self, data): From c4f9d432dde9027f1918d3014c013930eee3b788 Mon Sep 17 00:00:00 2001 From: Viicos <65306057+Viicos@users.noreply.github.com> Date: Thu, 18 Jan 2024 15:35:57 +0100 Subject: [PATCH 772/907] Update 3.11-announcement.md (#9227) --- docs/community/3.11-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/3.11-announcement.md b/docs/community/3.11-announcement.md index 5a1f2c8e30..2fc37a7647 100644 --- a/docs/community/3.11-announcement.md +++ b/docs/community/3.11-announcement.md @@ -64,7 +64,7 @@ In some circumstances a Validator class or a Default class may need to access th * Uniqueness validators need to be able to determine the name of the field to which they are applied, in order to run an appropriate database query. * The `CurrentUserDefault` needs to be able to determine the context with which the serializer was instantiated, in order to return the current user instance. -Previous our approach to this was that implementations could include a `set_context` method, which would be called prior to validation. However this approach had issues with potential race conditions. We have now move this approach into a pending deprecation state. It will continue to function, but will be escalated to a deprecated state in 3.12, and removed entirely in 3.13. +Our previous approach to this was that implementations could include a `set_context` method, which would be called prior to validation. However this approach had issues with potential race conditions. We have now move this approach into a pending deprecation state. It will continue to function, but will be escalated to a deprecated state in 3.12, and removed entirely in 3.13. Instead, validators or defaults which require the serializer context, should include a `requires_context = True` attribute on the class. From 552a67acde5eb13f84ec9dbdf4363313cdfda2f0 Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Fri, 19 Jan 2024 18:10:56 +0800 Subject: [PATCH 773/907] Skip coreapi tests in GenerateSchemaTests if coreapi is not installed (#9229) --- tests/schemas/test_managementcommand.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/schemas/test_managementcommand.py b/tests/schemas/test_managementcommand.py index 4104d4ca62..c0713f43ce 100644 --- a/tests/schemas/test_managementcommand.py +++ b/tests/schemas/test_managementcommand.py @@ -8,7 +8,7 @@ from django.test.utils import override_settings from django.urls import path -from rest_framework.compat import uritemplate, yaml +from rest_framework.compat import coreapi, uritemplate, yaml from rest_framework.management.commands import generateschema from rest_framework.utils import formatting, json from rest_framework.views import APIView @@ -91,6 +91,7 @@ def test_writes_schema_to_file_on_parameter(self): os.remove(path) @pytest.mark.skipif(yaml is None, reason='PyYAML is required.') + @pytest.mark.skipif(coreapi is None, reason='coreapi is required.') @override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) def test_coreapi_renders_default_schema_with_custom_title_url_and_description(self): expected_out = """info: @@ -113,6 +114,7 @@ def test_coreapi_renders_default_schema_with_custom_title_url_and_description(se self.assertIn(formatting.dedent(expected_out), self.out.getvalue()) + @pytest.mark.skipif(coreapi is None, reason='coreapi is required.') @override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) def test_coreapi_renders_openapi_json_schema(self): expected_out = { @@ -142,6 +144,7 @@ def test_coreapi_renders_openapi_json_schema(self): self.assertDictEqual(out_json, expected_out) + @pytest.mark.skipif(coreapi is None, reason='coreapi is required.') @override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) def test_renders_corejson_schema(self): expected_out = """{"_type":"document","":{"list":{"_type":"link","url":"/","action":"get"}}}""" From f85d8cb81a87eba525396ad944861a86914cce20 Mon Sep 17 00:00:00 2001 From: Kien Dang Date: Thu, 25 Jan 2024 05:44:46 +0800 Subject: [PATCH 774/907] Fix comment syntax error in doc that fails pre-commit blacken-docs (#9225) * Fix comment syntax in doc * Pre-commit --- docs/topics/browsable-api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/browsable-api.md b/docs/topics/browsable-api.md index 67bc978908..9a95edfc60 100644 --- a/docs/topics/browsable-api.md +++ b/docs/topics/browsable-api.md @@ -21,8 +21,8 @@ To quickly add authentication to the browesable api, add a routes named `"login" ```python urlpatterns = [ - // ... - url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) + # ... + url(r"^api-auth/", include("rest_framework.urls", namespace="rest_framework")) ] ``` From 74689b1f44fd3d5fd3f220b6f1e2bae650eba3d8 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 24 Jan 2024 22:47:46 +0100 Subject: [PATCH 775/907] pre-commit autoupdate (#9232) --- .pre-commit-config.yaml | 8 ++++---- docs/community/3.5-announcement.md | 8 ++------ rest_framework/decorators.py | 2 +- tests/test_throttling.py | 2 +- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 64ec37770f..c37da7449f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -9,17 +9,17 @@ repos: - id: check-symlinks - id: check-toml - repo: https://github.com/pycqa/isort - rev: 5.12.0 + rev: 5.13.2 hooks: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 3.9.0 + rev: 7.0.0 hooks: - id: flake8 additional_dependencies: - flake8-tidy-imports - repo: https://github.com/adamchainz/blacken-docs - rev: 1.13.0 + rev: 1.16.0 hooks: - id: blacken-docs exclude: ^(?!docs).*$ diff --git a/docs/community/3.5-announcement.md b/docs/community/3.5-announcement.md index 91bfce4286..43a628dd4e 100644 --- a/docs/community/3.5-announcement.md +++ b/docs/community/3.5-announcement.md @@ -64,14 +64,10 @@ from rest_framework.schemas import get_schema_view from rest_framework_swagger.renderers import OpenAPIRenderer, SwaggerUIRenderer schema_view = get_schema_view( - title='Example API', - renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer] + title="Example API", renderer_classes=[OpenAPIRenderer, SwaggerUIRenderer] ) -urlpatterns = [ - path('swagger/', schema_view), - ... -] +urlpatterns = [path("swagger/", schema_view), ...] ``` There have been a large number of fixes to the schema generation. These should diff --git a/rest_framework/decorators.py b/rest_framework/decorators.py index 3b572c09ef..864ff73958 100644 --- a/rest_framework/decorators.py +++ b/rest_framework/decorators.py @@ -36,7 +36,7 @@ def decorator(func): # WrappedAPIView.__doc__ = func.doc <--- Not possible to do this # api_view applied without (method_names) - assert not(isinstance(http_method_names, types.FunctionType)), \ + assert not isinstance(http_method_names, types.FunctionType), \ '@api_view missing list of allowed HTTP methods' # api_view applied with eg. string instead of list of strings diff --git a/tests/test_throttling.py b/tests/test_throttling.py index d5a61232d9..be9decebc5 100644 --- a/tests/test_throttling.py +++ b/tests/test_throttling.py @@ -192,7 +192,7 @@ def ensure_response_header_contains_proper_throttle_field(self, view, expected_h if expect is not None: assert response['Retry-After'] == expect else: - assert not'Retry-After' in response + assert 'Retry-After' not in response def test_seconds_fields(self): """ From ab694eccdeb94faeb8c36647f6b5d2e07729fbdb Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 24 Jan 2024 23:17:01 +0100 Subject: [PATCH 776/907] Fix typo (#9231) --- docs/api-guide/fields.md | 2 +- docs/community/release-notes.md | 6 +++--- rest_framework/filters.py | 4 ++-- rest_framework/schemas/generators.py | 4 ++-- rest_framework/schemas/openapi.py | 2 +- rest_framework/settings.py | 2 +- tests/schemas/test_openapi.py | 2 +- tests/test_response.py | 2 +- tests/test_serializer_nested.py | 4 ++-- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 0766fe3ef6..ea944e1fff 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -303,7 +303,7 @@ Corresponds to `django.db.models.fields.DecimalField`. * `min_value` Validate that the number provided is no less than this value. * `localize` Set to `True` to enable localization of input and output based on the current locale. This will also force `coerce_to_string` to `True`. Defaults to `False`. Note that data formatting is enabled if you have set `USE_L10N=True` in your settings file. * `rounding` Sets the rounding mode used when quantizing to the configured precision. Valid values are [`decimal` module rounding modes][python-decimal-rounding-modes]. Defaults to `None`. -* `normalize_output` Will normalize the decimal value when serialized. This will strip all trailing zeroes and change the value's precision to the minimum required precision to be able to represent the value without loosing data. Defaults to `False`. +* `normalize_output` Will normalize the decimal value when serialized. This will strip all trailing zeroes and change the value's precision to the minimum required precision to be able to represent the value without losing data. Defaults to `False`. #### Example usage diff --git a/docs/community/release-notes.md b/docs/community/release-notes.md index d8eadeb32e..71f29e5c05 100644 --- a/docs/community/release-notes.md +++ b/docs/community/release-notes.md @@ -47,7 +47,7 @@ Date: 22nd September 2022 * Stop calling `set_context` on Validators. [[#8589](https://github.com/encode/django-rest-framework/pull/8589)] * Return `NotImplemented` from `ErrorDetails.__ne__`. [[#8538](https://github.com/encode/django-rest-framework/pull/8538)] * Don't evaluate `DateTimeField.default_timezone` when a custom timezone is set. [[#8531](https://github.com/encode/django-rest-framework/pull/8531)] -* Make relative URLs clickable in Browseable API. [[#8464](https://github.com/encode/django-rest-framework/pull/8464)] +* Make relative URLs clickable in Browsable API. [[#8464](https://github.com/encode/django-rest-framework/pull/8464)] * Support `ManyRelatedField` falling back to the default value when the attribute specified by dot notation doesn't exist. Matches `ManyRelatedField.get_attribute` to `Field.get_attribute`. [[#7574](https://github.com/encode/django-rest-framework/pull/7574)] * Make `schemas.openapi.get_reference` public. [[#7515](https://github.com/encode/django-rest-framework/pull/7515)] * Make `ReturnDict` support `dict` union operators on Python 3.9 and later. [[#8302](https://github.com/encode/django-rest-framework/pull/8302)] @@ -65,7 +65,7 @@ Date: 15th December 2021 Date: 13th December 2021 -* Django 4.0 compatability. [#8178] +* Django 4.0 compatibility. [#8178] * Add `max_length` and `min_length` options to `ListSerializer`. [#8165] * Add `get_request_serializer` and `get_response_serializer` hooks to `AutoSchema`. [#7424] * Fix OpenAPI representation of null-able read only fields. [#8116] @@ -954,7 +954,7 @@ See the [release announcement][3.6-release]. * Prevent raising exception when limit is 0. ([#4098][gh4098]) * TokenAuthentication: Allow custom keyword in the header. ([#4097][gh4097]) * Handle incorrectly padded HTTP basic auth header. ([#4090][gh4090]) -* LimitOffset pagination crashes Browseable API when limit=0. ([#4079][gh4079]) +* LimitOffset pagination crashes Browsable API when limit=0. ([#4079][gh4079]) * Fixed DecimalField arbitrary precision support. ([#4075][gh4075]) * Added support for custom CSRF cookie names. ([#4049][gh4049]) * Fix regression introduced by #4035. ([#4041][gh4041]) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 065e72f94a..86effe24e3 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -21,14 +21,14 @@ def search_smart_split(search_terms): - """generator that first splits string by spaces, leaving quoted phrases togheter, + """generator that first splits string by spaces, leaving quoted phrases together, then it splits non-quoted phrases by commas. """ for term in smart_split(search_terms): # trim commas to avoid bad matching for quoted phrases term = term.strip(',') if term.startswith(('"', "'")) and term[0] == term[-1]: - # quoted phrases are kept togheter without any other split + # quoted phrases are kept together without any other split yield unescape_string_literal(term) else: # non-quoted tokens are split by comma, keeping only non-empty ones diff --git a/rest_framework/schemas/generators.py b/rest_framework/schemas/generators.py index d3c6446aa4..f59e25c213 100644 --- a/rest_framework/schemas/generators.py +++ b/rest_framework/schemas/generators.py @@ -102,12 +102,12 @@ def get_path_from_regex(self, path_regex): Given a URL conf regex, return a URI template string. """ # ???: Would it be feasible to adjust this such that we generate the - # path, plus the kwargs, plus the type from the convertor, such that we + # path, plus the kwargs, plus the type from the converter, such that we # could feed that straight into the parameter schema object? path = simplify_regex(path_regex) - # Strip Django 2.0 convertors as they are incompatible with uritemplate format + # Strip Django 2.0 converters as they are incompatible with uritemplate format return re.sub(_PATH_PARAMETER_COMPONENT_RE, r'{\g}', path) def should_include_endpoint(self, path, callback): diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index ad19f6df85..c154494e2e 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -84,7 +84,7 @@ def get_schema(self, request=None, public=False): continue if components_schemas[k] == components[k]: continue - warnings.warn('Schema component "{}" has been overriden with a different value.'.format(k)) + warnings.warn('Schema component "{}" has been overridden with a different value.'.format(k)) components_schemas.update(components) diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 96b6645742..b0d7bacecc 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -116,7 +116,7 @@ 'COERCE_DECIMAL_TO_STRING': True, 'UPLOADED_FILES_USE_URL': True, - # Browseable API + # Browsable API 'HTML_SELECT_CUTOFF': 1000, 'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...", diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index 6645cac728..a168cb4661 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -1347,7 +1347,7 @@ def test_duplicate_component_name(self): assert len(w) == 1 assert issubclass(w[-1].category, UserWarning) - assert 'has been overriden with a different value.' in str(w[-1].message) + assert 'has been overridden with a different value.' in str(w[-1].message) assert 'components' in schema assert 'schemas' in schema['components'] diff --git a/tests/test_response.py b/tests/test_response.py index cab19a1eb8..00b5691b0f 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -132,7 +132,7 @@ class HTMLNewModelView(generics.ListCreateAPIView): ] -# TODO: Clean tests bellow - remove duplicates with above, better unit testing, ... +# TODO: Clean tests below - remove duplicates with above, better unit testing, ... @override_settings(ROOT_URLCONF='tests.test_response') class RendererIntegrationTests(TestCase): """ diff --git a/tests/test_serializer_nested.py b/tests/test_serializer_nested.py index 986972a65a..b7195494c4 100644 --- a/tests/test_serializer_nested.py +++ b/tests/test_serializer_nested.py @@ -223,7 +223,7 @@ def test_json_validate(self): input_data = {} serializer = self.Serializer(data=input_data) - # request is empty, therefor 'nested' should not be in serializer.data + # request is empty, therefore 'nested' should not be in serializer.data assert serializer.is_valid() assert 'nested' not in serializer.validated_data @@ -237,7 +237,7 @@ def test_multipart_validate(self): input_data = QueryDict('') serializer = self.Serializer(data=input_data) - # the querydict is empty, therefor 'nested' should not be in serializer.data + # the querydict is empty, therefore 'nested' should not be in serializer.data assert serializer.is_valid() assert 'nested' not in serializer.validated_data From 2797c0f216288bd7ca82e9e32e5f7d699bac74df Mon Sep 17 00:00:00 2001 From: Marcelo Galigniana Date: Thu, 25 Jan 2024 12:01:06 +0100 Subject: [PATCH 777/907] Add missing import in /api-guide/viewsets/ example (#9235) --- docs/api-guide/viewsets.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 41ba1743c3..43007e95df 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -311,7 +311,7 @@ You may need to provide custom `ViewSet` classes that do not have the full set o To create a base viewset class that provides `create`, `list` and `retrieve` operations, inherit from `GenericViewSet`, and mixin the required actions: - from rest_framework import mixins + from rest_framework import mixins, viewsets class CreateListRetrieveViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, From 41edb3b9dde2105b9fb015ce6b8171e7822baa30 Mon Sep 17 00:00:00 2001 From: Yuekui Date: Fri, 26 Jan 2024 02:36:18 -0800 Subject: [PATCH 778/907] Avoid unnecessary unique together checking (#9154) --- rest_framework/validators.py | 17 +++++++++++++---- tests/test_validators.py | 18 +++++++++++++++++- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/rest_framework/validators.py b/rest_framework/validators.py index 07ad11b47b..3f09c15cd6 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -160,10 +160,19 @@ def __call__(self, attrs, serializer): queryset = self.exclude_current_instance(attrs, queryset, serializer.instance) # Ignore validation if any field is None - checked_values = [ - value for field, value in attrs.items() if field in self.fields - ] - if None not in checked_values and qs_exists(queryset): + if serializer.instance is None: + checked_values = [ + value for field, value in attrs.items() if field in self.fields + ] + else: + # Ignore validation if all field values are unchanged + checked_values = [ + value + for field, value in attrs.items() + if field in self.fields and value != getattr(serializer.instance, field) + ] + + if checked_values and None not in checked_values and qs_exists(queryset): field_names = ', '.join(self.fields) message = self.message.format(field_names=field_names) raise ValidationError(message, code='unique') diff --git a/tests/test_validators.py b/tests/test_validators.py index 1cf42ed07e..49b0db63a2 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -1,5 +1,5 @@ import datetime -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import pytest from django.db import DataError, models @@ -447,6 +447,22 @@ def test_do_not_ignore_validation_for_null_fields(self): serializer = NullUniquenessTogetherSerializer(data=data) assert not serializer.is_valid() + def test_ignore_validation_for_unchanged_fields(self): + """ + If all fields in the unique together constraint are unchanged, + then the instance should skip uniqueness validation. + """ + instance = UniquenessTogetherModel.objects.create( + race_name="Paris Marathon", position=1 + ) + data = {"race_name": "Paris Marathon", "position": 1} + serializer = UniquenessTogetherSerializer(data=data, instance=instance) + with patch( + "rest_framework.validators.qs_exists" + ) as mock: + assert serializer.is_valid() + assert not mock.called + def test_filter_queryset_do_not_skip_existing_attribute(self): """ filter_queryset should add value from existing instance attribute From 5ad467aa2c76a7cfb6218f6dbe22e314131bde3c Mon Sep 17 00:00:00 2001 From: iamkorniichuk <119371190+iamkorniichuk@users.noreply.github.com> Date: Mon, 29 Jan 2024 10:30:29 +0200 Subject: [PATCH 779/907] Remove read_only deprecated behaviour from documentation (#9239) --- docs/api-guide/validators.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index 2a93e4cdca..e181d4c61f 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -173,11 +173,9 @@ If you want the date field to be entirely hidden from the user, then use `Hidden # Advanced field defaults Validators that are applied across multiple fields in the serializer can sometimes require a field input that should not be provided by the API client, but that *is* available as input to the validator. +For this purposes use `HiddenField`. This field will be present in `validated_data` but *will not* be used in the serializer output representation. -Two patterns that you may want to use for this sort of validation include: - -* Using `HiddenField`. This field will be present in `validated_data` but *will not* be used in the serializer output representation. -* Using a standard field with `read_only=True`, but that also includes a `default=…` argument. This field *will* be used in the serializer output representation, but cannot be set directly by the user. +**Note:** Using a `read_only=True` field is excluded from writable fields so it won't use a `default=…` argument. Look [3.8 announcement](https://www.django-rest-framework.org/community/3.8-announcement/#altered-the-behaviour-of-read_only-plus-default-on-field). REST framework includes a couple of defaults that may be useful in this context. @@ -189,7 +187,7 @@ A default class that can be used to represent the current user. In order to use default=serializers.CurrentUserDefault() ) -#### CreateOnlyDefault +#### CreateOnlyDefault A default class that can be used to *only set a default argument during create operations*. During updates the field is omitted. From df89f32b88022828ca5d1b5e04be36d54f506e6c Mon Sep 17 00:00:00 2001 From: Victorien <65306057+Viicos@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:02:02 +0100 Subject: [PATCH 780/907] Fix typo in 3.14 announcement (#9255) --- docs/community/3.14-announcement.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/community/3.14-announcement.md b/docs/community/3.14-announcement.md index e7b10ede19..991c6fc5af 100644 --- a/docs/community/3.14-announcement.md +++ b/docs/community/3.14-announcement.md @@ -28,10 +28,10 @@ Our requirements are now: * Python 3.6+ * Django 4.1, 4.0, 3.2, 3.1, 3.0 -## `raise_exceptions` argument for `is_valid` is now keyword-only. +## `raise_exception` argument for `is_valid` is now keyword-only. Calling `serializer_instance.is_valid(True)` is no longer acceptable syntax. -If you'd like to use the `raise_exceptions` argument, you must use it as a +If you'd like to use the `raise_exception` argument, you must use it as a keyword argument. See Pull Request [#7952](https://github.com/encode/django-rest-framework/pull/7952) for more details. From 2ef77b18335322f10c8c6160b6f7794ff6bc92b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=9Fuayip=20=C3=BCz=C3=BClmez?= <17948971+realsuayip@users.noreply.github.com> Date: Tue, 20 Feb 2024 01:28:04 +0300 Subject: [PATCH 781/907] Use POST method instead of GET to perform logout in browsable API (#9208) * Use POST method instead of GET to perform logout in browsable API * Add a test that checks the presence of the logout form --- rest_framework/templates/rest_framework/admin.html | 2 +- rest_framework/templates/rest_framework/base.html | 2 +- rest_framework/templatetags/rest_framework.py | 13 +++++++++---- tests/browsable_api/test_browsable_api.py | 6 ++++++ 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/rest_framework/templates/rest_framework/admin.html b/rest_framework/templates/rest_framework/admin.html index 7c6917e2d3..a5edf529ed 100644 --- a/rest_framework/templates/rest_framework/admin.html +++ b/rest_framework/templates/rest_framework/admin.html @@ -42,7 +42,7 @@

    Try the homepage, or search the documentation.