diff options
Diffstat (limited to 'docs/topics/3.0-announcement.md')
| -rw-r--r-- | docs/topics/3.0-announcement.md | 205 |
1 files changed, 161 insertions, 44 deletions
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 885fc183..8791ad08 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -1,25 +1,17 @@ -## Pre-release notes: +# Django REST framework 3.0 -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 of Django REST framework is the result of almost four years of iteration and refinement. It comprehensively addresses some of the previous remaining design issues in serializers, fields and the generic views. - pip install https://github.com/tomchristie/django-rest-framework/archive/master.zip +**This release is incremental in nature. There *are* some breaking API changes, and upgrading *will* require you to read the release notes carefully, but the migration path should otherwise be relatively straightforward.** -See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-framework/pull/1800) for more details on remaining work. +The difference in quality of the REST framework API and implementation should make writing, maintaining and debugging your application far easier. -**Your feedback on the upgrade process and 3.0 changes is hugely important!** +3.0 is the first of three releases that have been funded by our recent [Kickstarter campaign][kickstarter]. -Please do get in touch via twitter, IRC, a GitHub ticket, or the discussion group. +As ever, a huge thank you to our many [wonderful sponsors][sponsors]. If you're looking for a Django gig, and want to work with smart community-minded folks, you should probably check out that list and see who's hiring. --- -# REST framework 3.0 - -The 3.0 release of Django REST framework is the result of almost four years of iteration and refinement. It comprehensively addresses some of the previous remaining design issues in serializers, fields and the generic views. - -This release is incremental in nature. There *are* some breaking API changes, and upgrading *will* require you to read the release notes carefully, but the migration path should otherwise be relatively straightforward. - -The difference in quality of the REST framework API and implementation should make writing, maintaining and debugging your application far easier. - ## New features Notable features of this new release include: @@ -32,6 +24,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 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. @@ -58,6 +53,8 @@ Instead of passing the files argument separately: The usage of `request.QUERY_PARAMS` is now pending deprecation in favor of the lowercased `request.query_params`. +--- + ## Serializers #### Single-step object creation. @@ -84,6 +81,8 @@ The resulting API changes are further detailed below. The `.restore_object()` method is now replaced with two separate methods, `.create()` and `.update()`. +These methods also replace the optional `.save_object()` method, which no longer exists. + When using the `.create()` and `.update()` methods you should both create *and save* the object instance. This is in contrast to the previous `.restore_object()` behavior that would instantiate the object but not save it. The following example from the tutorial previously used `restore_object()` to handle both creating and updating object instances. @@ -138,13 +137,23 @@ 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. The reason behind this is that Django's `ValidationError` class is intended for use with HTML forms and its API makes using it slightly awkward with nested validation errors that can occur in serializers. -For most users this change shouldn't require any updates to your codebase, but it is worth ensuring that whenever raising validation errors you are always using the `serializers.ValidationError` exception class, and not Django's built-in exception. +For most users this change shouldn't require any updates to your codebase, but it is worth ensuring that whenever raising validation errors you should prefer using the `serializers.ValidationError` exception class, and not Django's built-in exception. We strongly recommend that you use the namespaced import style of `import serializers` and not `from serializers import ValidationError` in order to avoid any potential confusion. @@ -153,7 +162,7 @@ We strongly recommend that you use the namespaced import style of `import serial The `validate_<field_name>` 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 @@ -172,21 +181,59 @@ 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. +#### Removal of `transform_<field_name>`. + +The under-used `transform_<field_name>` on serializer classes is no longer provided. Instead you should just override `to_representation()` if you need to apply any modifications to the representation style. + +For example: + + def to_representation(self, instance): + ret = super(UserSerializer, self).to_representation(instance) + ret['username'] = ret['username'].lower() + return ret + +Dropping the extra point of API means there's now only one right way to do things. This helps with repetition and reinforcement of the core API, rather than having multiple differing approaches. + +If you absolutely need to preserve `transform_<field_name>` behavior, for example, in order to provide a simpler 2.x to 3.0 upgrade, you can use a mixin, or serializer base class that add the behavior back in. For example: + + class BaseModelSerializer(ModelSerializer): + """ + A custom ModelSerializer class that preserves 2.x style `transform_<field_name>` behavior. + """ + def to_representation(self, instance): + ret = super(BaseModelSerializer, self).to_representation(instance) + for key, value in ret.items(): + method = getattr(self, 'transform_' + key, None) + if method is not None: + ret[key] = method(value) + return ret + #### Differences between ModelSerializer validation and ModelForm. This change also means that we no longer use the `.full_clean()` method on model instances, but instead perform all validation explicitly on the serializer. This gives a cleaner separation, and ensures that there's no automatic validation behavior on `ModelSerializer` classes that can't also be easily replicated on regular `Serializer` classes. -It's important to note that this change also means that the model `.clean()` method will not be called as part of serializer validation, as it would be if using a `ModelForm`. Use the serializer `.validate()` method to perform a final validation step on incoming data where required. +For the most part this change should be transparent. Field validation and uniqueness checks will still be run as normal, but the implementation is a little different. + +The one difference that you do need to note is that the `.clean()` method will not be called as part of serializer validation, as it would be if using a `ModelForm`. Use the serializer `.validate()` method to perform a final validation step on incoming data where required. + +There may be some cases where you really do need to keep validation logic in the model `.clean()` method, and cannot instead separate it into the serializer `.validate()`. You can do so by explicitly instantiating a model instance in the `.validate()` method. + + def validate(self, attrs): + instance = ExampleModel(**attrs) + instance.clean() + return attrs + +Again, you really should look at properly separating the validation logic out of the model method if possible, but the above might be useful in some backwards compatibility cases, or for an easy migration path. #### Writable nested serialization. @@ -286,7 +333,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`. @@ -297,7 +344,7 @@ The `view_name` and `lookup_field` options have been moved to `PendingDeprecatio model = MyModel fields = ('url', 'email', 'notes', 'is_admin') extra_kwargs = { - 'url': {'lookup_field': 'uuid'} + 'url': {'lookup_field': 'uuid'} } Alternatively, specify the field explicitly on the serializer class: @@ -347,7 +394,9 @@ 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. 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. @@ -367,7 +416,11 @@ There are four methods that can be overridden, depending on what functionality y * `.to_representation()` - Override this to support serialization, for read operations. * `.to_internal_value()` - Override this to support deserialization, for write operations. -* `.create()` and `.update()` - Overide either or both of these to support saving instances. +* `.create()` and `.update()` - Override either or both of these to support saving instances. + +Because this class provides the same interface as the `Serializer` class, you can use it with the existing generic class based views exactly as you would for a regular `Serializer` or `ModelSerializer`. + +The only difference you'll notice when doing so is the `BaseSerializer` classes will not generate HTML forms in the browsable API. This is because the data they return does not include all the field information that would allow each field to be rendered into a suitable HTML input. ##### Read-only `BaseSerializer` classes. @@ -417,7 +470,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({ @@ -431,7 +484,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 { @@ -444,7 +497,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) @@ -452,7 +505,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 arbitrary objects into primitive representations. class ObjectSerializer(serializers.BaseSerializer): """ @@ -472,12 +525,12 @@ The following class is an example of a generic serializer that can handle coerci # Primitive types can be passed through unmodified. output[attribute_name] = attribute elif isinstance(attribute, list): - # Recursivly deal with items in lists. + # Recursively deal with items in lists. output[attribute_name] = [ self.to_representation(item) for item in attribute ] elif isinstance(attribute, dict): - # Recursivly deal with items in dictionarys. + # Recursively deal with items in dictionaries. output[attribute_name] = { str(key): self.to_representation(value) for key, value in attribute.items() @@ -486,6 +539,8 @@ The following class is an example of a generic serializer that can handle coerci # Force anything else to its string representation. output[attribute_name] = str(attribute) +--- + ## Serializer fields #### The `Field` and `ReadOnly` field classes. @@ -502,28 +557,41 @@ We now use the following: * `Field` is the base class for all fields. It does not include any default implementation for either serializing or deserializing data. * `ReadOnlyField` is a concrete implementation for read-only fields that simply returns the attribute value without modification. -#### The `required`, `allow_none`, `allow_blank` and `default` arguments. +#### The `required`, `allow_null`, `allow_blank` and `default` arguments. REST framework now has more explicit and clear control over validating empty values for fields. Previously the meaning of the `required=False` keyword argument was underspecified. In practice its use meant that a field could either be not included in the input, or it could be included, but be `None` or the empty string. -We now have a better separation, with separate `required`, `allow_none` and `allow_blank` arguments. +We now have a better separation, with separate `required`, `allow_null` and `allow_blank` arguments. The following set of arguments are used to control validation of empty values: * `required=False`: The value does not need to be present in the input, and will not be passed to `.create()` or `.update()` if it is not seen. * `default=<value>`: The value does not need to be present in the input, and a default value will be passed to `.create()` or `.update()` if it is not seen. -* `allow_none=True`: `None` is a valid input. +* `allow_null=True`: `None` is a valid input. * `allow_blank=True`: `''` is valid input. For `CharField` and subclasses only. -Typically you'll want to use `required=False` if the corresponding model field has a default value, and additionally set either `allow_none=True` or `allow_blank=True` if required. +Typically you'll want to use `required=False` if the corresponding model field has a default value, and additionally set either `allow_null=True` or `allow_blank=True` if required. The `default` argument is also available and always implies that the field is not required to be in the input. It is unnecessary to use the `required` argument when a default is specified, and doing so will result in an error. #### 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. + +#### Removal of `.validate()`. + +The `.validate()` method is now removed from field classes. This method was in any case undocumented and not public API. You should instead simply override `to_internal_value()`. + + class UppercaseCharField(serializers.CharField): + def to_internal_value(self, data): + value = super(UppercaseCharField, self).to_internal_value(data) + if value != value.upper(): + raise serializers.ValidationError('The input should be uppercase only.') + return value + +Previously validation errors could be raised in either `.to_native()` or `.validate()`, making it non-obvious which should be used. Providing only a single point of API ensures more repetition and reinforcement of the core API. #### The `ListField` class. @@ -659,14 +727,19 @@ The `UniqueTogetherValidator` should be applied to a serializer, and takes a `qu position = serializers.IntegerField() name = serializers.CharField(max_length=100) - default_validators = [UniqueTogetherValidator( - queryset=RaceResult.objects.all(), - fields=('category', 'position') - )] + class Meta: + validators = [UniqueTogetherValidator( + queryset=RaceResult.objects.all(), + fields=('category', 'position') + )] #### 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 @@ -674,7 +747,7 @@ The `UniqueTogetherValidator` should be applied to a serializer, and takes a `qu 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)`. @@ -716,15 +789,48 @@ The generic views now raise `ValidationFailed` exception for invalid data. This This change means that you can now easily customize the style of error responses across your entire API, without having to modify any of the generic views. +--- + ## The metadata API Behavior for dealing with `OPTIONS` requests was previously built directly into the class based views. This has now been properly separated out into a Metadata API that allows the same pluggable style as other API policies in REST framework. This makes it far easier to use a different style for `OPTIONS` responses throughout your API, and makes it possible to create third-party metadata policies. +--- + ## 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 @@ -817,9 +923,18 @@ 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 + +* 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. +## 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. @@ -834,4 +949,6 @@ The 3.2 release is planned to introduce an alternative admin-style interface to You can follow development on the GitHub site, where we use [milestones to indicate planning timescales](https://github.com/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 +[kickstarter]: http://kickstarter.com/projects/tomchristie/django-rest-framework-3 +[sponsors]: http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors +[mixins.py]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py |
