From 311d315a739f4d1d02e87a09de0bbf9e7b0cee46 Mon Sep 17 00:00:00 2001 From: Xavier Ordoquy Date: Wed, 8 Oct 2014 08:33:28 +0200 Subject: [PATCH 001/128] Reverted 59d0a0387d907260ef8f91bbbca618831abd75a3 and fixed the tests --- rest_framework/response.py | 4 ---- tests/test_renderers.py | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/rest_framework/response.py b/rest_framework/response.py index 0a7d313f4..d6ca1aad4 100644 --- a/rest_framework/response.py +++ b/rest_framework/response.py @@ -5,7 +5,6 @@ it is initialized with unrendered data, instead of a pre-rendered string. The appropriate renderer is called during Django's template response rendering. """ from __future__ import unicode_literals -import django from django.core.handlers.wsgi import STATUS_CODE_TEXT from django.template.response import SimpleTemplateResponse from django.utils import six @@ -16,9 +15,6 @@ class Response(SimpleTemplateResponse): An HttpResponse that allows its data to be rendered into arbitrary media types. """ - # TODO: remove that once Django 1.3 isn't supported - if django.VERSION >= (1, 4): - rendering_attrs = SimpleTemplateResponse.rendering_attrs + ['_closable_objects'] def __init__(self, data=None, status=None, template_name=None, headers=None, diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 91244e261..b922ec291 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -643,7 +643,7 @@ class CacheRenderTest(TestCase): """ method = getattr(self.client, http_method) resp = method(url) - del resp.client, resp.request + del resp.client, resp.request, resp._closable_objects try: del resp.wsgi_request except AttributeError: From 67735687b297e66c7cfe61614040efcd9763f1f1 Mon Sep 17 00:00:00 2001 From: Doug Beck Date: Tue, 18 Nov 2014 01:26:23 -0500 Subject: [PATCH 002/128] Ensure `_resolve_model` does not return `None` --- rest_framework/utils/model_meta.py | 6 +++++- tests/test_utils.py | 33 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/rest_framework/utils/model_meta.py b/rest_framework/utils/model_meta.py index 82361edf9..54f9310db 100644 --- a/rest_framework/utils/model_meta.py +++ b/rest_framework/utils/model_meta.py @@ -43,7 +43,11 @@ def _resolve_model(obj): """ if isinstance(obj, six.string_types) and len(obj.split('.')) == 2: app_name, model_name = obj.split('.') - return models.get_model(app_name, model_name) + resolved_model = models.get_model(app_name, model_name) + if not resolved_model: + raise ValueError("Django did not return a model for " + "{0}.{1}".format(app_name, model_name)) + return resolved_model elif inspect.isclass(obj) and issubclass(obj, models.Model): return obj raise ValueError("{0} is not a Django model".format(obj)) diff --git a/tests/test_utils.py b/tests/test_utils.py index 96c5f997e..0bdb36bba 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -7,6 +7,8 @@ from rest_framework.utils.breadcrumbs import get_breadcrumbs from rest_framework.views import APIView from tests.models import BasicModel +import rest_framework.utils.model_meta + class Root(APIView): pass @@ -130,3 +132,34 @@ class ResolveModelTests(TestCase): def test_resolve_improper_string_representation(self): with self.assertRaises(ValueError): _resolve_model('BasicModel') + + +class ResolveModelWithPatchedDjangoTests(TestCase): + """ + Test coverage for when Django's `get_model` returns `None`. + + Under certain circumstances Django may return `None` with `get_model`: + http://git.io/get-model-source + + It usually happens with circular imports so it is important that DRF + excepts early, otherwise fault happens downstream and is much more + difficult to debug. + + """ + + def setUp(self): + """Monkeypatch get_model.""" + self.get_model = rest_framework.utils.model_meta.models.get_model + + def get_model(app_label, model_name): + return None + + rest_framework.utils.model_meta.models.get_model = get_model + + def tearDown(self): + """Revert monkeypatching.""" + rest_framework.utils.model_meta.models.get_model = self.get_model + + def test_blows_up_if_model_does_not_resolve(self): + with self.assertRaises(ValueError): + _resolve_model('tests.BasicModel') From c0d356edaa425868dbcdb29c5d2f8e88f177f604 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 18 Nov 2014 15:42:52 +0000 Subject: [PATCH 003/128] Fix byte decode error rendering 'display_name' with OPTIONS. Closes #2084 --- rest_framework/metadata.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_framework/metadata.py b/rest_framework/metadata.py index 90d3f2e01..de829d003 100644 --- a/rest_framework/metadata.py +++ b/rest_framework/metadata.py @@ -121,7 +121,10 @@ class SimpleMetadata(BaseMetadata): if hasattr(field, 'choices'): field_info['choices'] = [ - {'value': choice_value, 'display_name': choice_name} + { + 'value': choice_value, + 'display_name': force_text(choice_name, strings_only=True) + } for choice_value, choice_name in field.choices.items() ] From f269826a7d8a980536ffbce369be369c4df58d23 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 18 Nov 2014 16:26:45 +0000 Subject: [PATCH 004/128] Note on 3.0 beta --- docs/topics/3.0-announcement.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 90cbda4d6..06fdc9fdc 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -1,10 +1,12 @@ ## Pre-release notes: -The 3.0 release is now ready for some tentative testing and upgrades for early adopters. You can install the development version directly from GitHub like so: +The 3.0 release is now in beta and ready for final testing. You can install the development version directly from GitHub like so: - pip install https://github.com/tomchristie/django-rest-framework/archive/master.zip + pip install https://github.com/tomchristie/django-rest-framework/archive/3.0-beta.zip -See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-framework/pull/1800) for more details on remaining work. +Currently the only known remaining blockers are documentation issues and tickets. Any critical bugs raised in the next week or two will be resolved for the 3.0 release, but otherwise consider this as code-complete. + +Please work through this document throughly in order to understand the API differences that exist between 2.4 and 3.0. **Your feedback on the upgrade process and 3.0 changes is hugely important!** From f573aaee4eabb9bf677c350219c8feb2333a6fa1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 18 Nov 2014 17:25:05 +0000 Subject: [PATCH 005/128] List serializer no explicitly renders as 'not supported for HTML input' --- .../templates/rest_framework/horizontal/list_fieldset.html | 3 +++ .../templates/rest_framework/inline/list_fieldset.html | 1 + .../templates/rest_framework/vertical/list_fieldset.html | 1 + 3 files changed, 5 insertions(+) create mode 100644 rest_framework/templates/rest_framework/inline/list_fieldset.html diff --git a/rest_framework/templates/rest_framework/horizontal/list_fieldset.html b/rest_framework/templates/rest_framework/horizontal/list_fieldset.html index a30514c68..a9ff04a62 100644 --- a/rest_framework/templates/rest_framework/horizontal/list_fieldset.html +++ b/rest_framework/templates/rest_framework/horizontal/list_fieldset.html @@ -5,9 +5,12 @@ {{ field.label }} {% endif %} + +

Lists are not currently supported in HTML input.

diff --git a/rest_framework/templates/rest_framework/inline/list_fieldset.html b/rest_framework/templates/rest_framework/inline/list_fieldset.html new file mode 100644 index 000000000..2ae56d7cd --- /dev/null +++ b/rest_framework/templates/rest_framework/inline/list_fieldset.html @@ -0,0 +1 @@ +Lists are not currently supported in HTML input. diff --git a/rest_framework/templates/rest_framework/vertical/list_fieldset.html b/rest_framework/templates/rest_framework/vertical/list_fieldset.html index 74bbf4483..1d86c7f2b 100644 --- a/rest_framework/templates/rest_framework/vertical/list_fieldset.html +++ b/rest_framework/templates/rest_framework/vertical/list_fieldset.html @@ -4,4 +4,5 @@ {% for field_item in field.value.field_items.values() %} {{ renderer.render_field(field_item, layout=layout) }} {% endfor %} --> +

Lists are not currently supported in HTML input.

From 5cda5ad18e2871c0e0d80c1aed448b56301db8b3 Mon Sep 17 00:00:00 2001 From: Josh Kalderimis Date: Tue, 18 Nov 2014 13:06:08 -0500 Subject: [PATCH 006/128] Use the new build env on Travis faster vms, more ram, more cpu, better vm boot times official docs coming soon --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4c06acf59..9b2e47383 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: python python: 2.7 +sudo: false + env: - TOX_ENV=flake8 - TOX_ENV=py3.4-django1.7 From e49d22dbda5ac889ab89f277e17752c840819de2 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 19 Nov 2014 09:31:26 +0000 Subject: [PATCH 007/128] Allow blank choices to render. Closes #2071. --- rest_framework/fields.py | 2 ++ tests/test_fields.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 36afe7a99..bb43708dc 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -947,6 +947,8 @@ class ChoiceField(Field): self.fail('invalid_choice', input=data) def to_representation(self, value): + if value in ('', None): + return value return self.choice_strings_to_values[six.text_type(value)] diff --git a/tests/test_fields.py b/tests/test_fields.py index 5db381acc..135256320 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -793,7 +793,8 @@ class TestChoiceField(FieldValues): 'amazing': ['`amazing` is not a valid choice.'] } outputs = { - 'good': 'good' + 'good': 'good', + '': '' } field = serializers.ChoiceField( choices=[ From 6cb6510132b319c96b28bea732032aaf2d495895 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 19 Nov 2014 12:15:05 +0000 Subject: [PATCH 008/128] Use translatable error strings. Refs #2063. --- rest_framework/exceptions.py | 74 +++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index 0b06d6e60..dbab66842 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -5,7 +5,11 @@ 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 + +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy from rest_framework import status +from rest_framework.compat import force_text import math @@ -15,10 +19,13 @@ class APIException(Exception): Subclasses should provide `.status_code` and `.default_detail` properties. """ status_code = status.HTTP_500_INTERNAL_SERVER_ERROR - default_detail = 'A server error occured' + default_detail = _('A server error occured') def __init__(self, detail=None): - self.detail = detail or self.default_detail + if detail is not None: + self.detail = force_text(detail) + else: + self.detail = force_text(self.default_detail) def __str__(self): return self.detail @@ -31,6 +38,19 @@ class APIException(Exception): # from rest_framework import serializers # raise serializers.ValidationError('Value was invalid') +def force_text_recursive(data): + if isinstance(data, list): + return [ + force_text_recursive(item) for item in data + ] + elif isinstance(data, dict): + return dict([ + (key, force_text_recursive(value)) + for key, value in data.items() + ]) + return force_text(data) + + class ValidationError(APIException): status_code = status.HTTP_400_BAD_REQUEST @@ -39,7 +59,7 @@ class ValidationError(APIException): # The details should always be coerced to a list if not already. if not isinstance(detail, dict) and not isinstance(detail, list): detail = [detail] - self.detail = detail + self.detail = force_text_recursive(detail) def __str__(self): return str(self.detail) @@ -47,59 +67,77 @@ class ValidationError(APIException): class ParseError(APIException): status_code = status.HTTP_400_BAD_REQUEST - default_detail = 'Malformed request.' + default_detail = _('Malformed request.') class AuthenticationFailed(APIException): status_code = status.HTTP_401_UNAUTHORIZED - default_detail = 'Incorrect authentication credentials.' + default_detail = _('Incorrect authentication credentials.') class NotAuthenticated(APIException): status_code = status.HTTP_401_UNAUTHORIZED - default_detail = 'Authentication credentials were not provided.' + default_detail = _('Authentication credentials were not provided.') class PermissionDenied(APIException): status_code = status.HTTP_403_FORBIDDEN - default_detail = 'You do not have permission to perform this action.' + default_detail = _('You do not have permission to perform this action.') class MethodNotAllowed(APIException): status_code = status.HTTP_405_METHOD_NOT_ALLOWED - default_detail = "Method '%s' not allowed." + default_detail = _("Method '%s' not allowed.") def __init__(self, method, detail=None): - self.detail = detail or (self.default_detail % method) + if detail is not None: + self.detail = force_text(detail) + else: + self.detail = force_text(self.default_detail) % method class NotAcceptable(APIException): status_code = status.HTTP_406_NOT_ACCEPTABLE - default_detail = "Could not satisfy the request Accept header" + default_detail = _('Could not satisfy the request Accept header') def __init__(self, detail=None, available_renderers=None): - self.detail = detail or self.default_detail + if detail is not None: + self.detail = force_text(detail) + else: + self.detail = force_text(self.default_detail) self.available_renderers = available_renderers class UnsupportedMediaType(APIException): status_code = status.HTTP_415_UNSUPPORTED_MEDIA_TYPE - default_detail = "Unsupported media type '%s' in request." + default_detail = _("Unsupported media type '%s' in request.") def __init__(self, media_type, detail=None): - self.detail = detail or (self.default_detail % media_type) + if detail is not None: + self.detail = force_text(detail) + else: + self.detail = force_text(self.default_detail) % media_type class Throttled(APIException): status_code = status.HTTP_429_TOO_MANY_REQUESTS - default_detail = 'Request was throttled.' - extra_detail = " Expected available in %d second%s." + default_detail = _('Request was throttled.') + extra_detail = ungettext_lazy( + 'Expected available in %(wait)d second.', + 'Expected available in %(wait)d seconds.', + 'wait' + ) def __init__(self, wait=None, detail=None): + if detail is not None: + self.detail = force_text(detail) + else: + self.detail = force_text(self.default_detail) + if wait is None: - self.detail = detail or self.default_detail self.wait = None else: - format = (detail or self.default_detail) + self.extra_detail - self.detail = format % (wait, wait != 1 and 's' or '') self.wait = math.ceil(wait) + self.detail += ' ' + force_text( + self.extra_detail % {'wait': self.wait} + ) From 8586290df80ac8448d71cdb3326bc822c399cad1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 19 Nov 2014 13:55:10 +0000 Subject: [PATCH 009/128] Apply defaults and requiredness to unique_together fields. Closes #2092. --- rest_framework/serializers.py | 79 +++++++++++++++++++++-------------- rest_framework/validators.py | 14 ++++++- tests/test_validators.py | 4 +- 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 84282cdb2..2e34dbe75 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -720,49 +720,60 @@ class ModelSerializer(Serializer): # Determine if we need any additional `HiddenField` or extra keyword # arguments to deal with `unique_for` dates that are required to # be in the input data in order to validate it. - unique_fields = {} + hidden_fields = {} + for model_field_name, field_name in model_field_mapping.items(): try: model_field = model._meta.get_field(model_field_name) except FieldDoesNotExist: continue - # Deal with each of the `unique_for_*` cases. - for date_field_name in ( + # Include each of the `unique_for_*` field names. + unique_constraint_names = set([ model_field.unique_for_date, model_field.unique_for_month, model_field.unique_for_year - ): - if date_field_name is None: - continue + ]) + unique_constraint_names -= set([None]) - # Get the model field that is refered too. - date_field = model._meta.get_field(date_field_name) + # Include each of the `unique_together` field names, + # so long as all the field names are included on the serializer. + for parent_class in [model] + list(model._meta.parents.keys()): + for unique_together_list in parent_class._meta.unique_together: + if set(fields).issuperset(set(unique_together_list)): + unique_constraint_names |= set(unique_together_list) - if date_field.auto_now_add: - default = CreateOnlyDefault(timezone.now) - elif date_field.auto_now: - default = timezone.now - elif date_field.has_default(): - default = model_field.default + # Now we have all the field names that have uniqueness constraints + # applied, we can add the extra 'required=...' or 'default=...' + # arguments that are appropriate to these fields, or add a `HiddenField` for it. + for unique_constraint_name in unique_constraint_names: + # Get the model field that is refered too. + unique_constraint_field = model._meta.get_field(unique_constraint_name) + + if getattr(unique_constraint_field, 'auto_now_add', None): + default = CreateOnlyDefault(timezone.now) + elif getattr(unique_constraint_field, 'auto_now', None): + default = timezone.now + elif unique_constraint_field.has_default(): + default = model_field.default + else: + default = empty + + if unique_constraint_name in model_field_mapping: + # The corresponding field is present in the serializer + if unique_constraint_name not in extra_kwargs: + extra_kwargs[unique_constraint_name] = {} + if default is empty: + if 'required' not in extra_kwargs[unique_constraint_name]: + extra_kwargs[unique_constraint_name]['required'] = True else: - default = empty - - if date_field_name in model_field_mapping: - # The corresponding date field is present in the serializer - if date_field_name not in extra_kwargs: - extra_kwargs[date_field_name] = {} - if default is empty: - if 'required' not in extra_kwargs[date_field_name]: - extra_kwargs[date_field_name]['required'] = True - else: - if 'default' not in extra_kwargs[date_field_name]: - extra_kwargs[date_field_name]['default'] = default - else: - # The corresponding date field is not present in the, - # serializer. We have a default to use for the date, so - # add in a hidden field that populates it. - unique_fields[date_field_name] = HiddenField(default=default) + if 'default' not in extra_kwargs[unique_constraint_name]: + extra_kwargs[unique_constraint_name]['default'] = default + elif default is not empty: + # The corresponding field is not present in the, + # serializer. We have a default to use for it, so + # add in a hidden field that populates it. + hidden_fields[unique_constraint_name] = HiddenField(default=default) # Now determine the fields that should be included on the serializer. for field_name in fields: @@ -838,12 +849,16 @@ class ModelSerializer(Serializer): 'validators', 'queryset' ]: kwargs.pop(attr, None) + + if extras.get('default') and kwargs.get('required') is False: + kwargs.pop('required') + kwargs.update(extras) # Create the serializer field. ret[field_name] = field_cls(**kwargs) - for field_name, field in unique_fields.items(): + for field_name, field in hidden_fields.items(): ret[field_name] = field return ret diff --git a/rest_framework/validators.py b/rest_framework/validators.py index fa4f18474..7ca4e6a9b 100644 --- a/rest_framework/validators.py +++ b/rest_framework/validators.py @@ -93,6 +93,9 @@ class UniqueTogetherValidator: The `UniqueTogetherValidator` always forces an implied 'required' state on the fields it applies to. """ + if self.instance is not None: + return + missing = dict([ (field_name, self.missing_message) for field_name in self.fields @@ -105,8 +108,17 @@ class UniqueTogetherValidator: """ Filter the queryset to all instances matching the given attributes. """ + # If this is an update, then any unprovided field should + # have it's value set based on the existing instance attribute. + if self.instance is not None: + for field_name in self.fields: + if field_name not in attrs: + attrs[field_name] = getattr(self.instance, field_name) + + # Determine the filter keyword arguments and filter the queryset. filter_kwargs = dict([ - (field_name, attrs[field_name]) for field_name in self.fields + (field_name, attrs[field_name]) + for field_name in self.fields ]) return queryset.filter(**filter_kwargs) diff --git a/tests/test_validators.py b/tests/test_validators.py index 86614b109..1df0641c5 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -88,8 +88,8 @@ class TestUniquenessTogetherValidation(TestCase): expected = dedent(""" UniquenessTogetherSerializer(): id = IntegerField(label='ID', read_only=True) - race_name = CharField(max_length=100) - position = IntegerField() + race_name = CharField(max_length=100, required=True) + position = IntegerField(required=True) class Meta: validators = [] """) From 51b7033e4aeeefe19012a77b09a0b23d4a52a5bc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 19 Nov 2014 14:03:19 +0000 Subject: [PATCH 010/128] Further notes in 3.0 announcement --- docs/topics/3.0-announcement.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 90cbda4d6..3c73881ff 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -821,6 +821,11 @@ Or modify it on an individual serializer field, using the `coerce_to_string` key The default JSON renderer will return float objects for uncoerced `Decimal` instances. This allows you to easily switch between string or float representations for decimals depending on your API design needs. +## Miscellaneous notes. + +* The serializer `ChoiceField` does not currently display nested choices, as was the case in 2.4. This will be address as part of 3.1. +* Due to the new templated form rendering, the 'widget' option is no longer valid. This means there's no easy way of using third party "autocomplete" widgets for rendering select inputs that contain a large number of choices. You'll either need to use a regular select or a plain text input. We may consider addressing this in 3.1 or 3.2 if there's sufficient demand. + ## What's coming next. 3.0 is an incremental release, and there are several upcoming features that will build on the baseline improvements that it makes. From 851628107842a5bf84725247a42cae1ac90decf6 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 19 Nov 2014 14:40:30 +0000 Subject: [PATCH 011/128] Minor fix for #2092. --- rest_framework/serializers.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2e34dbe75..3189619e3 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -736,12 +736,12 @@ class ModelSerializer(Serializer): ]) unique_constraint_names -= set([None]) - # Include each of the `unique_together` field names, - # so long as all the field names are included on the serializer. - for parent_class in [model] + list(model._meta.parents.keys()): - for unique_together_list in parent_class._meta.unique_together: - if set(fields).issuperset(set(unique_together_list)): - unique_constraint_names |= set(unique_together_list) + # Include each of the `unique_together` field names, + # so long as all the field names are included on the serializer. + for parent_class in [model] + list(model._meta.parents.keys()): + for unique_together_list in parent_class._meta.unique_together: + if set(fields).issuperset(set(unique_together_list)): + unique_constraint_names |= set(unique_together_list) # Now we have all the field names that have uniqueness constraints # applied, we can add the extra 'required=...' or 'default=...' From 40b1ea919b00bd8bf1f53f8eb8bf33a498b237b8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 19 Nov 2014 14:51:49 +0000 Subject: [PATCH 012/128] Fix non-determanistic unique constraint mapping. Refs #2092. --- rest_framework/serializers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 3189619e3..ce0d14d6f 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -721,6 +721,7 @@ class ModelSerializer(Serializer): # arguments to deal with `unique_for` dates that are required to # be in the input data in order to validate it. hidden_fields = {} + unique_constraint_names = set() for model_field_name, field_name in model_field_mapping.items(): try: @@ -729,12 +730,13 @@ class ModelSerializer(Serializer): continue # Include each of the `unique_for_*` field names. - unique_constraint_names = set([ + unique_constraint_names |= set([ model_field.unique_for_date, model_field.unique_for_month, model_field.unique_for_year ]) - unique_constraint_names -= set([None]) + + unique_constraint_names -= set([None]) # Include each of the `unique_together` field names, # so long as all the field names are included on the serializer. From 928cbc640ef1db2c3fb96d352f69b8ffb66313e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raony=20Guimar=C3=A3es?= Date: Wed, 19 Nov 2014 13:53:36 -0200 Subject: [PATCH 013/128] small type --- 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 c2dc4bea9..1c398c1ff 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -19,7 +19,7 @@ Create a new Django project named `tutorial`, then start a new app called `quick pip install djangorestframework # Set up a new project with a single application - django-admin.py startproject tutorial . + django-admin.py startproject tutorial cd tutorial django-admin.py startapp quickstart cd .. From bde725541359de1fef785801fc5dad98e70a8e2f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 09:30:49 +0000 Subject: [PATCH 014/128] Fix non-determanistic default bug. Closes #2099. --- 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 ce0d14d6f..2d5c843e5 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -757,7 +757,7 @@ class ModelSerializer(Serializer): elif getattr(unique_constraint_field, 'auto_now', None): default = timezone.now elif unique_constraint_field.has_default(): - default = model_field.default + default = unique_constraint_field.default else: default = empty From 04d2635b24f0699ed8ed24436a58c203ad6a9a11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gil=20Gon=C3=A7alves?= Date: Thu, 20 Nov 2014 11:33:42 +0000 Subject: [PATCH 015/128] Removed unused import from code snippet in tutorial --- 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 36473ce91..50552616b 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -6,7 +6,6 @@ At the moment relationships within our API are represented by using primary keys Right now we have endpoints for 'snippets' and 'users', but we don't have a single entry point to our API. To create one, we'll use a regular function-based view and the `@api_view` decorator we introduced earlier. In your `snippets/views.py` add: - from rest_framework import renderers from rest_framework.decorators import api_view from rest_framework.response import Response from rest_framework.reverse import reverse From 7d417fc67864fc4c1c340eeae276cea0d5d428b1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 12:02:58 +0000 Subject: [PATCH 016/128] Make _force_text_recursive private. --- rest_framework/exceptions.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/rest_framework/exceptions.py b/rest_framework/exceptions.py index dbab66842..906de3b04 100644 --- a/rest_framework/exceptions.py +++ b/rest_framework/exceptions.py @@ -13,6 +13,23 @@ from rest_framework.compat import force_text import math +def _force_text_recursive(data): + """ + Descend into a nested data structure, forcing any + lazy translation strings into plain text. + """ + if isinstance(data, list): + return [ + _force_text_recursive(item) for item in data + ] + elif isinstance(data, dict): + return dict([ + (key, _force_text_recursive(value)) + for key, value in data.items() + ]) + return force_text(data) + + class APIException(Exception): """ Base class for REST framework exceptions. @@ -38,19 +55,6 @@ class APIException(Exception): # from rest_framework import serializers # raise serializers.ValidationError('Value was invalid') -def force_text_recursive(data): - if isinstance(data, list): - return [ - force_text_recursive(item) for item in data - ] - elif isinstance(data, dict): - return dict([ - (key, force_text_recursive(value)) - for key, value in data.items() - ]) - return force_text(data) - - class ValidationError(APIException): status_code = status.HTTP_400_BAD_REQUEST @@ -59,7 +63,7 @@ class ValidationError(APIException): # The details should always be coerced to a list if not already. if not isinstance(detail, dict) and not isinstance(detail, list): detail = [detail] - self.detail = force_text_recursive(detail) + self.detail = _force_text_recursive(detail) def __str__(self): return str(self.detail) From 6794b3380a32b53fa88547a8b2a2b34834fe4df7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 12:15:33 +0000 Subject: [PATCH 017/128] Fixes for defaulting empty HTML fields to '', None, or empty. --- rest_framework/fields.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index bb43708dc..778bc7187 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -181,6 +181,9 @@ class Field(object): self.style = {} if style is None else style self.allow_null = allow_null + if allow_null and self.default_empty_html is empty: + self.default_empty_html = None + if validators is not None: self.validators = validators[:] @@ -495,6 +498,7 @@ class CharField(Field): } initial = '' coerce_blank_to_null = False + default_empty_html = '' def __init__(self, **kwargs): self.allow_blank = kwargs.pop('allow_blank', False) From 9c6bead8b6d3f9ae11e9859ed305e8d0f7a9e0d8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 12:38:08 +0000 Subject: [PATCH 018/128] Add --- null option for selects. Closes #2096. --- .../templates/rest_framework/horizontal/select.html | 3 +++ rest_framework/templates/rest_framework/inline/select.html | 5 ++++- rest_framework/templates/rest_framework/vertical/select.html | 5 ++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/rest_framework/templates/rest_framework/horizontal/select.html b/rest_framework/templates/rest_framework/horizontal/select.html index 1d00f4241..380b38e94 100644 --- a/rest_framework/templates/rest_framework/horizontal/select.html +++ b/rest_framework/templates/rest_framework/horizontal/select.html @@ -4,6 +4,9 @@ {% endif %}
+ {% if field.allow_null %} + + {% endif %} {% for key, text in field.choices.items %} - + {% endfor %}
diff --git a/rest_framework/templates/rest_framework/vertical/select.html b/rest_framework/templates/rest_framework/vertical/select.html index 7c673ebb5..de72e1ddb 100644 --- a/rest_framework/templates/rest_framework/vertical/select.html +++ b/rest_framework/templates/rest_framework/vertical/select.html @@ -3,8 +3,11 @@ {% endif %} {% if field.errors %} From 6ec96d0bac1e738aceec9f8c21282c172120c7ac Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 13:43:38 +0000 Subject: [PATCH 019/128] Resolve PUT and POST buttons in browsable API --- rest_framework/renderers.py | 14 ++++++++++++-- .../templates/rest_framework/api_form.html | 8 ++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 rest_framework/templates/rest_framework/api_form.html diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 6596fc44c..8717137af 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -429,7 +429,10 @@ class HTMLFormRenderer(BaseRenderer): style['base_template'] = self.base_template style['renderer'] = self - if 'template' in style: + # This API needs to be finessed and finalized for 3.1 + if 'template' in renderer_context: + template_name = renderer_context['template'] + elif 'template' in style: template_name = style['template'] else: template_name = style['template_pack'].strip('/') + '/' + style['base_template'] @@ -555,7 +558,14 @@ class BrowsableAPIRenderer(BaseRenderer): if data is not None: serializer.is_valid() form_renderer = self.form_renderer_class() - return form_renderer.render(serializer.data, self.accepted_media_type, self.renderer_context) + return form_renderer.render( + serializer.data, + self.accepted_media_type, + dict( + self.renderer_context.items() + + [('template', 'rest_framework/api_form.html')] + ) + ) def get_raw_data_form(self, data, view, method, request): """ diff --git a/rest_framework/templates/rest_framework/api_form.html b/rest_framework/templates/rest_framework/api_form.html new file mode 100644 index 000000000..96f924ed8 --- /dev/null +++ b/rest_framework/templates/rest_framework/api_form.html @@ -0,0 +1,8 @@ +{% load rest_framework %} +{% csrf_token %} +{% for field in form %} + {% if not field.read_only %} + {% render_field field style=style %} + {% endif %} +{% endfor %} + From 6a2023a362bcc10978ed725fa8816ddf08ea7c73 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 13:52:00 +0000 Subject: [PATCH 020/128] Note on 3.1 and 3.2 releases. --- docs/topics/3.0-announcement.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 958be2d6b..03e3e070b 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -34,6 +34,9 @@ Notable features of this new release include: * Support for overriding how validation errors are handled by your API. * A metadata API that allows you to customize how `OPTIONS` requests are handled by your API. * A more compact JSON output with unicode style encoding turned on by default. +* Templated based HTML form rendering for serializers. This will be finalized as public API in the upcoming 3.1 release. + +Significant new functionality continues to be planned for the 3.1 and 3.2 releases. These releases will present simple upgrades, without the same level of fundamental API changes necessary for the 3.0 release. Below is an in-depth guide to the API changes and migration notes for 3.0. From 071c064d251bb2f268762fb34cf3f248b5ce4557 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 14:22:53 +0000 Subject: [PATCH 021/128] Fill in TODOs for 3.0 beta release notes --- docs/topics/3.0-announcement.md | 47 ++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 03e3e070b..7bbfba5ba 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -36,7 +36,7 @@ Notable features of this new release include: * A more compact JSON output with unicode style encoding turned on by default. * Templated based HTML form rendering for serializers. This will be finalized as public API in the upcoming 3.1 release. -Significant new functionality continues to be planned for the 3.1 and 3.2 releases. These releases will present simple upgrades, without the same level of fundamental API changes necessary for the 3.0 release. +Significant new functionality continues to be planned for the 3.1 and 3.2 releases. These releases will correspond to the two [Kickstarter stretch goals](https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3) - "Feature improvements" and "Admin interface". Further 3.x releases will present simple upgrades, without the same level of fundamental API changes necessary for the 3.0 release. Below is an in-depth guide to the API changes and migration notes for 3.0. @@ -145,6 +145,16 @@ The corresponding code would now look like this: logging.info('Creating ticket "%s"' % name) serializer.save(user=request.user) # Include the user when saving. +#### Using `.is_valid(raise_exception=True)` + +The `.is_valid()` method now takes an optional boolean flag, `raise_exception`. + +Calling `.is_valid(raise_exception=True)` will cause a `ValidationError` to be raised if the serializer data contains validation errors. This error will be handled by REST framework's default exception handler, allowing you to remove error response handling from your view code. + +The handling and formatting of error responses may be altered globally by using the `EXCEPTION_HANDLER` settings key. + +This change also means it's now possible to alter the style of error responses used by the built-in generic views, without having to include mixin classes or other overrides. + #### Using `serializers.ValidationError`. Previously `serializers.ValidationError` error was simply a synonym for `django.core.exceptions.ValidationError`. This has now been altered so that it inherits from the standard `APIException` base class. @@ -356,6 +366,8 @@ The `ListSerializer` class has now been added, and allows you to create base ser You can also still use the `many=True` argument to serializer classes. It's worth noting that `many=True` argument transparently creates a `ListSerializer` instance, allowing the validation logic for list and non-list data to be cleanly separated in the REST framework codebase. +You will typically want to *continue to use the existing `many=True` flag* rather than declaring `ListSerializer` classes explicitly, but declaring the classes explicitly can be useful if you need to write custom `create` or `update` methods for bulk updates, or provide for other custom behavior. + See also the new `ListField` class, which validates input in the same way, but does not include the serializer interfaces of `.is_valid()`, `.data`, `.save()` and so on. #### The `BaseSerializer` class. @@ -673,7 +685,9 @@ The `UniqueTogetherValidator` should be applied to a serializer, and takes a `qu #### The `UniqueForDateValidator` classes. -**TODO: Needs documenting.** +REST framework also now includes explicit validator classes for validating the `unique_for_date`, `unique_for_month`, and `unique_for_year` model field constraints. These are used internally instead of calling into `Model.full_clean()`. + +These classes are documented in the [Validators](../api-guide/validators.md) section of the documentation. ## Generic views @@ -731,7 +745,34 @@ This makes it far easier to use a different style for `OPTIONS` responses throug ## Serializers as HTML forms -**TODO: Document this.** +REST framework 3.0 includes templated HTML form rendering for serializers. + +This API should not yet be considered finalized, and will only be promoted to public API for the 3.1 release. + +Significant changes that you do need to be aware of include: + +* Nested HTML forms are now supported, for example, a `UserSerializer` with a nested `ProfileSerializer` will now render a nested `fieldset` when used in the browsable API. +* Nested lists of HTML forms are not yet supported, but are planned for 3.1. +* Because we now use templated HTML form generation, **the `widget` option is no longer available for serializer fields**. You can instead control the template that is used for a given field, by using the `style` dictionary. + +#### The `style` keyword argument for serializer fields. + +The `style` keyword argument can be used to pass through additional information from a serializer field, to the renderer class. In particular, the `HTMLFormRenderer` uses the `base_template` key to determine which template to render the field with. + +For example, to use a `textarea` control instead of the default `input` control, you would use the following… + + additional_notes = serializers.CharField( + style={'base_template': 'text_area.html'} + ) + +Similarly, to use a radio button control instead of the default `select` control, you would use the following… + + color_channel = serializers.ChoiceField( + choices=['red', 'blue', 'green'], + style={'base_template': 'radio.html'} + ) + +This API should be considered provisional, and there may be minor alterations with the incoming 3.1 release. ## API style From 1aa58d16be0e96c7337b1d6bef928c9542c72741 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 14:26:40 +0000 Subject: [PATCH 022/128] Py3 fix - .items is not a list. --- 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 8717137af..a9885d3e0 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -562,7 +562,7 @@ class BrowsableAPIRenderer(BaseRenderer): serializer.data, self.accepted_media_type, dict( - self.renderer_context.items() + + list(self.renderer_context.items()) + [('template', 'rest_framework/api_form.html')] ) ) From 5b671cb515cf49bc0335bdaa7ca0759827eb844d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 16:14:51 +0000 Subject: [PATCH 023/128] Fix rendering HTML form when API error raised. Closes #2103. --- rest_framework/renderers.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index a9885d3e0..e87d16d0d 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -525,7 +525,10 @@ class BrowsableAPIRenderer(BaseRenderer): else: instance = None - if request.method == method: + # If this is valid serializer data, and the form is for the same + # HTTP method as was used in the request then use the existing + # serializer instance, rather than dynamically creating a new one. + if request.method == method and serializer is not None: try: data = request.data except ParseError: From 8e940a22fb292ed6b20b9e4b5da607ffca1dba2f Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Fri, 21 Nov 2014 15:58:31 -0500 Subject: [PATCH 024/128] Clarify how permission classes are composed all/every/AND or any/OR? all/every/AND! --- docs/api-guide/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 48af30a52..96d715ea2 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -74,7 +74,7 @@ Default: #### DEFAULT_PERMISSION_CLASSES -A list or tuple of permission classes, that determines the default set of permissions checked at the start of a view. +A list or tuple of permission classes, that determines the default set of permissions checked at the start of a view. Permission must be granted by every class in the list. Default: From fe9a758d7e0db90be7fe5878d1617444c2465391 Mon Sep 17 00:00:00 2001 From: Andreas Bernacca Date: Fri, 21 Nov 2014 22:15:43 +0100 Subject: [PATCH 025/128] remove sponsored by dabapps --- rest_framework/templates/rest_framework/base.html | 1 - 1 file changed, 1 deletion(-) diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index e9d99a659..a2bd8cd57 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -240,7 +240,6 @@
{% block footer %} -

Sponsored by DabApps.

{% endblock %}
From 2ec0e7417f7e64a40062266f535778435fd3f9ba Mon Sep 17 00:00:00 2001 From: Andreas Bernacca Date: Fri, 21 Nov 2014 22:45:28 +0100 Subject: [PATCH 026/128] removed the whole footer section --- rest_framework/templates/rest_framework/base.html | 6 ------ 1 file changed, 6 deletions(-) diff --git a/rest_framework/templates/rest_framework/base.html b/rest_framework/templates/rest_framework/base.html index a2bd8cd57..e96681932 100644 --- a/rest_framework/templates/rest_framework/base.html +++ b/rest_framework/templates/rest_framework/base.html @@ -237,12 +237,6 @@ - -
- {% block footer %} - {% endblock %} -
- {% block script %} From c94d1e6d3ea76eb8bdd28717364e42d14e6722d7 Mon Sep 17 00:00:00 2001 From: Tony Nguyen Date: Sun, 23 Nov 2014 22:40:40 +0700 Subject: [PATCH 027/128] Fix typo "serailizers" to "serializers" --- docs/topics/3.0-announcement.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 7bbfba5ba..694ad8a5c 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -189,13 +189,13 @@ You can either return `non_field_errors` from the validate method by raising a s def validate(self, attrs): # serializer.errors == {'non_field_errors': ['A non field error']} - raise serailizers.ValidationError('A non field error') + raise serializers.ValidationError('A non field error') Alternatively if you want the errors to be against a specific field, use a dictionary of when instantiating the `ValidationError`, like so: def validate(self, attrs): # serializer.errors == {'my_field': ['A field error']} - raise serailizers.ValidationError({'my_field': 'A field error'}) + raise serializers.ValidationError({'my_field': 'A field error'}) This ensures you can still write validation that compares all the input fields, but that marks the error against a particular field. @@ -303,7 +303,7 @@ Alternatively, specify the field explicitly on the serializer class: model = MyModel fields = ('id', 'email', 'notes', 'is_admin') -The `read_only_fields` option remains as a convenient shortcut for the more common case. +The `read_only_fields` option remains as a convenient shortcut for the more common case. #### Changes to `HyperlinkedModelSerializer`. @@ -364,7 +364,7 @@ The `ListSerializer` class has now been added, and allows you to create base ser class MultipleUserSerializer(ListSerializer): child = UserSerializer() -You can also still use the `many=True` argument to serializer classes. It's worth noting that `many=True` argument transparently creates a `ListSerializer` instance, allowing the validation logic for list and non-list data to be cleanly separated in the REST framework codebase. +You can also still use the `many=True` argument to serializer classes. It's worth noting that `many=True` argument transparently creates a `ListSerializer` instance, allowing the validation logic for list and non-list data to be cleanly separated in the REST framework codebase. You will typically want to *continue to use the existing `many=True` flag* rather than declaring `ListSerializer` classes explicitly, but declaring the classes explicitly can be useful if you need to write custom `create` or `update` methods for bulk updates, or provide for other custom behavior. @@ -436,7 +436,7 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd def to_internal_value(self, data): score = data.get('score') player_name = data.get('player_name') - + # Perform the data validation. if not score: raise ValidationError({ @@ -450,7 +450,7 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd raise ValidationError({ 'player_name': 'May not be more than 10 characters.' }) - + # Return the validated values. This will be available as # the `.validated_data` property. return { @@ -463,7 +463,7 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd 'score': obj.score, 'player_name': obj.player_name } - + def create(self, validated_data): return HighScore.objects.create(**validated_data) @@ -471,7 +471,7 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd The `BaseSerializer` class is also useful if you want to implement new generic serializer classes for dealing with particular serialization styles, or for integrating with alternative storage backends. -The following class is an example of a generic serializer that can handle coercing aribitrary objects into primitive representations. +The following class is an example of a generic serializer that can handle coercing aribitrary objects into primitive representations. class ObjectSerializer(serializers.BaseSerializer): """ @@ -542,7 +542,7 @@ The `default` argument is also available and always implies that the field is no #### Coercing output types. -The previous field implementations did not forcibly coerce returned values into the correct type in many cases. For example, an `IntegerField` would return a string output if the attribute value was a string. We now more strictly coerce to the correct return type, leading to more constrained and expected behavior. +The previous field implementations did not forcibly coerce returned values into the correct type in many cases. For example, an `IntegerField` would return a string output if the attribute value was a string. We now more strictly coerce to the correct return type, leading to more constrained and expected behavior. #### The `ListField` class. @@ -695,7 +695,7 @@ These classes are documented in the [Validators](../api-guide/validators.md) sec The view logic for the default method handlers has been significantly simplified, due to the new serializers API. -#### Changes to pre/post save hooks. +#### Changes to pre/post save hooks. The `pre_save` and `post_save` hooks no longer exist, but are replaced with `perform_create(self, serializer)` and `perform_update(self, serializer)`. @@ -887,4 +887,4 @@ The 3.2 release is planned to introduce an alternative admin-style interface to You can follow development on the GitHub site, where we use [milestones to indicate planning timescales](https://github.com/tomchristie/django-rest-framework/milestones). -[mixins.py]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py \ No newline at end of file +[mixins.py]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py From 790a651893c4bc6e68bc4d724d5df4ea3f883ced Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gil=20Gon=C3=A7alves?= Date: Mon, 24 Nov 2014 08:51:08 +0000 Subject: [PATCH 028/128] Fixed database update instructions --- docs/tutorial/4-authentication-and-permissions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index adab1b55a..4e4edeeac 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -44,7 +44,9 @@ 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. rm tmp.db - python manage.py syncdb + rm -r snippets/migrations + python manage.py makemigrations snippets + python manage.py migrate You might also want to create a few different users, to use for testing the API. The quickest way to do this will be with the `createsuperuser` command. From 918b9cc6a605ee4701d77f73e93a62f04c2345b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gil=20Gon=C3=A7alves?= Date: Mon, 24 Nov 2014 08:54:07 +0000 Subject: [PATCH 029/128] Added missing import in tutorial snippet --- 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 cf37a2601..3fad509a1 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -60,7 +60,7 @@ To see what's going on under the hood let's first explicitly create a set of vie In the `urls.py` file we bind our `ViewSet` classes into a set of concrete views. - from snippets.views import SnippetViewSet, UserViewSet + from snippets.views import SnippetViewSet, UserViewSet, api_root from rest_framework import renderers snippet_list = SnippetViewSet.as_view({ From bdacb6624484209fb008293a9dc40c06a439ef71 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 08:59:45 +0000 Subject: [PATCH 030/128] Lowercase HTML label tag. Refs #2121. --- rest_framework/templates/rest_framework/login_base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest_framework/templates/rest_framework/login_base.html b/rest_framework/templates/rest_framework/login_base.html index 8ab682ac8..c688cc28e 100644 --- a/rest_framework/templates/rest_framework/login_base.html +++ b/rest_framework/templates/rest_framework/login_base.html @@ -22,7 +22,7 @@
- +
- + From ecc26f6cb1e8b1f1a4cd72cf980b750aeed65cf7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 09:07:25 +0000 Subject: [PATCH 031/128] Style tweaks to login template. --- rest_framework/templates/rest_framework/login_base.html | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rest_framework/templates/rest_framework/login_base.html b/rest_framework/templates/rest_framework/login_base.html index c688cc28e..e050cbdc4 100644 --- a/rest_framework/templates/rest_framework/login_base.html +++ b/rest_framework/templates/rest_framework/login_base.html @@ -36,7 +36,8 @@
+ class="clearfix control-group {% if form.password.errors %}error{% endif %}" + style="margin-top: 10px">
{{ error }}
{% endfor %} {% endif %} -
+
From 585054f0120b9c73c8158a92466f8959ff7f932e Mon Sep 17 00:00:00 2001 From: Tymur Maryokhin Date: Tue, 25 Nov 2014 10:57:46 +0100 Subject: [PATCH 032/128] Streamlined tox config with 1.8 features --- tox.ini | 193 +++++++------------------------------------------------- 1 file changed, 22 insertions(+), 171 deletions(-) diff --git a/tox.ini b/tox.ini index 493712a1b..b6d94678e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,181 +1,32 @@ [tox] -downloadcache = {toxworkdir}/cache/ envlist = - flake8, - py3.4-django1.7,py3.3-django1.7,py3.2-django1.7,py2.7-django1.7, - py3.4-django1.6,py3.3-django1.6,py3.2-django1.6,py2.7-django1.6,py2.6-django1.6, - py3.4-django1.5,py3.3-django1.5,py3.2-django1.5,py2.7-django1.5,py2.6-django1.5, - py2.7-django1.4,py2.6-django1.4, - py3.4-djangomaster,py3.3-djangomaster,py2.7-djangomaster + py27-flake8, + {py26,py27}-django1.4, + {py26,py27,py32,py33,py34}-django{1.5,1.6}, + {py27,py32,py33,py34}-django1.7, + {py27,py33,py34}-djangomaster [testenv] commands = ./runtests.py --fast setenv = PYTHONDONTWRITEBYTECODE=1 +deps = + django1.4: django==1.4.11 + django1.5: django==1.5.6 + django1.6: Django==1.6.3 + django1.7: Django==1.7 + djangomaster: https://github.com/django/django/zipball/master + {py26,py27}-django{1.4,1.5,1.6,1.7}: django-guardian==1.2.3 + {py26,py27}-django{1.4,1.5,1.6}: oauth2==1.5.211 + {py26,py27}-django{1.4,1.5,1.6}: django-oauth-plus==2.2.1 + {py26,py27}-django{1.4,1.5}: django-oauth2-provider==0.2.3 + {py26,py27}-django1.6: django-oauth2-provider==0.2.4 + pytest-django==2.6.1 + django-filter==0.7 + defusedxml==0.3 -[testenv:flake8] -basepython = python2.7 -deps = pytest==2.5.2 +[testenv:py27-flake8] +deps = + pytest==2.5.2 flake8==2.2.2 commands = ./runtests.py --lintonly - -[testenv:py3.4-django1.7] -basepython = python3.4 -deps = Django==1.7 - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py3.3-django1.7] -basepython = python3.3 -deps = Django==1.7 - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py3.2-django1.7] -basepython = python3.2 -deps = Django==1.7 - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py2.7-django1.7] -basepython = python2.7 -deps = Django==1.7 - django-filter==0.7 - defusedxml==0.3 - # django-oauth-plus==2.2.1 - # oauth2==1.5.211 - # django-oauth2-provider==0.2.4 - django-guardian==1.2.3 - pytest-django==2.6.1 - -[testenv:py3.4-django1.6] -basepython = python3.4 -deps = Django==1.6.3 - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py3.3-django1.6] -basepython = python3.3 -deps = Django==1.6.3 - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py3.2-django1.6] -basepython = python3.2 -deps = Django==1.6.3 - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py2.7-django1.6] -basepython = python2.7 -deps = Django==1.6.3 - django-filter==0.7 - defusedxml==0.3 - django-oauth-plus==2.2.1 - oauth2==1.5.211 - django-oauth2-provider==0.2.4 - django-guardian==1.2.3 - pytest-django==2.6.1 - -[testenv:py2.6-django1.6] -basepython = python2.6 -deps = Django==1.6.3 - django-filter==0.7 - defusedxml==0.3 - django-oauth-plus==2.2.1 - oauth2==1.5.211 - django-oauth2-provider==0.2.4 - django-guardian==1.2.3 - pytest-django==2.6.1 - -[testenv:py3.4-django1.5] -basepython = python3.4 -deps = django==1.5.6 - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py3.3-django1.5] -basepython = python3.3 -deps = django==1.5.6 - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py3.2-django1.5] -basepython = python3.2 -deps = django==1.5.6 - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py2.7-django1.5] -basepython = python2.7 -deps = django==1.5.6 - django-filter==0.7 - defusedxml==0.3 - django-oauth-plus==2.2.1 - oauth2==1.5.211 - django-oauth2-provider==0.2.3 - django-guardian==1.2.3 - pytest-django==2.6.1 - -[testenv:py2.6-django1.5] -basepython = python2.6 -deps = django==1.5.6 - django-filter==0.7 - defusedxml==0.3 - django-oauth-plus==2.2.1 - oauth2==1.5.211 - django-oauth2-provider==0.2.3 - django-guardian==1.2.3 - pytest-django==2.6.1 - -[testenv:py2.7-django1.4] -basepython = python2.7 -deps = django==1.4.11 - django-filter==0.7 - defusedxml==0.3 - django-oauth-plus==2.2.1 - oauth2==1.5.211 - django-oauth2-provider==0.2.3 - django-guardian==1.2.3 - pytest-django==2.6.1 - -[testenv:py2.6-django1.4] -basepython = python2.6 -deps = django==1.4.11 - django-filter==0.7 - defusedxml==0.3 - django-oauth-plus==2.2.1 - oauth2==1.5.211 - django-oauth2-provider==0.2.3 - django-guardian==1.2.3 - pytest-django==2.6.1 - -[testenv:py3.4-djangomaster] -basepython = python3.4 -deps = https://github.com/django/django/zipball/master - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py3.3-djangomaster] -basepython = python3.3 -deps = https://github.com/django/django/zipball/master - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 - -[testenv:py2.7-djangomaster] -basepython = python3.2 -deps = https://github.com/django/django/zipball/master - django-filter==0.7 - defusedxml==0.3 - pytest-django==2.6.1 From 1fcb495456bebf56b820c89951af285a45383d6f Mon Sep 17 00:00:00 2001 From: Tymur Maryokhin Date: Tue, 25 Nov 2014 10:58:03 +0100 Subject: [PATCH 033/128] Updated travis config --- .travis.yml | 50 ++++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9b2e47383..e19efdba5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,40 +1,38 @@ language: python -python: 2.7 - sudo: false env: - - TOX_ENV=flake8 - - TOX_ENV=py3.4-django1.7 - - TOX_ENV=py3.3-django1.7 - - TOX_ENV=py3.2-django1.7 - - TOX_ENV=py2.7-django1.7 - - TOX_ENV=py3.4-django1.6 - - TOX_ENV=py3.3-django1.6 - - TOX_ENV=py3.2-django1.6 - - TOX_ENV=py2.7-django1.6 - - TOX_ENV=py2.6-django1.6 - - TOX_ENV=py3.4-django1.5 - - TOX_ENV=py3.3-django1.5 - - TOX_ENV=py3.2-django1.5 - - TOX_ENV=py2.7-django1.5 - - TOX_ENV=py2.6-django1.5 - - TOX_ENV=py2.7-django1.4 - - TOX_ENV=py2.6-django1.4 - - TOX_ENV=py3.4-djangomaster - - TOX_ENV=py3.3-djangomaster - - TOX_ENV=py2.7-djangomaster + - TOX_ENV=py27-flake8 + - TOX_ENV=py34-django1.7 + - TOX_ENV=py33-django1.7 + - TOX_ENV=py32-django1.7 + - TOX_ENV=py27-django1.7 + - TOX_ENV=py34-django1.6 + - TOX_ENV=py33-django1.6 + - TOX_ENV=py32-django1.6 + - TOX_ENV=py27-django1.6 + - TOX_ENV=py26-django1.6 + - TOX_ENV=py34-django1.5 + - TOX_ENV=py33-django1.5 + - TOX_ENV=py32-django1.5 + - TOX_ENV=py27-django1.5 + - TOX_ENV=py26-django1.5 + - TOX_ENV=py27-django1.4 + - TOX_ENV=py26-django1.4 + - TOX_ENV=py34-djangomaster + - TOX_ENV=py33-djangomaster + - TOX_ENV=py27-djangomaster matrix: fast_finish: true allow_failures: - - env: TOX_ENV=py3.4-djangomaster - - env: TOX_ENV=py3.3-djangomaster - - env: TOX_ENV=py2.7-djangomaster + - env: TOX_ENV=py34-djangomaster + - env: TOX_ENV=py33-djangomaster + - env: TOX_ENV=py27-djangomaster install: - - "pip install tox --download-cache $HOME/.pip-cache" + - pip install tox script: - tox -e $TOX_ENV From b6de2660eaa19dbd656215f47b6d24a5cc8bb5b3 Mon Sep 17 00:00:00 2001 From: Tymur Maryokhin Date: Tue, 25 Nov 2014 11:09:36 +0100 Subject: [PATCH 034/128] Added python 3.2 to test against django master . Related to #2075. --- .travis.yml | 2 ++ tox.ini | 3 +-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e19efdba5..2791df6e6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ env: - TOX_ENV=py26-django1.4 - TOX_ENV=py34-djangomaster - TOX_ENV=py33-djangomaster + - TOX_ENV=py32-djangomaster - TOX_ENV=py27-djangomaster matrix: @@ -29,6 +30,7 @@ matrix: allow_failures: - env: TOX_ENV=py34-djangomaster - env: TOX_ENV=py33-djangomaster + - env: TOX_ENV=py32-djangomaster - env: TOX_ENV=py27-djangomaster install: diff --git a/tox.ini b/tox.ini index b6d94678e..b1e03580e 100644 --- a/tox.ini +++ b/tox.ini @@ -3,8 +3,7 @@ envlist = py27-flake8, {py26,py27}-django1.4, {py26,py27,py32,py33,py34}-django{1.5,1.6}, - {py27,py32,py33,py34}-django1.7, - {py27,py33,py34}-djangomaster + {py27,py32,py33,py34}-django{1.7,master} [testenv] commands = ./runtests.py --fast From 9c58dfec4e6865a5296d0913be907a20f712f0d3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 10:34:21 +0000 Subject: [PATCH 035/128] Fix shoddy test case --- tests/test_authentication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_authentication.py b/tests/test_authentication.py index 32041f9c1..28c3a8b35 100644 --- a/tests/test_authentication.py +++ b/tests/test_authentication.py @@ -142,7 +142,7 @@ class SessionAuthTests(TestCase): cf. [#1810](https://github.com/tomchristie/django-rest-framework/pull/1810) """ response = self.csrf_client.get('/auth/login/') - self.assertContains(response, '') + self.assertContains(response, '') def test_post_form_session_auth_failing_csrf(self): """ From fd980be39be7f09d4cf0ceb16688ad0157d4df35 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 10:39:58 +0000 Subject: [PATCH 036/128] Documentation in 'many_init' docstring. Refs #2120. --- rest_framework/serializers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rest_framework/serializers.py b/rest_framework/serializers.py index 2d5c843e5..00362dbb4 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -86,6 +86,15 @@ class BaseSerializer(Field): class when `many=True` is used. You can customize it if you need to control which keyword arguments are passed to the parent, and which are passed to the child. + + Note that we're over-cautious in passing most arguments to both parent + and child classes in order to try to cover the general case. If you're + overriding this method you'll probably want something much simpler, eg: + + @classmethod + def many_init(cls, *args, **kwargs): + kwargs['child'] = cls() + return CustomListSerializer(*args, **kwargs) """ child_serializer = cls(*args, **kwargs) list_kwargs = {'child': child_serializer} From c559ec6da8f983e34ca9a4183e5f632124d77cbc Mon Sep 17 00:00:00 2001 From: Tymur Maryokhin Date: Tue, 25 Nov 2014 12:03:54 +0100 Subject: [PATCH 037/128] Removed dots in Django versions in tox. Refs #2124. --- .travis.yml | 32 ++++++++++++++++---------------- tox.ini | 24 ++++++++++++------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2791df6e6..9dd587be9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,22 +4,22 @@ sudo: false env: - TOX_ENV=py27-flake8 - - TOX_ENV=py34-django1.7 - - TOX_ENV=py33-django1.7 - - TOX_ENV=py32-django1.7 - - TOX_ENV=py27-django1.7 - - TOX_ENV=py34-django1.6 - - TOX_ENV=py33-django1.6 - - TOX_ENV=py32-django1.6 - - TOX_ENV=py27-django1.6 - - TOX_ENV=py26-django1.6 - - TOX_ENV=py34-django1.5 - - TOX_ENV=py33-django1.5 - - TOX_ENV=py32-django1.5 - - TOX_ENV=py27-django1.5 - - TOX_ENV=py26-django1.5 - - TOX_ENV=py27-django1.4 - - TOX_ENV=py26-django1.4 + - TOX_ENV=py34-django17 + - TOX_ENV=py33-django17 + - TOX_ENV=py32-django17 + - TOX_ENV=py27-django17 + - TOX_ENV=py34-django16 + - TOX_ENV=py33-django16 + - TOX_ENV=py32-django16 + - TOX_ENV=py27-django16 + - TOX_ENV=py26-django16 + - TOX_ENV=py34-django15 + - TOX_ENV=py33-django15 + - TOX_ENV=py32-django15 + - TOX_ENV=py27-django15 + - TOX_ENV=py26-django15 + - TOX_ENV=py27-django14 + - TOX_ENV=py26-django14 - TOX_ENV=py34-djangomaster - TOX_ENV=py33-djangomaster - TOX_ENV=py32-djangomaster diff --git a/tox.ini b/tox.ini index b1e03580e..a7200a3f3 100644 --- a/tox.ini +++ b/tox.ini @@ -1,25 +1,25 @@ [tox] envlist = py27-flake8, - {py26,py27}-django1.4, - {py26,py27,py32,py33,py34}-django{1.5,1.6}, - {py27,py32,py33,py34}-django{1.7,master} + {py26,py27}-django14, + {py26,py27,py32,py33,py34}-django{15,16}, + {py27,py32,py33,py34}-django{17,master} [testenv] commands = ./runtests.py --fast setenv = PYTHONDONTWRITEBYTECODE=1 deps = - django1.4: django==1.4.11 - django1.5: django==1.5.6 - django1.6: Django==1.6.3 - django1.7: Django==1.7 + django14: django==1.4.11 + django15: django==1.5.6 + django16: Django==1.6.3 + django17: Django==1.7 djangomaster: https://github.com/django/django/zipball/master - {py26,py27}-django{1.4,1.5,1.6,1.7}: django-guardian==1.2.3 - {py26,py27}-django{1.4,1.5,1.6}: oauth2==1.5.211 - {py26,py27}-django{1.4,1.5,1.6}: django-oauth-plus==2.2.1 - {py26,py27}-django{1.4,1.5}: django-oauth2-provider==0.2.3 - {py26,py27}-django1.6: django-oauth2-provider==0.2.4 + {py26,py27}-django{14,15,16,17}: django-guardian==1.2.3 + {py26,py27}-django{14,15,16}: oauth2==1.5.211 + {py26,py27}-django{14,15,16}: django-oauth-plus==2.2.1 + {py26,py27}-django{14,15}: django-oauth2-provider==0.2.3 + {py26,py27}-django16: django-oauth2-provider==0.2.4 pytest-django==2.6.1 django-filter==0.7 defusedxml==0.3 From 06fd63dade20e1a19276b7414a54b9f5d2ef8329 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 11:14:28 +0000 Subject: [PATCH 038/128] Don't use default_empty_html value for partial updates. Closes #2118. --- rest_framework/fields.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 778bc7187..3cf348865 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -262,7 +262,11 @@ class Field(object): if html.is_html_input(dictionary): # HTML forms will represent empty fields as '', and cannot # represent None or False values directly. - ret = dictionary.get(self.field_name, '') + if self.field_name not in dictionary: + if getattr(self.root, 'partial', False): + return empty + return self.default_empty_html + ret = dictionary[self.field_name] return self.default_empty_html if (ret == '') else ret return dictionary.get(self.field_name, empty) From 83e556ba1e129fac28a74b0a2784e4728f491a41 Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 25 Nov 2014 12:37:20 +0100 Subject: [PATCH 039/128] Missing quotes on validate_score example --- docs/topics/3.0-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 694ad8a5c..6a662326b 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -170,7 +170,7 @@ We strongly recommend that you use the namespaced import style of `import serial The `validate_` method hooks that can be attached to serializer classes change their signature slightly and return type. Previously these would take a dictionary of all incoming data, and a key representing the field name, and would return a dictionary including the validated data for that field: def validate_score(self, attrs, source): - if attrs[score] % 10 != 0: + if attrs['score'] % 10 != 0: raise serializers.ValidationError('This field should be a multiple of ten.') return attrs From 2e726e22a394347b7337eb38a2a3a1b0ccde88bc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 11:42:43 +0000 Subject: [PATCH 040/128] request.DATA, request.FILES -> request.data --- docs/api-guide/exceptions.md | 4 ++-- docs/api-guide/parsers.md | 20 ++++++++++---------- docs/api-guide/requests.md | 21 ++++++++++++--------- docs/api-guide/settings.md | 2 +- docs/api-guide/viewsets.md | 2 +- docs/tutorial/2-requests-and-responses.md | 12 ++++++------ docs/tutorial/3-class-based-views.md | 4 ++-- rest_framework/request.py | 2 +- 8 files changed, 35 insertions(+), 32 deletions(-) diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index e61dcfa90..8a99abb9d 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -100,7 +100,7 @@ For example, if your API relies on a third party service that may sometimes be u **Signature:** `ParseError(detail=None)` -Raised if the request contains malformed data when accessing `request.DATA` or `request.FILES`. +Raised if the request contains malformed data when accessing `request.data`. By default this exception results in a response with the HTTP status code "400 Bad Request". @@ -140,7 +140,7 @@ By default this exception results in a response with the HTTP status code "405 M **Signature:** `UnsupportedMediaType(media_type, detail=None)` -Raised if there are no parsers that can handle the content type of the request data when accessing `request.DATA` or `request.FILES`. +Raised if there are no parsers that can handle the content type of the request data when accessing `request.data`. By default this exception results in a response with the HTTP status code "415 Unsupported Media Type". diff --git a/docs/api-guide/parsers.md b/docs/api-guide/parsers.md index 72a4af643..a50b52408 100644 --- a/docs/api-guide/parsers.md +++ b/docs/api-guide/parsers.md @@ -12,7 +12,7 @@ REST framework includes a number of built in Parser classes, that allow you to a ## How the parser is determined -The set of valid parsers for a view is always defined as a list of classes. When either `request.DATA` or `request.FILES` is accessed, REST framework will examine the `Content-Type` header on the incoming request, and determine which parser to use to parse the request content. +The set of valid parsers for a view is always defined as a list of classes. When `request.data` is accessed, REST framework will examine the `Content-Type` header on the incoming request, and determine which parser to use to parse the request content. --- @@ -48,7 +48,7 @@ using the `APIView` class based views. parser_classes = (YAMLParser,) def post(self, request, format=None): - return Response({'received data': request.DATA}) + return Response({'received data': request.data}) Or, if you're using the `@api_view` decorator with function based views. @@ -58,7 +58,7 @@ Or, if you're using the `@api_view` decorator with function based views. """ A view that can accept POST requests with YAML content. """ - return Response({'received data': request.DATA}) + return Response({'received data': request.data}) --- @@ -92,7 +92,7 @@ Requires the `defusedxml` package to be installed. ## FormParser -Parses HTML form content. `request.DATA` will be populated with a `QueryDict` of data, `request.FILES` will be populated with an empty `QueryDict` of data. +Parses HTML form content. `request.data` will be populated with a `QueryDict` of data. You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data. @@ -100,7 +100,7 @@ You will typically want to use both `FormParser` and `MultiPartParser` together ## MultiPartParser -Parses multipart HTML form content, which supports file uploads. Both `request.DATA` and `request.FILES` will be populated with a `QueryDict`. +Parses multipart HTML form content, which supports file uploads. Both `request.data` will be populated with a `QueryDict`. You will typically want to use both `FormParser` and `MultiPartParser` together in order to fully support HTML form data. @@ -108,7 +108,7 @@ You will typically want to use both `FormParser` and `MultiPartParser` together ## FileUploadParser -Parses raw file upload content. The `request.DATA` property will be an empty `QueryDict`, and `request.FILES` will be a dictionary with a single key `'file'` containing the uploaded file. +Parses raw file upload content. The `request.data` property will be a dictionary with a single key `'file'` containing the uploaded file. If the view used with `FileUploadParser` is called with a `filename` URL keyword argument, then that argument will be used as the filename. If it is called without a `filename` URL keyword argument, then the client must set the filename in the `Content-Disposition` HTTP header. For example `Content-Disposition: attachment; filename=upload.jpg`. @@ -126,7 +126,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword parser_classes = (FileUploadParser,) def put(self, request, filename, format=None): - file_obj = request.FILES['file'] + file_obj = request.data['file'] # ... # do some staff with uploaded file # ... @@ -139,7 +139,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword To implement a custom parser, you should override `BaseParser`, set the `.media_type` property, and implement the `.parse(self, stream, media_type, parser_context)` method. -The method should return the data that will be used to populate the `request.DATA` property. +The method should return the data that will be used to populate the `request.data` property. The arguments passed to `.parse()` are: @@ -161,7 +161,7 @@ By default this will include the following keys: `view`, `request`, `args`, `kwa ## Example -The following is an example plaintext parser that will populate the `request.DATA` property with a string representing the body of the request. +The following is an example plaintext parser that will populate the `request.data` property with a string representing the body of the request. class PlainTextParser(BaseParser): """ @@ -197,4 +197,4 @@ The following third party packages are also available. [juanriaza]: https://github.com/juanriaza [vbabiy]: https://github.com/vbabiy [djangorestframework-msgpack]: https://github.com/juanriaza/django-rest-framework-msgpack -[djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case \ No newline at end of file +[djangorestframework-camel-case]: https://github.com/vbabiy/djangorestframework-camel-case diff --git a/docs/api-guide/requests.md b/docs/api-guide/requests.md index 87425ed1b..d659e17a0 100644 --- a/docs/api-guide/requests.md +++ b/docs/api-guide/requests.md @@ -14,26 +14,29 @@ REST framework's `Request` class extends the standard `HttpRequest`, adding supp REST framework's Request objects provide flexible request parsing that allows you to treat requests with JSON data or other media types in the same way that you would normally deal with form data. -## .DATA +## .data -`request.DATA` returns the parsed content of the request body. This is similar to the standard `request.POST` attribute except that: +`request.data` returns the parsed content of the request body. This is similar to the standard `request.POST` and `request.FILES` attributes except that: +* 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. For more details see the [parsers documentation]. -## .FILES +## .query_params -`request.FILES` returns any uploaded files that may be present in the content of the request body. This is the same as the standard `HttpRequest` behavior, except that the same flexible request parsing is used for `request.DATA`. +`request.query_params` is a more correctly named synonym for `request.GET`. -For more details see the [parsers documentation]. +For clarity inside your code, we recommend using `request.query_params` instead of the Django's standard `request.GET`. Doing so will help keep your codebase more correct and obvious - any HTTP method type may include query parameters, not just `GET` requests. + +## .DATA and .FILES + +The old-style version 2.x `request.data` and `request.FILES` attributes are still available, but are now pending deprecation in favor of the unified `request.data` attribute. ## .QUERY_PARAMS -`request.QUERY_PARAMS` is a more correctly named synonym for `request.GET`. - -For clarity inside your code, we recommend using `request.QUERY_PARAMS` instead of the usual `request.GET`, as *any* HTTP method type may include query parameters. +The old-style version 2.x `request.QUERY_PARAMS` attribute is still available, but is now pending deprecation in favor of the more pythonic `request.query_params`. ## .parsers @@ -43,7 +46,7 @@ You won't typically need to access this property. --- -**Note:** If a client sends malformed content, then accessing `request.DATA` or `request.FILES` may raise a `ParseError`. By default REST framework's `APIView` class or `@api_view` decorator will catch the error and return a `400 Bad Request` response. +**Note:** If a client sends malformed content, then accessing `request.data` may raise a `ParseError`. By default REST framework's `APIView` class or `@api_view` decorator will catch the error and return a `400 Bad Request` response. If a client sends a request with a content-type that cannot be parsed then a `UnsupportedMediaType` exception will be raised, which by default will be caught and return a `415 Unsupported Media Type` response. diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 96d715ea2..ba470f8bb 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -51,7 +51,7 @@ Default: #### DEFAULT_PARSER_CLASSES -A list or tuple of parser classes, that determines the default set of parsers used when accessing the `request.DATA` property. +A list or tuple of parser classes, that determines the default set of parsers used when accessing the `request.data` property. Default: diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md index 9030e3ee0..f60d4a47f 100644 --- a/docs/api-guide/viewsets.md +++ b/docs/api-guide/viewsets.md @@ -124,7 +124,7 @@ For example: @detail_route(methods=['post']) def set_password(self, request, pk=None): user = self.get_object() - serializer = PasswordSerializer(data=request.DATA) + serializer = PasswordSerializer(data=request.data) if serializer.is_valid(): user.set_password(serializer.data['password']) user.save() diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index 136b01351..f377c7122 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -5,10 +5,10 @@ Let's introduce a couple of essential building blocks. ## Request objects -REST framework introduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing. The core functionality of the `Request` object is the `request.DATA` attribute, which is similar to `request.POST`, but more useful for working with Web APIs. +REST framework introduces a `Request` object that extends the regular `HttpRequest`, and provides more flexible request parsing. The core functionality of the `Request` object is the `request.data` attribute, which is similar to `request.POST`, but more useful for working with Web APIs. request.POST # Only handles form data. Only works for 'POST' method. - request.DATA # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods. + request.data # Handles arbitrary data. Works for 'POST', 'PUT' and 'PATCH' methods. ## Response objects @@ -29,7 +29,7 @@ REST framework provides two wrappers you can use to write API views. These wrappers provide a few bits of functionality such as making sure you receive `Request` instances in your view, and adding context to `Response` objects so that content negotiation can be performed. -The wrappers also provide behaviour such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exception that occurs when accessing `request.DATA` with malformed input. +The wrappers also provide behaviour such as returning `405 Method Not Allowed` responses when appropriate, and handling any `ParseError` exception that occurs when accessing `request.data` with malformed input. ## Pulling it all together @@ -55,7 +55,7 @@ We don't need our `JSONResponse` class in `views.py` anymore, so go ahead and de return Response(serializer.data) elif request.method == 'POST': - serializer = SnippetSerializer(data=request.DATA) + serializer = SnippetSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) @@ -80,7 +80,7 @@ Here is the view for an individual snippet, in the `views.py` module. return Response(serializer.data) elif request.method == 'PUT': - serializer = SnippetSerializer(snippet, data=request.DATA) + serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) @@ -92,7 +92,7 @@ Here is the view for an individual snippet, in the `views.py` module. This should all feel very familiar - it is not a lot different from working with regular Django views. -Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.DATA` can handle incoming `json` requests, but it can also handle `yaml` and other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us. +Notice that we're no longer explicitly tying our requests or responses to a given content type. `request.data` can handle incoming `json` requests, but it can also handle `yaml` and other formats. Similarly we're returning response objects with data, but allowing REST framework to render the response into the correct content type for us. ## Adding optional format suffixes to our URLs diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index 382f078a3..0a9ea3f15 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -24,7 +24,7 @@ We'll start by rewriting the root view as a class based view. All this involves return Response(serializer.data) def post(self, request, format=None): - serializer = SnippetSerializer(data=request.DATA) + serializer = SnippetSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) @@ -49,7 +49,7 @@ So far, so good. It looks pretty similar to the previous case, but we've got be def put(self, request, pk, format=None): snippet = self.get_object(pk) - serializer = SnippetSerializer(snippet, data=request.DATA) + serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) diff --git a/rest_framework/request.py b/rest_framework/request.py index 096b30424..d7e746743 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -310,7 +310,7 @@ class Request(object): def _load_data_and_files(self): """ - Parses the request content into self.DATA and self.FILES. + Parses the request content into `self.data`. """ if not _hasattr(self, '_content_type'): self._load_method_and_content_type() From a3c0c8fb90f084d494cdf46551c8a6228ca5ffd0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 12:04:35 +0000 Subject: [PATCH 041/128] Add missing period. --- docs/api-guide/generic-views.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 49a5e58f0..16ae8c554 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -352,7 +352,7 @@ You can then simply apply this mixin to a view or viewset anytime you need to ap serializer_class = UserSerializer lookup_fields = ('account', 'username') -Using custom mixins is a good option if you have custom behavior that needs to be used +Using custom mixins is a good option if you have custom behavior that needs to be used. ## Creating custom base classes From c4dff54ecc487b192d17763e48adc0bfcaa620d3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 12:04:46 +0000 Subject: [PATCH 042/128] Docs on ValidationError --- docs/api-guide/exceptions.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index 8a99abb9d..33eb74c8e 100644 --- a/docs/api-guide/exceptions.md +++ b/docs/api-guide/exceptions.md @@ -152,5 +152,23 @@ Raised when an incoming request fails the throttling checks. By default this exception results in a response with the HTTP status code "429 Too Many Requests". +## ValidationError + +**Signature:** `ValidationError(detail)` + +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. +* 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: + + serializer.is_valid(raise_exception=True) + +The generic views use the `raise_exception=True` flag, which means that you can override the style of validation error responses globally in your API. To do so, use a custom exception handler, as described above. + +By default this exception results in a response with the HTTP status code "400 Bad Request". + [cite]: http://www.doughellmann.com/articles/how-tos/python-exception-handling/index.html [authentication]: authentication.md From 83281254d1a4685535ab877fb9f9affeb93578bd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 12:06:27 +0000 Subject: [PATCH 043/128] Hypenate 'un-coerced' --- docs/topics/3.0-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 694ad8a5c..24b0923f9 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -865,7 +865,7 @@ Or modify it on an individual serializer field, using the `coerce_to_string` key coerce_to_string=False ) -The default JSON renderer will return float objects for uncoerced `Decimal` instances. This allows you to easily switch between string or float representations for decimals depending on your API design needs. +The default JSON renderer will return float objects for un-coerced `Decimal` instances. This allows you to easily switch between string or float representations for decimals depending on your API design needs. ## Miscellaneous notes. From 3b40f8c1c110ef44d112a705652b9ef57a36d9c9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 12:15:48 +0000 Subject: [PATCH 044/128] Drop 0.x release notes --- docs/topics/release-notes.md | 122 ++--------------------------------- 1 file changed, 4 insertions(+), 118 deletions(-) diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 88780c3fb..efc49ba11 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -442,7 +442,7 @@ The security vulnerabilities only affect APIs which use the `XMLParser` class, b * Bugfix: Validation errors instead of exceptions when related fields receive incorrect types. * Bugfix: Handle ObjectDoesNotExist exception when serializing null reverse one-to-one -**Note**: Prior to 2.1.16, The Decimals would render in JSON using floating point if `simplejson` was installed, but otherwise render using string notation. Now that use of `simplejson` has been deprecated, Decimals will consistently render using string notation. See [#582] for more details. +**Note**: Prior to 2.1.16, The Decimals would render in JSON using floating point if `simplejson` was installed, but otherwise render using string notation. Now that use of `simplejson` has been deprecated, Decimals will consistently render using string notation. See [ticket 582](ticket-582) for more details. ### 2.1.15 @@ -614,122 +614,7 @@ This change will not affect user code, so long as it's following the recommended * **Fix all of the things.** (Well, almost.) * For more information please see the [2.0 announcement][announcement]. ---- - -## 0.4.x series - -### 0.4.0 - -* Supports Django 1.5. -* Fixes issues with 'HEAD' method. -* Allow views to specify template used by TemplateRenderer -* More consistent error responses -* Some serializer fixes -* Fix internet explorer ajax behavior -* Minor xml and yaml fixes -* Improve setup (e.g. use staticfiles, not the defunct ADMIN_MEDIA_PREFIX) -* Sensible absolute URL generation, not using hacky set_script_prefix - ---- - -## 0.3.x series - -### 0.3.3 - -* Added DjangoModelPermissions class to support `django.contrib.auth` style permissions. -* Use `staticfiles` for css files. - - Easier to override. Won't conflict with customized admin styles (e.g. grappelli) -* Templates are now nicely namespaced. - - Allows easier overriding. -* Drop implied 'pk' filter if last arg in urlconf is unnamed. - - Too magical. Explicit is better than implicit. -* Saner template variable auto-escaping. -* Tidier setup.py -* Updated for URLObject 2.0 -* Bugfixes: - - Bug with PerUserThrottling when user contains unicode chars. - -### 0.3.2 - -* Bugfixes: - * Fix 403 for POST and PUT from the UI with UserLoggedInAuthentication (#115) - * serialize_model method in serializer.py may cause wrong value (#73) - * Fix Error when clicking OPTIONS button (#146) - * And many other fixes -* Remove short status codes - - Zen of Python: "There should be one-- and preferably only one --obvious way to do it." -* get_name, get_description become methods on the view - makes them overridable. -* Improved model mixin API - Hooks for build_query, get_instance_data, get_model, get_queryset, get_ordering - -### 0.3.1 - -* [not documented] - -### 0.3.0 - -* JSONP Support -* Bugfixes, including support for latest markdown release - ---- - -## 0.2.x series - -### 0.2.4 - -* Fix broken IsAdminUser permission. -* OPTIONS support. -* XMLParser. -* Drop mentions of Blog, BitBucket. - -### 0.2.3 - -* Fix some throttling bugs. -* ``X-Throttle`` header on throttling. -* Support for nesting resources on related models. - -### 0.2.2 - -* Throttling support complete. - -### 0.2.1 - -* Couple of simple bugfixes over 0.2.0 - -### 0.2.0 - -* Big refactoring changes since 0.1.0, ask on the discussion group if anything isn't clear. - The public API has been massively cleaned up. Expect it to be fairly stable from here on in. - -* ``Resource`` becomes decoupled into ``View`` and ``Resource``, your views should now inherit from ``View``, not ``Resource``. - -* The handler functions on views ``.get() .put() .post()`` etc, no longer have the ``content`` and ``auth`` args. - Use ``self.CONTENT`` inside a view to access the deserialized, validated content. - Use ``self.user`` inside a view to access the authenticated user. - -* ``allowed_methods`` and ``anon_allowed_methods`` are now defunct. if a method is defined, it's available. - The ``permissions`` attribute on a ``View`` is now used to provide generic permissions checking. - Use permission classes such as ``FullAnonAccess``, ``IsAuthenticated`` or ``IsUserOrIsAnonReadOnly`` to set the permissions. - -* The ``authenticators`` class becomes ``authentication``. Class names change to ``Authentication``. - -* The ``emitters`` class becomes ``renderers``. Class names change to ``Renderers``. - -* ``ResponseException`` becomes ``ErrorResponse``. - -* The mixin classes have been nicely refactored, the basic mixins are now ``RequestMixin``, ``ResponseMixin``, ``AuthMixin``, and ``ResourceMixin`` - You can reuse these mixin classes individually without using the ``View`` class. - ---- - -## 0.1.x series - -### 0.1.1 - -* Final build before pulling in all the refactoring changes for 0.2, in case anyone needs to hang on to 0.1. - -### 0.1.0 - -* Initial release. +For older release notes, [please see the GitHub repo](old-release-notes). [cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html [deprecation-policy]: #deprecation-policy @@ -742,5 +627,6 @@ This change will not affect user code, so long as it's following the recommended [staticfiles13]: https://docs.djangoproject.com/en/1.3/howto/static-files/#with-a-template-tag [2.1.0-notes]: https://groups.google.com/d/topic/django-rest-framework/Vv2M0CMY9bg/discussion [announcement]: rest-framework-2-announcement.md -[#582]: https://github.com/tomchristie/django-rest-framework/issues/582 +[ticket-582]: https://github.com/tomchristie/django-rest-framework/issues/582 [rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3 +[old-release-notes]: https://github.com/tomchristie/django-rest-framework/blob/2.4.4/docs/topics/release-notes.md#04x-series From 3a648b9f0b0aa9a955065b96ea1a03376816ec00 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Thu, 9 Oct 2014 10:14:04 +0100 Subject: [PATCH 045/128] Migrate documentation to MkDocs proper --- .gitignore | 1 + docs/{ => theme}/404.html | 0 docs/{template.html => theme/base.html} | 7 +- mkdocs.py | 203 ------------------------ mkdocs.yml | 55 +++++++ 5 files changed, 62 insertions(+), 204 deletions(-) rename docs/{ => theme}/404.html (100%) rename docs/{template.html => theme/base.html} (96%) delete mode 100755 mkdocs.py create mode 100644 mkdocs.yml diff --git a/.gitignore b/.gitignore index ae73f8379..9e17827bd 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ local/ !.gitignore !.travis.yml +site/ diff --git a/docs/404.html b/docs/theme/404.html similarity index 100% rename from docs/404.html rename to docs/theme/404.html diff --git a/docs/template.html b/docs/theme/base.html similarity index 96% rename from docs/template.html rename to docs/theme/base.html index f36cffc6d..45e19cf37 100644 --- a/docs/template.html +++ b/docs/theme/base.html @@ -187,7 +187,12 @@ a.fusion-poweredby { -->
From 49a493f61f4d5b4447d139d189d78995e65231b2 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Wed, 29 Oct 2014 20:54:31 +0000 Subject: [PATCH 055/128] Bring back the promo section --- docs/theme/base.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/theme/base.html b/docs/theme/base.html index e5ad43777..7ca7de145 100644 --- a/docs/theme/base.html +++ b/docs/theme/base.html @@ -191,7 +191,8 @@ a.fusion-poweredby {
  • {{ toc_item.title }}
  • {% endfor %}
    - {{ ad_block }} +
    +
    From 3c49b9fe4648d6fb291f0d34bf2e9ef4b72d45be Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Wed, 29 Oct 2014 21:03:00 +0000 Subject: [PATCH 056/128] Add next and previous page. --- docs/theme/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/theme/base.html b/docs/theme/base.html index 7ca7de145..0f876cc16 100644 --- a/docs/theme/base.html +++ b/docs/theme/base.html @@ -57,8 +57,8 @@ a.fusion-poweredby {
    - {% include "content.html" %} + {% if meta.source %} + {% for filename in meta.source %} + + {{ filename }} + + {% endfor %} + {% endif %} + + {{ content }}
    From 3bfc82068b068defee470910a850757e4c12df3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Padilla?= Date: Fri, 31 Oct 2014 11:38:27 -0400 Subject: [PATCH 067/128] Update tabbing and cleanup theme templates --- docs_theme/404.html | 197 +++++++++++++++++++---------------- docs_theme/base.html | 241 +++++++++++++++++++++++-------------------- docs_theme/nav.html | 51 +++++---- 3 files changed, 258 insertions(+), 231 deletions(-) diff --git a/docs_theme/404.html b/docs_theme/404.html index 864247e78..44993e37d 100644 --- a/docs_theme/404.html +++ b/docs_theme/404.html @@ -1,50 +1,54 @@ - - - Django REST framework - 404 - Page not found - - - - - - - - - - + + + + Django REST framework - 404 - Page not found + + + + + - - + + + + + - + - - - + + + + + + + + + -
    - +
    + + - - - - - - + + + + - // Dynamically force sidenav to no higher than browser window - $('.side-nav').css('max-height', window.innerHeight - 130); - - $(function(){ - $(window).resize(function(){ - $('.side-nav').css('max-height', window.innerHeight - 130); - }); - }); - - + diff --git a/docs_theme/base.html b/docs_theme/base.html index 67290df60..544e21881 100644 --- a/docs_theme/base.html +++ b/docs_theme/base.html @@ -1,56 +1,62 @@ - - - {{ page_title }} - - - - - - - - - - + + + + {{ page_title }} + + + + + - - + + + + + - - - + +{# TODO: This is a bit of a hack. We don't want to refer to the file specifically. #} -a.fusion-poweredby { - display: block; - margin-top: 10px; -} -@media (max-width: 767px) { - div.promo {display: none;} -} - - - {# TODO: This is a bit of a hack. We don't want to refer to the file specifically. #} - +
    @@ -59,32 +65,34 @@ a.fusion-poweredby {
    - - + +
    @@ -98,18 +106,16 @@ a.fusion-poweredby {
    @@ -127,42 +133,51 @@ a.fusion-poweredby { {% endif %} {{ content }} -
    -
    -
    -
    - -
    -
    + + + + + + + + +
    + + - - - - - + + + + + - + - // Dynamically force sidenav to no higher than browser window - $('.side-nav').css('max-height', window.innerHeight - 130); - - $(function(){ - $(window).resize(function(){ - $('.side-nav').css('max-height', window.innerHeight - 130); - }); - }); - - + diff --git a/docs_theme/nav.html b/docs_theme/nav.html index a7a72d68e..87e197b35 100644 --- a/docs_theme/nav.html +++ b/docs_theme/nav.html @@ -1,11 +1,10 @@ - From 06683b86b2b15153df52fe481b5c4eeb489a80cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Padilla?= Date: Fri, 31 Oct 2014 13:02:11 -0400 Subject: [PATCH 068/128] Use single quotes for consistency Conflicts: mkdocs.yml --- mkdocs.yml | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index 2d1886a05..c70de9822 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -13,30 +13,30 @@ pages: - ['tutorial/4-authentication-and-permissions.md', ] - ['tutorial/5-relationships-and-hyperlinked-apis.md', ] - ['tutorial/6-viewsets-and-routers.md', ] - - ['api-guide/requests.md', "API Guide", ] - - ['api-guide/responses.md', "API Guide", ] - - ['api-guide/views.md', "API Guide", ] - - ['api-guide/generic-views.md', "API Guide", ] - - ['api-guide/viewsets.md', "API Guide", ] - - ['api-guide/routers.md', "API Guide", ] - - ['api-guide/parsers.md', "API Guide", ] - - ['api-guide/renderers.md', "API Guide", ] - - ['api-guide/serializers.md', "API Guide", ] - - ['api-guide/fields.md', "API Guide", ] - - ['api-guide/relations.md', "API Guide", ] - - ['api-guide/validators.md', "API Guide", ] - - ['api-guide/authentication.md', "API Guide", ] - - ['api-guide/permissions.md', "API Guide", ] - - ['api-guide/throttling.md', "API Guide", ] - - ['api-guide/filtering.md', "API Guide", ] - - ['api-guide/pagination.md', "API Guide", ] - - ['api-guide/content-negotiation.md', "API Guide", ] - - ['api-guide/format-suffixes.md', "API Guide", ] - - ['api-guide/reverse.md', "API Guide", ] - - ['api-guide/exceptions.md', "API Guide", ] - - ['api-guide/status-codes.md', "API Guide", ] - - ['api-guide/testing.md', "API Guide", ] - - ['api-guide/settings.md', "API Guide", ] + - ['api-guide/requests.md', 'API Guide', ] + - ['api-guide/responses.md', 'API Guide', ] + - ['api-guide/views.md', 'API Guide', ] + - ['api-guide/generic-views.md', 'API Guide', ] + - ['api-guide/viewsets.md', 'API Guide', ] + - ['api-guide/routers.md', 'API Guide', ] + - ['api-guide/parsers.md', 'API Guide', ] + - ['api-guide/renderers.md', 'API Guide', ] + - ['api-guide/serializers.md', 'API Guide', ] + - ['api-guide/fields.md', 'API Guide', ] + - ['api-guide/relations.md', 'API Guide', ] + - ['api-guide/validators.md', 'API Guide', ] + - ['api-guide/authentication.md', 'API Guide', ] + - ['api-guide/permissions.md', 'API Guide', ] + - ['api-guide/throttling.md', 'API Guide', ] + - ['api-guide/filtering.md', 'API Guide', ] + - ['api-guide/pagination.md', 'API Guide', ] + - ['api-guide/content-negotiation.md', 'API Guide', ] + - ['api-guide/format-suffixes.md', 'API Guide', ] + - ['api-guide/reverse.md', 'API Guide', ] + - ['api-guide/exceptions.md', 'API Guide', ] + - ['api-guide/status-codes.md', 'API Guide', ] + - ['api-guide/testing.md', 'API Guide', ] + - ['api-guide/settings.md', 'API Guide', ] - ['topics/documenting-your-api.md', ] - ['topics/ajax-csrf-cors.md', ] - ['topics/browser-enhancements.md', ] From 200e0b17daecd07de6d1f9926a430d29b3ee948f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Padilla?= Date: Fri, 31 Oct 2014 13:03:39 -0400 Subject: [PATCH 069/128] Clean up extra white space --- docs/topics/2.2-announcement.md | 8 ++++---- docs/topics/2.3-announcement.md | 8 ++++---- docs/topics/documenting-your-api.md | 8 ++++---- docs/topics/kickstarter-announcement.md | 2 +- docs/topics/release-notes.md | 2 +- docs/topics/rest-framework-2-announcement.md | 4 ++-- docs/topics/writable-nested-serializers.md | 10 +++++----- docs/tutorial/1-serialization.md | 2 +- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/topics/2.2-announcement.md b/docs/topics/2.2-announcement.md index a997c7829..1df52cff2 100644 --- a/docs/topics/2.2-announcement.md +++ b/docs/topics/2.2-announcement.md @@ -42,7 +42,7 @@ The 2.2 release makes a few changes to the API, in order to make it more consist The `ManyRelatedField()` style is being deprecated in favor of a new `RelatedField(many=True)` syntax. -For example, if a user is associated with multiple questions, which we want to represent using a primary key relationship, we might use something like the following: +For example, if a user is associated with multiple questions, which we want to represent using a primary key relationship, we might use something like the following: class UserSerializer(serializers.HyperlinkedModelSerializer): questions = serializers.PrimaryKeyRelatedField(many=True) @@ -58,10 +58,10 @@ The change also applies to serializers. If you have a nested serializer, you sh class Meta: model = Track fields = ('name', 'duration') - + class AlbumSerializer(serializer.ModelSerializer): tracks = TrackSerializer(many=True) - + class Meta: model = Album fields = ('album_name', 'artist', 'tracks') @@ -87,7 +87,7 @@ For example, is a user account has an optional foreign key to a company, that yo This is in line both with the rest of the serializer fields API, and with Django's `Form` and `ModelForm` API. -Using `required` throughout the serializers API means you won't need to consider if a particular field should take `blank` or `null` arguments instead of `required`, and also means there will be more consistent behavior for how fields are treated when they are not present in the incoming data. +Using `required` throughout the serializers API means you won't need to consider if a particular field should take `blank` or `null` arguments instead of `required`, and also means there will be more consistent behavior for how fields are treated when they are not present in the incoming data. The `null=True` argument will continue to function, and will imply `required=False`, but will raise a `PendingDeprecationWarning`. diff --git a/docs/topics/2.3-announcement.md b/docs/topics/2.3-announcement.md index 7c800afa0..9c9f3e9f6 100644 --- a/docs/topics/2.3-announcement.md +++ b/docs/topics/2.3-announcement.md @@ -27,7 +27,7 @@ As an example of just how simple REST framework APIs can now be, here's an API w class GroupViewSet(viewsets.ModelViewSet): model = Group - + # Routers provide an easy way of automatically determining the URL conf router = routers.DefaultRouter() router.register(r'users', UserViewSet) @@ -197,13 +197,13 @@ Usage of the old-style attributes continues to be supported, but will raise a `P For most cases APIs using model fields will behave as previously, however if you are using a custom renderer, not provided by REST framework, then you may now need to add support for rendering `Decimal` instances to your renderer implementation. -## ModelSerializers and reverse relationships +## ModelSerializers and reverse relationships The support for adding reverse relationships to the `fields` option on a `ModelSerializer` class means that the `get_related_field` and `get_nested_field` method signatures have now changed. In the unlikely event that you're providing a custom serializer class, and implementing these methods you should note the new call signature for both methods is now `(self, model_field, related_model, to_many)`. For reverse relationships `model_field` will be `None`. -The old-style signature will continue to function but will raise a `PendingDeprecationWarning`. +The old-style signature will continue to function but will raise a `PendingDeprecationWarning`. ## View names and descriptions @@ -211,7 +211,7 @@ The mechanics of how the names and descriptions used in the browseable API are g If you've been customizing this behavior, for example perhaps to use `rst` markup for the browseable API, then you'll need to take a look at the implementation to see what updates you need to make. -Note that the relevant methods have always been private APIs, and the docstrings called them out as intended to be deprecated. +Note that the relevant methods have always been private APIs, and the docstrings called them out as intended to be deprecated. --- diff --git a/docs/topics/documenting-your-api.md b/docs/topics/documenting-your-api.md index e20f97122..d65e251f1 100644 --- a/docs/topics/documenting-your-api.md +++ b/docs/topics/documenting-your-api.md @@ -54,7 +54,7 @@ The title that is used in the browsable API is generated from the view class nam For example, the view `UserListView`, will be named `User List` when presented in the browsable API. -When working with viewsets, an appropriate suffix is appended to each generated view. For example, the view set `UserViewSet` will generate views named `User List` and `User Instance`. +When working with viewsets, an appropriate suffix is appended to each generated view. For example, the view set `UserViewSet` will generate views named `User List` and `User Instance`. #### Setting the description @@ -65,9 +65,9 @@ If the python `markdown` library is installed, then [markdown syntax][markdown] class AccountListView(views.APIView): """ Returns a list of all **active** accounts in the system. - + For more details on how accounts are activated please [see here][ref]. - + [ref]: http://example.com/activating-accounts """ @@ -84,7 +84,7 @@ You can modify the response behavior to `OPTIONS` requests by overriding the `me def metadata(self, request): """ Don't include the view description in OPTIONS responses. - """ + """ data = super(ExampleView, self).metadata(request) data.pop('description') return data diff --git a/docs/topics/kickstarter-announcement.md b/docs/topics/kickstarter-announcement.md index 7d1f6d0eb..e8bad95be 100644 --- a/docs/topics/kickstarter-announcement.md +++ b/docs/topics/kickstarter-announcement.md @@ -160,4 +160,4 @@ The following individuals made a significant financial contribution to the devel ### Supporters -There were also almost 300 further individuals choosing to help fund the project at other levels or choosing to give anonymously. Again, thank you, thank you, thank you! \ No newline at end of file +There were also almost 300 further individuals choosing to help fund the project at other levels or choosing to give anonymously. Again, thank you, thank you, thank you! diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 88780c3fb..9fca949ab 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -63,7 +63,7 @@ You can determine your currently installed version using `pip freeze`: * Bugfix: Fix migration in `authtoken` application. * Bugfix: Allow selection of integer keys in nested choices. * Bugfix: Return `None` instead of `'None'` in `CharField` with `allow_none=True`. -* Bugfix: Ensure custom model fields map to equivelent serializer fields more reliably. +* Bugfix: Ensure custom model fields map to equivelent serializer fields more reliably. * Bugfix: `DjangoFilterBackend` no longer quietly changes queryset ordering. ### 2.4.2 diff --git a/docs/topics/rest-framework-2-announcement.md b/docs/topics/rest-framework-2-announcement.md index f1060d90b..a7746932e 100644 --- a/docs/topics/rest-framework-2-announcement.md +++ b/docs/topics/rest-framework-2-announcement.md @@ -8,7 +8,7 @@ What it is, and why you should care. --- -**Announcement:** REST framework 2 released - Tue 30th Oct 2012 +**Announcement:** REST framework 2 released - Tue 30th Oct 2012 --- @@ -37,7 +37,7 @@ REST framework 2 includes a totally re-worked serialization engine, that was ini * A declarative serialization API, that mirrors Django's `Forms`/`ModelForms` API. * Structural concerns are decoupled from encoding concerns. * Able to support rendering and parsing to many formats, including both machine-readable representations and HTML forms. -* Validation that can be mapped to obvious and comprehensive error responses. +* Validation that can be mapped to obvious and comprehensive error responses. * Serializers that support both nested, flat, and partially-nested representations. * Relationships that can be expressed as primary keys, hyperlinks, slug fields, and other custom representations. diff --git a/docs/topics/writable-nested-serializers.md b/docs/topics/writable-nested-serializers.md index abc6a82f7..ed614bd24 100644 --- a/docs/topics/writable-nested-serializers.md +++ b/docs/topics/writable-nested-serializers.md @@ -8,7 +8,7 @@ Although flat data structures serve to properly delineate between the individual Nested data structures are easy enough to work with if they're read-only - simply nest your serializer classes and you're good to go. However, there are a few more subtleties to using writable nested serializers, due to the dependencies between the various model instances, and the need to save or delete multiple instances in a single action. -## One-to-many data structures +## One-to-many data structures *Example of a **read-only** nested serializer. Nothing complex to worry about here.* @@ -16,10 +16,10 @@ Nested data structures are easy enough to work with if they're read-only - simpl class Meta: model = ToDoItem fields = ('text', 'is_completed') - + class ToDoListSerializer(serializers.ModelSerializer): items = ToDoItemSerializer(many=True, read_only=True) - + class Meta: model = ToDoList fields = ('title', 'items') @@ -31,7 +31,7 @@ Some example output from our serializer. 'items': { {'text': 'Compile playlist', 'is_completed': True}, {'text': 'Send invites', 'is_completed': False}, - {'text': 'Clean house', 'is_completed': False} + {'text': 'Clean house', 'is_completed': False} } } @@ -44,4 +44,4 @@ Let's take a look at updating our nested one-to-many data structure. ### Making PATCH requests -[cite]: http://jsonapi.org/format/#url-based-json-api \ No newline at end of file +[cite]: http://jsonapi.org/format/#url-based-json-api diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index db5b9ea7b..f9027b688 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -134,7 +134,7 @@ A serializer class is very similar to a Django `Form` class, and includes simila The field flags can also control how the serializer should be displayed in certain circumstances, such as when rendering to HTML. The `style={'type': 'textarea'}` flag above is equivelent to using `widget=widgets.Textarea` on a Django `Form` class. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial. -We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit. +We can actually also save ourselves some time by using the `ModelSerializer` class, as we'll see later, but for now we'll keep our serializer definition explicit. ## Working with Serializers From b8aa7e0c34dc839a47b679aa2402d0f1b98704a0 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Tue, 18 Nov 2014 17:16:54 +0000 Subject: [PATCH 070/128] Fix previous and next links --- docs_theme/nav.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs_theme/nav.html b/docs_theme/nav.html index 87e197b35..0f3b9871c 100644 --- a/docs_theme/nav.html +++ b/docs_theme/nav.html @@ -2,8 +2,12 @@