aboutsummaryrefslogtreecommitdiffstats
path: root/docs/topics
diff options
context:
space:
mode:
authorCarlton Gibson2014-11-18 16:42:39 +0100
committerCarlton Gibson2014-11-18 16:42:39 +0100
commitc50a42bddc66e28d624cd3caadd2d63502ac2e6e (patch)
treebf03e2dcb23c2e4f372ff00cba4a810a96c24681 /docs/topics
parentba52c0c62762b9976fffa72dde7ce922176e481d (diff)
parent080bd3d24e1866df2acc3aae0ec0f97ebe3a8c36 (diff)
downloaddjango-rest-framework-c50a42bddc66e28d624cd3caadd2d63502ac2e6e.tar.bz2
Merge branch 'master' of github.com:tomchristie/django-rest-framework
Diffstat (limited to 'docs/topics')
-rw-r--r--docs/topics/2.4-announcement.md2
-rw-r--r--docs/topics/3.0-announcement.md839
-rw-r--r--docs/topics/ajax-csrf-cors.md6
-rw-r--r--docs/topics/contributing.md32
-rw-r--r--docs/topics/release-notes.md12
-rw-r--r--docs/topics/rest-hypermedia-hateoas.md4
-rw-r--r--docs/topics/writable-nested-serializers.md2
7 files changed, 873 insertions, 24 deletions
diff --git a/docs/topics/2.4-announcement.md b/docs/topics/2.4-announcement.md
index 8e4f3bb2..f38c743c 100644
--- a/docs/topics/2.4-announcement.md
+++ b/docs/topics/2.4-announcement.md
@@ -23,7 +23,7 @@ The documentation has previously stated that usage of the more explicit style is
Doing so will mean that there are cases of API code where you'll now need to include a serializer class where you previously were just using the `.model` shortcut. However we firmly believe that it is the right trade-off to make.
-Removing the shortcut takes away an unneccessary layer of abstraction, and makes your codebase more explicit without any significant extra complexity. It also results in better consistency, as there's now only one way to set the serializer class and queryset attributes for the view, instead of two.
+Removing the shortcut takes away an unnecessary layer of abstraction, and makes your codebase more explicit without any significant extra complexity. It also results in better consistency, as there's now only one way to set the serializer class and queryset attributes for the view, instead of two.
The `DEFAULT_MODEL_SERIALIZER_CLASS` API setting is now also deprecated.
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md
new file mode 100644
index 00000000..90cbda4d
--- /dev/null
+++ b/docs/topics/3.0-announcement.md
@@ -0,0 +1,839 @@
+## 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:
+
+ pip install https://github.com/tomchristie/django-rest-framework/archive/master.zip
+
+See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-framework/pull/1800) for more details on remaining work.
+
+**Your feedback on the upgrade process and 3.0 changes is hugely important!**
+
+Please do get in touch via twitter, IRC, a GitHub ticket, or the discussion group.
+
+---
+
+# 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:
+
+* Printable representations on serializers that allow you to inspect exactly what fields are present on the instance.
+* Simple model serializers that are vastly easier to understand and debug, and that make it easy to switch between the implicit `ModelSerializer` class and the explicit `Serializer` class.
+* A new `BaseSerializer` class, making it easier to write serializers for alternative storage backends, or to completely customize your serialization and validation logic.
+* A cleaner fields API including new classes such as `ListField` and `MultipleChoiceField`.
+* [Super simple default implementations][mixins.py] for the generic views.
+* 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.
+
+Below is an in-depth guide to the API changes and migration notes for 3.0.
+
+---
+
+## Request objects
+
+#### The `.data` and `.query_params` properties.
+
+The usage of `request.DATA` and `request.FILES` is now pending deprecation in favor of a single `request.data` attribute that contains *all* the parsed data.
+
+Having separate attributes is reasonable for web applications that only ever parse url-encoded or multipart requests, but makes less sense for the general-purpose request parsing that REST framework supports.
+
+You may now pass all the request data to a serializer class in a single argument:
+
+ # Do this...
+ ExampleSerializer(data=request.data)
+
+Instead of passing the files argument separately:
+
+ # Don't do this...
+ ExampleSerializer(data=request.DATA, files=request.FILES)
+
+
+The usage of `request.QUERY_PARAMS` is now pending deprecation in favor of the lowercased `request.query_params`.
+
+## Serializers
+
+#### Single-step object creation.
+
+Previously the serializers used a two-step object creation, as follows:
+
+1. Validating the data would create an object instance. This instance would be available as `serializer.object`.
+2. Calling `serializer.save()` would then save the object instance to the database.
+
+This style is in-line with how the `ModelForm` class works in Django, but is problematic for a number of reasons:
+
+* Some data, such as many-to-many relationships, cannot be added to the object instance until after it has been saved. This type of data needed to be hidden in some undocumented state on the object instance, or kept as state on the serializer instance so that it could be used when `.save()` is called.
+* Instantiating model instances directly means that you cannot use model manager classes for instance creation, e.g. `ExampleModel.objects.create(...)`. Manager classes are an excellent layer at which to enforce business logic and application-level data constraints.
+* The two step process makes it unclear where to put deserialization logic. For example, should extra attributes such as the current user get added to the instance during object creation or during object save?
+
+We now use single-step object creation, like so:
+
+1. Validating the data makes the cleaned data available as `serializer.validated_data`.
+2. Calling `serializer.save()` then saves and returns the new object instance.
+
+The resulting API changes are further detailed below.
+
+#### The `.create()` and `.update()` methods.
+
+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.
+
+ def restore_object(self, attrs, instance=None):
+ if instance:
+ # Update existing instance
+ instance.title = attrs.get('title', instance.title)
+ instance.code = attrs.get('code', instance.code)
+ instance.linenos = attrs.get('linenos', instance.linenos)
+ instance.language = attrs.get('language', instance.language)
+ instance.style = attrs.get('style', instance.style)
+ return instance
+
+ # Create new instance
+ return Snippet(**attrs)
+
+This would now be split out into two separate methods.
+
+ def update(self, instance, validated_data):
+ instance.title = validated_data.get('title', instance.title)
+ instance.code = validated_data.get('code', instance.code)
+ instance.linenos = validated_data.get('linenos', instance.linenos)
+ instance.language = validated_data.get('language', instance.language)
+ instance.style = validated_data.get('style', instance.style)
+ instance.save()
+ return instance
+
+ def create(self, validated_data):
+ return Snippet.objects.create(**validated_data)
+
+Note that these methods should return the newly created object instance.
+
+#### Use `.validated_data` instead of `.object`.
+
+You must now use the `.validated_data` attribute if you need to inspect the data before saving, rather than using the `.object` attribute, which no longer exists.
+
+For example the following code *is no longer valid*:
+
+ if serializer.is_valid():
+ name = serializer.object.name # Inspect validated field data.
+ logging.info('Creating ticket "%s"' % name)
+ serializer.object.user = request.user # Include the user when saving.
+ serializer.save()
+
+Instead of using `.object` to inspect a partially constructed instance, you would now use `.validated_data` to inspect the cleaned incoming values. Also you can't set extra attributes on the instance directly, but instead pass them to the `.save()` method as keyword arguments.
+
+The corresponding code would now look like this:
+
+ if serializer.is_valid():
+ name = serializer.validated_data['name'] # Inspect validated field data.
+ logging.info('Creating ticket "%s"' % name)
+ serializer.save(user=request.user) # Include the user when saving.
+
+#### 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.
+
+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.
+
+#### Change to `validate_<field_name>`.
+
+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:
+ raise serializers.ValidationError('This field should be a multiple of ten.')
+ return attrs
+
+This is now simplified slightly, and the method hooks simply take the value to be validated, and return the validated value.
+
+ def validate_score(self, value):
+ if value % 10 != 0:
+ raise serializers.ValidationError('This field should be a multiple of ten.')
+ return value
+
+Any ad-hoc validation that applies to more than one field should go in the `.validate(self, attrs)` method as usual.
+
+Because `.validate_<field_name>` would previously accept the complete dictionary of attributes, it could be used to validate a field depending on the input in another field. Now if you need to do this you should use `.validate()` instead.
+
+You can either return `non_field_errors` from the validate method by raising a simple `ValidationError`
+
+ def validate(self, attrs):
+ # serializer.errors == {'non_field_errors': ['A non field error']}
+ raise serailizers.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'})
+
+This ensures you can still write validation that compares all the input fields, but that marks the error against a particular field.
+
+#### 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.
+
+#### Writable nested serialization.
+
+REST framework 2.x attempted to automatically support writable nested serialization, but the behavior was complex and non-obvious. Attempting to automatically handle these case is problematic:
+
+* There can be complex dependencies involved in order of saving multiple related model instances.
+* It's unclear what behavior the user should expect when related models are passed `None` data.
+* It's unclear how the user should expect to-many relationships to handle updates, creations and deletions of multiple records.
+
+Using the `depth` option on `ModelSerializer` will now create **read-only nested serializers** by default.
+
+If you try to use a writable nested serializer without writing a custom `create()` and/or `update()` method you'll see an assertion error when you attempt to save the serializer. For example:
+
+ >>> class ProfileSerializer(serializers.ModelSerializer):
+ >>> class Meta:
+ >>> model = Profile
+ >>> fields = ('address', 'phone')
+ >>>
+ >>> class UserSerializer(serializers.ModelSerializer):
+ >>> profile = ProfileSerializer()
+ >>> class Meta:
+ >>> model = User
+ >>> fields = ('username', 'email', 'profile')
+ >>>
+ >>> data = {
+ >>> 'username': 'lizzy',
+ >>> 'email': 'lizzy@example.com',
+ >>> 'profile': {'address': '123 Acacia Avenue', 'phone': '01273 100200'}
+ >>> }
+ >>>
+ >>> serializer = UserSerializer(data=data)
+ >>> serializer.save()
+ AssertionError: The `.create()` method does not support nested writable fields by default. Write an explicit `.create()` method for serializer `UserSerializer`, or set `read_only=True` on nested serializer fields.
+
+To use writable nested serialization you'll want to declare a nested field on the serializer class, and write the `create()` and/or `update()` methods explicitly.
+
+ class UserSerializer(serializers.ModelSerializer):
+ profile = ProfileSerializer()
+
+ class Meta:
+ model = User
+ fields = ('username', 'email', 'profile')
+
+ def create(self, validated_data):
+ profile_data = validated_data.pop('profile')
+ user = User.objects.create(**validated_data)
+ Profile.objects.create(user=user, **profile_data)
+ return user
+
+The single-step object creation makes this far simpler and more obvious than the previous `.restore_object()` behavior.
+
+#### Printable serializer representations.
+
+Serializer instances now support a printable representation that allows you to inspect the fields present on the instance.
+
+For instance, given the following example model:
+
+ class LocationRating(models.Model):
+ location = models.CharField(max_length=100)
+ rating = models.IntegerField()
+ created_by = models.ForeignKey(User)
+
+Let's create a simple `ModelSerializer` class corresponding to the `LocationRating` model.
+
+ class LocationRatingSerializer(serializer.ModelSerializer):
+ class Meta:
+ model = LocationRating
+
+We can now inspect the serializer representation in the Django shell, using `python manage.py shell`...
+
+ >>> serializer = LocationRatingSerializer()
+ >>> print(serializer) # Or use `print serializer` in Python 2.x
+ LocationRatingSerializer():
+ id = IntegerField(label='ID', read_only=True)
+ location = CharField(max_length=100)
+ rating = IntegerField()
+ created_by = PrimaryKeyRelatedField(queryset=User.objects.all())
+
+#### The `extra_kwargs` option.
+
+The `write_only_fields` option on `ModelSerializer` has been moved to `PendingDeprecation` and replaced with a more generic `extra_kwargs`.
+
+ class MySerializer(serializer.ModelSerializer):
+ class Meta:
+ model = MyModel
+ fields = ('id', 'email', 'notes', 'is_admin')
+ extra_kwargs = {
+ 'is_admin': {'write_only': True}
+ }
+
+Alternatively, specify the field explicitly on the serializer class:
+
+ class MySerializer(serializer.ModelSerializer):
+ is_admin = serializers.BooleanField(write_only=True)
+
+ class Meta:
+ model = MyModel
+ fields = ('id', 'email', 'notes', 'is_admin')
+
+The `read_only_fields` option remains as a convenient shortcut for the more common case.
+
+#### Changes to `HyperlinkedModelSerializer`.
+
+The `view_name` and `lookup_field` options have been moved to `PendingDeprecation`. They are no longer required, as you can use the `extra_kwargs` argument instead:
+
+ class MySerializer(serializer.HyperlinkedModelSerializer):
+ class Meta:
+ model = MyModel
+ fields = ('url', 'email', 'notes', 'is_admin')
+ extra_kwargs = {
+ 'url': {'lookup_field': 'uuid'}
+ }
+
+Alternatively, specify the field explicitly on the serializer class:
+
+ class MySerializer(serializer.HyperlinkedModelSerializer):
+ url = serializers.HyperlinkedIdentityField(
+ view_name='mymodel-detail',
+ lookup_field='uuid'
+ )
+
+ class Meta:
+ model = MyModel
+ fields = ('url', 'email', 'notes', 'is_admin')
+
+#### Fields for model methods and properties.
+
+With `ModelSerializer` you can now specify field names in the `fields` option that refer to model methods or properties. For example, suppose you have the following model:
+
+ class Invitation(models.Model):
+ created = models.DateTimeField()
+ to_email = models.EmailField()
+ message = models.CharField(max_length=1000)
+
+ def expiry_date(self):
+ return self.created + datetime.timedelta(days=30)
+
+You can include `expiry_date` as a field option on a `ModelSerializer` class.
+
+ class InvitationSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = Invitation
+ fields = ('to_email', 'message', 'expiry_date')
+
+These fields will be mapped to `serializers.ReadOnlyField()` instances.
+
+ >>> serializer = InvitationSerializer()
+ >>> print repr(serializer)
+ InvitationSerializer():
+ to_email = EmailField(max_length=75)
+ message = CharField(max_length=1000)
+ expiry_date = ReadOnlyField()
+
+#### The `ListSerializer` class.
+
+The `ListSerializer` class has now been added, and allows you to create base serializer classes for only accepting multiple inputs.
+
+ 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.
+
+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.
+
+REST framework now includes a simple `BaseSerializer` class that can be used to easily support alternative serialization and deserialization styles.
+
+This class implements the same basic API as the `Serializer` class:
+
+* `.data` - Returns the outgoing primitive representation.
+* `.is_valid()` - Deserializes and validates incoming data.
+* `.validated_data` - Returns the validated incoming data.
+* `.errors` - Returns an errors during validation.
+* `.save()` - Persists the validated data into an object instance.
+
+There are four methods that can be overridden, depending on what functionality you want the serializer class to support:
+
+* `.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.
+
+##### Read-only `BaseSerializer` classes.
+
+To implement a read-only serializer using the `BaseSerializer` class, we just need to override the `.to_representation()` method. Let's take a look at an example using a simple Django model:
+
+ class HighScore(models.Model):
+ created = models.DateTimeField(auto_now_add=True)
+ player_name = models.CharField(max_length=10)
+ score = models.IntegerField()
+
+It's simple to create a read-only serializer for converting `HighScore` instances into primitive data types.
+
+ class HighScoreSerializer(serializers.BaseSerializer):
+ def to_representation(self, obj):
+ return {
+ 'score': obj.score,
+ 'player_name': obj.player_name
+ }
+
+We can now use this class to serialize single `HighScore` instances:
+
+ @api_view(['GET'])
+ def high_score(request, pk):
+ instance = HighScore.objects.get(pk=pk)
+ serializer = HighScoreSerializer(instance)
+ return Response(serializer.data)
+
+Or use it to serialize multiple instances:
+
+ @api_view(['GET'])
+ def all_high_scores(request):
+ queryset = HighScore.objects.order_by('-score')
+ serializer = HighScoreSerializer(queryset, many=True)
+ return Response(serializer.data)
+
+##### Read-write `BaseSerializer` classes.
+
+To create a read-write serializer we first need to implement a `.to_internal_value()` method. This method returns the validated values that will be used to construct the object instance, and may raise a `ValidationError` if the supplied data is in an incorrect format.
+
+Once you've implemented `.to_internal_value()`, the basic validation API will be available on the serializer, and you will be able to use `.is_valid()`, `.validated_data` and `.errors`.
+
+If you want to also support `.save()` you'll need to also implement either or both of the `.create()` and `.update()` methods.
+
+Here's a complete example of our previous `HighScoreSerializer`, that's been updated to support both read and write operations.
+
+ class HighScoreSerializer(serializers.BaseSerializer):
+ 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({
+ 'score': 'This field is required.'
+ })
+ if not player_name:
+ raise ValidationError({
+ 'player_name': 'This field is required.'
+ })
+ if len(player_name) > 10:
+ 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 {
+ 'score': int(score),
+ 'player_name': player_name
+ }
+
+ def to_representation(self, obj):
+ return {
+ 'score': obj.score,
+ 'player_name': obj.player_name
+ }
+
+ def create(self, validated_data):
+ return HighScore.objects.create(**validated_data)
+
+#### Creating new generic serializers with `BaseSerializer`.
+
+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.
+
+ class ObjectSerializer(serializers.BaseSerializer):
+ """
+ A read-only serializer that coerces arbitrary complex objects
+ into primitive representations.
+ """
+ def to_representation(self, obj):
+ for attribute_name in dir(obj):
+ attribute = getattr(obj, attribute_name)
+ if attribute_name('_'):
+ # Ignore private attributes.
+ pass
+ elif hasattr(attribute, '__call__'):
+ # Ignore methods and other callables.
+ pass
+ elif isinstance(attribute, (str, int, bool, float, type(None))):
+ # Primitive types can be passed through unmodified.
+ output[attribute_name] = attribute
+ elif isinstance(attribute, list):
+ # Recursivly 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.
+ output[attribute_name] = {
+ str(key): self.to_representation(value)
+ for key, value in attribute.items()
+ }
+ else:
+ # Force anything else to its string representation.
+ output[attribute_name] = str(attribute)
+
+## Serializer fields
+
+#### The `Field` and `ReadOnly` field classes.
+
+There are some minor tweaks to the field base classes.
+
+Previously we had these two base classes:
+
+* `Field` as the base class for read-only fields. A default implementation was included for serializing data.
+* `WritableField` as the base class for read-write fields.
+
+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.
+
+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.
+
+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_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.
+
+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 `ListField` class.
+
+The `ListField` class has now been added. This field validates list input. It takes a `child` keyword argument which is used to specify the field used to validate each item in the list. For example:
+
+ scores = ListField(child=IntegerField(min_value=0, max_value=100))
+
+You can also use a declarative style to create new subclasses of `ListField`, like this:
+
+ class ScoresField(ListField):
+ child = IntegerField(min_value=0, max_value=100)
+
+We can now use the `ScoresField` class inside another serializer:
+
+ scores = ScoresField()
+
+See also the new `ListSerializer` class, which validates input in the same way, but also includes the serializer interfaces of `.is_valid()`, `.data`, `.save()` and so on.
+
+#### The `ChoiceField` class may now accept a flat list.
+
+The `ChoiceField` class may now accept a list of choices in addition to the existing style of using a list of pairs of `(name, display_value)`. The following is now valid:
+
+ color = ChoiceField(choices=['red', 'green', 'blue'])
+
+#### The `MultipleChoiceField` class.
+
+The `MultipleChoiceField` class has been added. This field acts like `ChoiceField`, but returns a set, which may include none, one or many of the valid choices.
+
+#### Changes to the custom field API.
+
+The `from_native(self, value)` and `to_native(self, data)` method names have been replaced with the more obviously named `to_internal_value(self, data)` and `to_representation(self, value)`.
+
+The `field_from_native()` and `field_to_native()` methods are removed. Previously you could use these methods if you wanted to customise the behaviour in a way that did not simply lookup the field value from the object. For example...
+
+ def field_to_native(self, obj, field_name):
+ """A custom read-only field that returns the class name."""
+ return obj.__class__.__name__
+
+Now if you need to access the entire object you'll instead need to override one or both of the following:
+
+* Use `get_attribute` to modify the attribute value passed to `to_representation()`.
+* Use `get_value` to modify the data value passed `to_internal_value()`.
+
+For example:
+
+ def get_attribute(self, obj):
+ # Pass the entire object through to `to_representation()`,
+ # instead of the standard attribute lookup.
+ return obj
+
+ def to_representation(self, value):
+ return value.__class__.__name__
+
+#### Explicit `queryset` required on relational fields.
+
+Previously relational fields that were explicitly declared on a serializer class could omit the queryset argument if (and only if) they were declared on a `ModelSerializer`.
+
+This code *would be valid* in `2.4.3`:
+
+ class AccountSerializer(serializers.ModelSerializer):
+ organizations = serializers.SlugRelatedField(slug_field='name')
+
+ class Meta:
+ model = Account
+
+However this code *would not be valid* in `2.4.3`:
+
+ # Missing `queryset`
+ class AccountSerializer(serializers.Serializer):
+ organizations = serializers.SlugRelatedField(slug_field='name')
+
+ def restore_object(self, attrs, instance=None):
+ # ...
+
+The queryset argument is now always required for writable relational fields.
+This removes some magic and makes it easier and more obvious to move between implicit `ModelSerializer` classes and explicit `Serializer` classes.
+
+ class AccountSerializer(serializers.ModelSerializer):
+ organizations = serializers.SlugRelatedField(
+ slug_field='name',
+ queryset=Organization.objects.all()
+ )
+
+ class Meta:
+ model = Account
+
+The `queryset` argument is only ever required for writable fields, and is not required or valid for fields with `read_only=True`.
+
+#### Optional argument to `SerializerMethodField`.
+
+The argument to `SerializerMethodField` is now optional, and defaults to `get_<field_name>`. For example the following is valid:
+
+ class AccountSerializer(serializers.Serializer):
+ # `method_name='get_billing_details'` by default.
+ billing_details = serializers.SerializerMethodField()
+
+ def get_billing_details(self, account):
+ return calculate_billing(account)
+
+In order to ensure a consistent code style an assertion error will be raised if you include a redundant method name argument that matches the default method name. For example, the following code *will raise an error*:
+
+ billing_details = serializers.SerializerMethodField('get_billing_details')
+
+#### Enforcing consistent `source` usage.
+
+I've see several codebases that unnecessarily include the `source` argument, setting it to the same value as the field name. This usage is redundant and confusing, making it less obvious that `source` is usually not required.
+
+The following usage will *now raise an error*:
+
+ email = serializers.EmailField(source='email')
+
+#### The `UniqueValidator` and `UniqueTogetherValidator` classes.
+
+REST framework now provides new validators that allow you to ensure field uniqueness, while still using a completely explicit `Serializer` class instead of using `ModelSerializer`.
+
+The `UniqueValidator` should be applied to a serializer field, and takes a single `queryset` argument.
+
+ from rest_framework import serializers
+ from rest_framework.validators import UniqueValidator
+
+ class OrganizationSerializer(serializers.Serializer):
+ url = serializers.HyperlinkedIdentityField(view_name='organization_detail')
+ created = serializers.DateTimeField(read_only=True)
+ name = serializers.CharField(
+ max_length=100,
+ validators=UniqueValidator(queryset=Organization.objects.all())
+ )
+
+The `UniqueTogetherValidator` should be applied to a serializer, and takes a `queryset` argument and a `fields` argument which should be a list or tuple of field names.
+
+ class RaceResultSerializer(serializers.Serializer):
+ category = serializers.ChoiceField(['5k', '10k'])
+ position = serializers.IntegerField()
+ name = serializers.CharField(max_length=100)
+
+ default_validators = [UniqueTogetherValidator(
+ queryset=RaceResult.objects.all(),
+ fields=('category', 'position')
+ )]
+
+#### The `UniqueForDateValidator` classes.
+
+**TODO: Needs documenting.**
+
+## Generic views
+
+#### Simplification of view logic.
+
+The view logic for the default method handlers has been significantly simplified, due to the new serializers API.
+
+#### 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)`.
+
+These methods should save the object instance by calling `serializer.save()`, adding in any additional arguments as required. They may also perform any custom pre-save or post-save behavior.
+
+For example:
+
+ def perform_create(self, serializer):
+ # Include the owner attribute directly, rather than from request data.
+ instance = serializer.save(owner=self.request.user)
+ # Perform a custom post-save action.
+ send_email(instance.to_email, instance.message)
+
+The `pre_delete` and `post_delete` hooks no longer exist, and are replaced with `.perform_destroy(self, instance)`, which should delete the instance and perform any custom actions.
+
+ def perform_destroy(self, instance):
+ # Perform a custom pre-delete action.
+ send_deletion_alert(user=instance.created_by, deleted=instance)
+ # Delete the object instance.
+ instance.delete()
+
+#### Removal of view attributes.
+
+The `.object` and `.object_list` attributes are no longer set on the view instance. Treating views as mutable object instances that store state during the processing of the view tends to be poor design, and can lead to obscure flow logic.
+
+I would personally recommend that developers treat view instances as immutable objects in their application code.
+
+#### PUT as create.
+
+Allowing `PUT` as create operations is problematic, as it necessarily exposes information about the existence or non-existence of objects. It's also not obvious that transparently allowing re-creating of previously deleted instances is necessarily a better default behavior than simply returning `404` responses.
+
+Both styles "`PUT` as 404" and "`PUT` as create" can be valid in different circumstances, but we've now opted for the 404 behavior as the default, due to it being simpler and more obvious.
+
+If you need to restore the previous behavior you may want to include [this `AllowPUTAsCreateMixin` class](https://gist.github.com/tomchristie/a2ace4577eff2c603b1b) as a mixin to your views.
+
+#### Customizing error responses.
+
+The generic views now raise `ValidationFailed` exception for invalid data. This exception is then dealt with by the exception handler, rather than the view returning a `400 Bad Request` response directly.
+
+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.**
+
+## API style
+
+There are some improvements in the default style we use in our API responses.
+
+#### Unicode JSON by default.
+
+Unicode JSON is now the default. The `UnicodeJSONRenderer` class no longer exists, and the `UNICODE_JSON` setting has been added. To revert this behavior use the new setting:
+
+ REST_FRAMEWORK = {
+ 'UNICODE_JSON': False
+ }
+
+#### Compact JSON by default.
+
+We now output compact JSON in responses by default. For example, we return:
+
+ {"email":"amy@example.com","is_admin":true}
+
+Instead of the following:
+
+ {"email": "amy@example.com", "is_admin": true}
+
+The `COMPACT_JSON` setting has been added, and can be used to revert this behavior if needed:
+
+ REST_FRAMEWORK = {
+ 'COMPACT_JSON': False
+ }
+
+#### File fields as URLs
+
+The `FileField` and `ImageField` classes are now represented as URLs by default. You should ensure you set Django's [standard `MEDIA_URL` setting](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-MEDIA_URL) appropriately, and ensure your application [serves the uploaded files](https://docs.djangoproject.com/en/dev/howto/static-files/#serving-uploaded-files-in-development).
+
+You can revert this behavior, and display filenames in the representation by using the `UPLOADED_FILES_USE_URL` settings key:
+
+ REST_FRAMEWORK = {
+ 'UPLOADED_FILES_USE_URL': False
+ }
+
+You can also modify serializer fields individually, using the `use_url` argument:
+
+ uploaded_file = serializers.FileField(use_url=False)
+
+Also note that you should pass the `request` object to the serializer as context when instantiating it, so that a fully qualified URL can be returned. Returned URLs will then be of the form `https://example.com/url_path/filename.txt`. For example:
+
+ context = {'request': request}
+ serializer = ExampleSerializer(instance, context=context)
+ return Response(serializer.data)
+
+If the request is omitted from the context, the returned URLs will be of the form `/url_path/filename.txt`.
+
+#### Throttle headers using `Retry-After`.
+
+The custom `X-Throttle-Wait-Second` header has now been dropped in favor of the standard `Retry-After` header. You can revert this behavior if needed by writing a custom exception handler for your application.
+
+#### Date and time objects as ISO-8859-1 strings in serializer data.
+
+Date and Time objects are now coerced to strings by default in the serializer output. Previously they were returned as `Date`, `Time` and `DateTime` objects, and later coerced to strings by the renderer.
+
+You can modify this behavior globally by settings the existing `DATE_FORMAT`, `DATETIME_FORMAT` and `TIME_FORMAT` settings keys. Setting these values to `None` instead of their default value of `'iso-8859-1'` will result in native objects being returned in serializer data.
+
+ REST_FRAMEWORK = {
+ # Return native `Date` and `Time` objects in `serializer.data`
+ 'DATETIME_FORMAT': None
+ 'DATE_FORMAT': None
+ 'TIME_FORMAT': None
+ }
+
+You can also modify serializer fields individually, using the `date_format`, `time_format` and `datetime_format` arguments:
+
+ # Return `DateTime` instances in `serializer.data`, not strings.
+ created = serializers.DateTimeField(format=None)
+
+#### Decimals as strings in serializer data.
+
+Decimals are now coerced to strings by default in the serializer output. Previously they were returned as `Decimal` objects, and later coerced to strings by the renderer.
+
+You can modify this behavior globally by using the `COERCE_DECIMAL_TO_STRING` settings key.
+
+ REST_FRAMEWORK = {
+ 'COERCE_DECIMAL_TO_STRING': False
+ }
+
+Or modify it on an individual serializer field, using the `coerce_to_string` keyword argument.
+
+ # Return `Decimal` instances in `serializer.data`, not strings.
+ amount = serializers.DecimalField(
+ max_digits=10,
+ decimal_places=2,
+ 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.
+
+## 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.
+
+The 3.1 release is planned to address improvements in the following components:
+
+* Public API for using serializers as HTML forms.
+* Request parsing, mediatypes & the implementation of the browsable API.
+* Introduction of a new pagination API.
+* Better support for API versioning.
+
+The 3.2 release is planned to introduce an alternative admin-style interface to the browsable API.
+
+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
diff --git a/docs/topics/ajax-csrf-cors.md b/docs/topics/ajax-csrf-cors.md
index 97dd4710..ad88810d 100644
--- a/docs/topics/ajax-csrf-cors.md
+++ b/docs/topics/ajax-csrf-cors.md
@@ -10,7 +10,7 @@ If you’re building a JavaScript client to interface with your Web API, you'll
AJAX requests that are made within the same context as the API they are interacting with will typically use `SessionAuthentication`. This ensures that once a user has logged in, any AJAX requests made can be authenticated using the same session-based authentication that is used for the rest of the website.
-AJAX requests that are made on a different site from the API they are communicating with will typically need to use a non-session-based authentication scheme, such as `TokenAuthentication`.
+AJAX requests that are made on a different site from the API they are communicating with will typically need to use a non-session-based authentication scheme, such as `TokenAuthentication`.
## CSRF protection
@@ -19,7 +19,7 @@ AJAX requests that are made on a different site from the API they are communicat
To guard against these type of attacks, you need to do two things:
1. Ensure that the 'safe' HTTP operations, such as `GET`, `HEAD` and `OPTIONS` cannot be used to alter any server-side state.
-2. Ensure that any 'unsafe' HTTP operations, such as `POST`, `PUT`, `PATCH` and `DELETE`, always require a valid CSRF token.
+2. Ensure that any 'unsafe' HTTP operations, such as `POST`, `PUT`, `PATCH` and `DELETE`, always require a valid CSRF token.
If you're using `SessionAuthentication` you'll need to include valid CSRF tokens for any `POST`, `PUT`, `PATCH` or `DELETE` operations.
@@ -35,7 +35,7 @@ The best way to deal with CORS in REST framework is to add the required response
[cite]: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html
[csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
-[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#ajax
+[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
[cors]: http://www.w3.org/TR/cors/
[ottoyiu]: https://github.com/ottoyiu/
[django-cors-headers]: https://github.com/ottoyiu/django-cors-headers/
diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md
index 4fafb1b1..52f6e287 100644
--- a/docs/topics/contributing.md
+++ b/docs/topics/contributing.md
@@ -10,9 +10,9 @@ There are many ways you can contribute to Django REST framework. We'd like it t
The most important thing you can do to help push the REST framework project forward is to be actively involved wherever possible. Code contributions are often overvalued as being the primary way to get involved in a project, we don't believe that needs to be the case.
-If you use REST framework, we'd love you to be vocal about your experiences with it - you might consider writing a blog post about using REST framework, or publishing a tutorial about building a project with a particular Javascript framework. Experiences from beginners can be particularly helpful because you'll be in the best position to assess which bits of REST framework are more difficult to understand and work with.
+If you use REST framework, we'd love you to be vocal about your experiences with it - you might consider writing a blog post about using REST framework, or publishing a tutorial about building a project with a particular JavaScript framework. Experiences from beginners can be particularly helpful because you'll be in the best position to assess which bits of REST framework are more difficult to understand and work with.
-Other really great ways you can help move the community forward include helping answer questions on the [discussion group][google-group], or setting up an [email alert on StackOverflow][so-filter] so that you get notified of any new questions with the `django-rest-framework` tag.
+Other really great ways you can help move the community forward include helping to answer questions on the [discussion group][google-group], or setting up an [email alert on StackOverflow][so-filter] so that you get notified of any new questions with the `django-rest-framework` tag.
When answering questions make sure to help future contributors find their way around by hyperlinking wherever possible to related threads and tickets, and include backlinks from those items if relevant.
@@ -33,7 +33,7 @@ Some tips on good issue reporting:
* When describing issues try to phrase your ticket in terms of the *behavior* you think needs changing rather than the *code* you think need changing.
* Search the issue list first for related items, and make sure you're running the latest version of REST framework before reporting an issue.
* If reporting a bug, then try to include a pull request with a failing test case. This will help us quickly identify if there is a valid issue, and make sure that it gets fixed more quickly if there is one.
-* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintainence overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation.
+* Feature requests will often be closed with a recommendation that they be implemented outside of the core REST framework library. Keeping new feature requests implemented as third party libraries allows us to keep down the maintenance overhead of REST framework, so that the focus can be on continued stability, bugfixes, and great documentation.
* Closing an issue doesn't necessarily mean the end of a discussion. If you believe your issue has been closed incorrectly, explain why and we'll consider if it needs to be reopened.
## Triaging issues
@@ -52,7 +52,7 @@ To start developing on Django REST framework, clone the repo:
git clone git@github.com:tomchristie/django-rest-framework.git
-Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you setup your editor to automatically indicated non-conforming styles.
+Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you set up your editor to automatically indicate non-conforming styles.
## Testing
@@ -71,31 +71,31 @@ To run the tests, clone the repository, and then:
Run using a more concise output style.
- ./runtests -q
+ ./runtests.py -q
Run the tests using a more concise output style, no coverage, no flake8.
- ./runtests --fast
+ ./runtests.py --fast
Don't run the flake8 code linting.
- ./runtests --nolint
+ ./runtests.py --nolint
Only run the flake8 code linting, don't run the tests.
- ./runtests --lintonly
+ ./runtests.py --lintonly
Run the tests for a given test case.
- ./runtests MyTestCase
+ ./runtests.py MyTestCase
Run the tests for a given test method.
- ./runtests MyTestCase.test_this_method
+ ./runtests.py MyTestCase.test_this_method
Shorter form to run the tests for a given test method.
- ./runtests test_this_method
+ ./runtests.py test_this_method
Note: The test case and test method matching is fuzzy and will sometimes run other tests that contain a partial string match to the given command line input.
@@ -109,7 +109,7 @@ You can also use the excellent [tox][tox] testing tool to run the tests against
It's a good idea to make pull requests early on. A pull request represents the start of a discussion, and doesn't necessarily need to be the final, finished submission.
-It's also always best to make a new branch before starting work on a pull request. This means that you'll be able to later switch back to working on another seperate issue without interfering with an ongoing pull requests.
+It's also always best to make a new branch before starting work on a pull request. This means that you'll be able to later switch back to working on another separate issue without interfering with an ongoing pull requests.
It's also useful to remember that if you have an outstanding pull request then pushing new commits to your GitHub repo will also automatically update the pull requests.
@@ -117,7 +117,7 @@ GitHub's documentation for working on pull requests is [available here][pull-req
Always run the tests before submitting pull requests, and ideally run `tox` in order to check that your modifications are compatible with both Python 2 and Python 3, and that they run properly on all supported versions of Django.
-Once you've made a pull request take a look at the travis build status in the GitHub interface and make sure the tests are runnning as you'd expect.
+Once you've made a pull request take a look at the Travis build status in the GitHub interface and make sure the tests are running as you'd expect.
![Travis status][travis-status]
@@ -131,7 +131,7 @@ Sometimes, in order to ensure your code works on various different versions of D
The documentation for REST framework is built from the [Markdown][markdown] source files in [the docs directory][docs].
-There are many great markdown editors that make working with the documentation really easy. The [Mou editor for Mac][mou] is one such editor that comes highly recommended.
+There are many great Markdown editors that make working with the documentation really easy. The [Mou editor for Mac][mou] is one such editor that comes highly recommended.
## Building the documentation
@@ -153,7 +153,7 @@ Some other tips:
* Keep paragraphs reasonably short.
* Use double spacing after the end of sentences.
-* Don't use the abbreviations such as 'e.g.' but instead use long form, such as 'For example'.
+* Don't use abbreviations such as 'e.g.' but instead use the long form, such as 'For example'.
## Markdown style
@@ -186,7 +186,7 @@ If you are hyperlinking to another REST framework document, you should use a rel
[authentication]: ../api-guide/authentication.md
-Linking in this style means you'll be able to click the hyperlink in your markdown editor to open the referenced document. When the documentation is built, these links will be converted into regular links to HTML pages.
+Linking in this style means you'll be able to click the hyperlink in your Markdown editor to open the referenced document. When the documentation is built, these links will be converted into regular links to HTML pages.
##### 3. Notes
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 16589f3b..88780c3f 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -40,6 +40,16 @@ You can determine your currently installed version using `pip freeze`:
## 2.4.x series
+### 2.4.4
+
+**Date**: [3rd November 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.4+Release%22+).
+
+* **Security fix**: Escape URLs when replacing `format=` query parameter, as used in dropdown on `GET` button in browsable API to allow explicit selection of JSON vs HTML output.
+* Maintain ordering of URLs in API root view for `DefaultRouter`.
+* Fix `follow=True` in `APIRequestFactory`
+* Resolve issue with invalid `read_only=True`, `required=True` fields being automatically generated by `ModelSerializer` in some cases.
+* Resolve issue with `OPTIONS` requests returning incorrect information for views using `get_serializer_class` to dynamically determine serializer based on request method.
+
### 2.4.3
**Date**: [19th September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.3+Release%22+).
@@ -149,7 +159,7 @@ You can determine your currently installed version using `pip freeze`:
* Added `write_only_fields` option to `ModelSerializer` classes.
* JSON renderer now deals with objects that implement a dict-like interface.
* Fix compatiblity with newer versions of `django-oauth-plus`.
-* Bugfix: Refine behavior that calls model manager `all()` across nested serializer relationships, preventing erronous behavior with some non-ORM objects, and preventing unneccessary queryset re-evaluations.
+* Bugfix: Refine behavior that calls model manager `all()` across nested serializer relationships, preventing erronous behavior with some non-ORM objects, and preventing unnecessary queryset re-evaluations.
* Bugfix: Allow defaults on BooleanFields to be properly honored when values are not supplied.
* Bugfix: Prevent double-escaping of non-latin1 URL query params when appending `format=json` params.
diff --git a/docs/topics/rest-hypermedia-hateoas.md b/docs/topics/rest-hypermedia-hateoas.md
index 3d700488..4e3ccf92 100644
--- a/docs/topics/rest-hypermedia-hateoas.md
+++ b/docs/topics/rest-hypermedia-hateoas.md
@@ -13,7 +13,7 @@ The following fall into the "required reading" category.
* Roy Fielding's dissertation - [Architectural Styles and
the Design of Network-based Software Architectures][dissertation].
* Roy Fielding's "[REST APIs must be hypertext-driven][hypertext-driven]" blog post.
-* Leonard Richardson & Sam Ruby's [RESTful Web Services][restful-web-services].
+* Leonard Richardson & Mike Amundsen's [RESTful Web APIs][restful-web-apis].
* Mike Amundsen's [Building Hypermedia APIs with HTML5 and Node][building-hypermedia-apis].
* Steve Klabnik's [Designing Hypermedia APIs][designing-hypermedia-apis].
* The [Richardson Maturity Model][maturitymodel].
@@ -37,7 +37,7 @@ What REST framework doesn't do is give you is machine readable hypermedia format
[cite]: http://vimeo.com/channels/restfest/page:2
[dissertation]: http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
[hypertext-driven]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
-[restful-web-services]: http://www.amazon.com/Restful-Web-Services-Leonard-Richardson/dp/0596529260
+[restful-web-apis]: http://restfulwebapis.org/
[building-hypermedia-apis]: http://www.amazon.com/Building-Hypermedia-APIs-HTML5-Node/dp/1449306578
[designing-hypermedia-apis]: http://designinghypermediaapis.com/
[restisover]: http://blog.steveklabnik.com/posts/2012-02-23-rest-is-over
diff --git a/docs/topics/writable-nested-serializers.md b/docs/topics/writable-nested-serializers.md
index 66ea7815..abc6a82f 100644
--- a/docs/topics/writable-nested-serializers.md
+++ b/docs/topics/writable-nested-serializers.md
@@ -6,7 +6,7 @@
Although flat data structures serve to properly delineate between the individual entities in your service, there are cases where it may be more appropriate or convenient to use nested data structures.
-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 dependancies between the various model instances, and the need to save or delete multiple instances in a single action.
+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