From 20424251a3da82681fee04c66b7be0c7d3a40fec Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 19 Sep 2014 14:26:28 +0100 Subject: Version 2.4.3 --- docs/topics/release-notes.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index d758ae6a..2174cdda 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -40,21 +40,37 @@ You can determine your currently installed version using `pip freeze`: ## 2.4.x series +### 2.4.3 + +**Date**: [19th September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.3+Release%22+). + +* Support translatable view docstrings being displayed in the browsable API. +* Support [encoded `filename*`][rfc-6266] in raw file uploads with `FileUploadParser`. +* Allow routers to support viewsets that don't include any list routes or that don't include any detail routes. +* Don't render an empty login control in browsable API if `login` view is not included. +* CSRF exemption performed in `.as_view()` to prevent accidental omission if overriding `.dispatch()`. +* Login on browsable API now displays validation errors. +* 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: `DjangoFilterBackend` no longer quietly changes queryset ordering. + ### 2.4.2 -**Date**: 3rd September 2014 +**Date**: [3rd September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.2+Release%22+). * Bugfix: Fix broken pagination for 2.4.x series. ### 2.4.1 -**Date**: 1st September 2014 +**Date**: [1st September 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.1+Release%22+). * Bugfix: Fix broken login template for browsable API. ### 2.4.0 -**Date**: 29th August 2014 +**Date**: [29th August 2014](https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%222.4.0+Release%22+). **Django version requirements**: The lowest supported version of Django is now 1.4.2. @@ -717,3 +733,4 @@ This change will not affect user code, so long as it's following the recommended [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 +[rfc-6266]: http://tools.ietf.org/html/rfc6266#section-4.3 -- cgit v1.2.3 From 8495cd898a5d34f00858a379b54e39cd19ded215 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 19 Sep 2014 14:31:28 +0100 Subject: Drop 'No major point releases are currently planned.', cos they are. --- docs/topics/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 2174cdda..16589f3b 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -10,7 +10,7 @@ Minor version numbers (0.0.x) are used for changes that are API compatible. You Medium version numbers (0.x.0) may include API changes, in line with the [deprecation policy][deprecation-policy]. You should read the release notes carefully before upgrading between medium point releases. -Major version numbers (x.0.0) are reserved for substantial project milestones. No major point releases are currently planned. +Major version numbers (x.0.0) are reserved for substantial project milestones. ## Deprecation policy -- cgit v1.2.3 From c0150e619ca02a69d87c335a70c47644e9b2e509 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 19 Sep 2014 14:59:59 +0100 Subject: Add BaseSerializer heading --- docs/topics/3.0-announcement.md | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 029d9896..cd883cdd 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -30,6 +30,10 @@ See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-fr **TODO**: Drop`.object`, use `.validated_data` or get the instance with `.save()`. +#### The `BaseSerializer` class. + +**TODO** + #### Always use `fields`, not `exclude`. The `exclude` option is no longer available. You should use the more explicit `fields` option instead. -- cgit v1.2.3 From da385c9c1f9deeeefd705154a6e6612d6d62f41b Mon Sep 17 00:00:00 2001 From: Collin Anderson Date: Tue, 23 Sep 2014 17:08:38 -0400 Subject: remove patterns and strings from urls #1898 --- docs/api-guide/authentication.md | 7 ++++--- docs/api-guide/format-suffixes.md | 13 +++++++------ docs/index.md | 4 ++-- docs/topics/2.3-announcement.md | 6 +++--- docs/tutorial/1-serialization.md | 13 +++++++------ docs/tutorial/2-requests-and-responses.md | 9 +++++---- docs/tutorial/3-class-based-views.md | 4 ++-- docs/tutorial/4-authentication-and-permissions.md | 4 ++-- docs/tutorial/5-relationships-and-hyperlinked-apis.md | 10 +++++----- docs/tutorial/6-viewsets-and-routers.md | 12 ++++++------ 10 files changed, 43 insertions(+), 39 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 343466ee..0ec5bad1 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -190,9 +190,10 @@ If you've already created some users, you can generate tokens for all existing u When using `TokenAuthentication`, you may want to provide a mechanism for clients to obtain a token given the username and password. REST framework provides a built-in view to provide this behavior. To use it, add the `obtain_auth_token` view to your URLconf: - urlpatterns += patterns('', - url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token') - ) + from rest_framework.authtoken import views + urlpatterns += [ + url(r'^api-token-auth/', views.obtain_auth_token) + ] Note that the URL part of the pattern can be whatever you want to use. diff --git a/docs/api-guide/format-suffixes.md b/docs/api-guide/format-suffixes.md index 529738e3..76a3367b 100644 --- a/docs/api-guide/format-suffixes.md +++ b/docs/api-guide/format-suffixes.md @@ -26,12 +26,13 @@ Arguments: Example: from rest_framework.urlpatterns import format_suffix_patterns - - urlpatterns = patterns('blog.views', - url(r'^/$', 'api_root'), - url(r'^comments/$', 'comment_list'), - url(r'^comments/(?P[0-9]+)/$', 'comment_detail') - ) + from blog import views + + urlpatterns = [ + url(r'^/$', views.apt_root), + url(r'^comments/$', views.comment_list), + url(r'^comments/(?P[0-9]+)/$', views.comment_detail) + ] urlpatterns = format_suffix_patterns(urlpatterns, allowed=['json', 'html']) diff --git a/docs/index.md b/docs/index.md index 6dcb962f..e4c971f9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -85,10 +85,10 @@ Add `'rest_framework'` to your `INSTALLED_APPS` setting. If you're intending to use the browsable API you'll probably also want to add REST framework's login and logout views. Add the following to your root `urls.py` file. - urlpatterns = patterns('', + urlpatterns = [ ... url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) - ) + ] Note that the URL path can be whatever you want, but you must include `'rest_framework.urls'` with the `'rest_framework'` namespace. diff --git a/docs/topics/2.3-announcement.md b/docs/topics/2.3-announcement.md index ba435145..7c800afa 100644 --- a/docs/topics/2.3-announcement.md +++ b/docs/topics/2.3-announcement.md @@ -15,7 +15,7 @@ As an example of just how simple REST framework APIs can now be, here's an API w """ A REST framework API for viewing and editing users and groups. """ - from django.conf.urls.defaults import url, patterns, include + from django.conf.urls.defaults import url, include from django.contrib.auth.models import User, Group from rest_framework import viewsets, routers @@ -36,10 +36,10 @@ As an example of just how simple REST framework APIs can now be, here's an API w # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browseable API. - urlpatterns = patterns('', + urlpatterns = [ url(r'^', include(router.urls)), url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) - ) + ] The best place to get started with ViewSets and Routers is to take a look at the [newest section in the tutorial][part-6], which demonstrates their usage. diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index 96214f5b..b0565d91 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -64,9 +64,9 @@ We'll also need to add our new `snippets` app and the `rest_framework` app to `I We also need to wire up the root urlconf, in the `tutorial/urls.py` file, to include our snippet app's URLs. - urlpatterns = patterns('', + urlpatterns = [ url(r'^', include('snippets.urls')), - ) + ] Okay, we're ready to roll. @@ -297,11 +297,12 @@ We'll also need a view which corresponds to an individual snippet, and can be us Finally we need to wire these views up. Create the `snippets/urls.py` file: from django.conf.urls import patterns, url + from snippets import views - urlpatterns = patterns('snippets.views', - url(r'^snippets/$', 'snippet_list'), - url(r'^snippets/(?P[0-9]+)/$', 'snippet_detail'), - ) + urlpatterns = [ + url(r'^snippets/$', views.snippet_list), + url(r'^snippets/(?P[0-9]+)/$', views.snippet_detail), + ] It's worth noting that there are a couple of edge cases we're not dealing with properly at the moment. If we send malformed `json`, or if a request is made with a method that the view doesn't handle, then we'll end up with a 500 "server error" response. Still, this'll do for now. diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md index e70bbbfc..136b0135 100644 --- a/docs/tutorial/2-requests-and-responses.md +++ b/docs/tutorial/2-requests-and-responses.md @@ -110,11 +110,12 @@ Now update the `urls.py` file slightly, to append a set of `format_suffix_patter from django.conf.urls import patterns, url from rest_framework.urlpatterns import format_suffix_patterns + from snippets import views - urlpatterns = patterns('snippets.views', - url(r'^snippets/$', 'snippet_list'), - url(r'^snippets/(?P[0-9]+)$', 'snippet_detail'), - ) + urlpatterns = [ + url(r'^snippets/$', views.snippet_list), + url(r'^snippets/(?P[0-9]+)$', views.snippet_detail), + ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md index e04072ca..382f078a 100644 --- a/docs/tutorial/3-class-based-views.md +++ b/docs/tutorial/3-class-based-views.md @@ -68,10 +68,10 @@ We'll also need to refactor our `urls.py` slightly now we're using class based v from rest_framework.urlpatterns import format_suffix_patterns from snippets import views - urlpatterns = patterns('', + urlpatterns = [ url(r'^snippets/$', views.SnippetList.as_view()), url(r'^snippets/(?P[0-9]+)/$', views.SnippetDetail.as_view()), - ) + ] urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 74ad9a55..9120e254 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -137,10 +137,10 @@ Add the following import at the top of the file: And, at the end of the file, add a pattern to include the login and logout views for the browsable API. - urlpatterns += patterns('', + urlpatterns += [ url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), - ) + ] The `r'^api-auth/'` part of pattern can actually be whatever URL you want to use. The only restriction is that the included urls must use the `'rest_framework'` namespace. diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index 9c61fe3d..36473ce9 100644 --- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md +++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md @@ -108,8 +108,8 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p After adding all those names into our URLconf, our final `snippets/urls.py` file should look something like this: # API endpoints - urlpatterns = format_suffix_patterns(patterns('snippets.views', - url(r'^$', 'api_root'), + urlpatterns = format_suffix_patterns([ + url(r'^$', views.api_root), url(r'^snippets/$', views.SnippetList.as_view(), name='snippet-list'), @@ -125,13 +125,13 @@ After adding all those names into our URLconf, our final `snippets/urls.py` file url(r'^users/(?P[0-9]+)/$', views.UserDetail.as_view(), name='user-detail') - )) + ]) # Login and logout views for the browsable API - urlpatterns += patterns('', + urlpatterns += [ url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), - ) + ] ## Adding pagination diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index b2019520..cf37a260 100644 --- a/docs/tutorial/6-viewsets-and-routers.md +++ b/docs/tutorial/6-viewsets-and-routers.md @@ -87,14 +87,14 @@ Notice how we're creating multiple views from each `ViewSet` class, by binding t Now that we've bound our resources into concrete views, we can register the views with the URL conf as usual. - urlpatterns = format_suffix_patterns(patterns('snippets.views', - url(r'^$', 'api_root'), + urlpatterns = format_suffix_patterns([ + url(r'^$', api_root), url(r'^snippets/$', snippet_list, name='snippet-list'), url(r'^snippets/(?P[0-9]+)/$', snippet_detail, name='snippet-detail'), url(r'^snippets/(?P[0-9]+)/highlight/$', snippet_highlight, name='snippet-highlight'), url(r'^users/$', user_list, name='user-list'), url(r'^users/(?P[0-9]+)/$', user_detail, name='user-detail') - )) + ]) ## Using Routers @@ -102,7 +102,7 @@ Because we're using `ViewSet` classes rather than `View` classes, we actually do Here's our re-wired `urls.py` file. - from django.conf.urls import patterns, url, include + from django.conf.urls import url, include from snippets import views from rest_framework.routers import DefaultRouter @@ -113,10 +113,10 @@ Here's our re-wired `urls.py` file. # The API URLs are now determined automatically by the router. # Additionally, we include the login URLs for the browseable API. - urlpatterns = patterns('', + urlpatterns = [ url(r'^', include(router.urls)), url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) - ) + ] Registering the viewsets with the router is similar to providing a urlpattern. We include two arguments - the URL prefix for the views, and the viewset itself. -- cgit v1.2.3 From 2859eaf524bca23f27e666d24a0b63ba61698a76 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 10:46:52 +0100 Subject: request.data attribute --- docs/topics/3.0-announcement.md | 344 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 323 insertions(+), 21 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index cd883cdd..1795611c 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -4,36 +4,65 @@ See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-fr # REST framework 3.0 -**Note incremental nature, discuss upgrading.** +**TODO**: Note incremental nature, discuss upgrading, motivation, features. -## Motivation - -**TODO** +* Serializer reprs. +* Non-magical model serializers. +* Base serializer class. +* Clean logic in views, serializers, fields. --- ## Request objects -#### The `request.data` property. +#### The `.data` and `.query_params` properties. -**TODO** +The usage of `request.DATA` and `request.FILES` is now discouraged in favor of a single `request.data` attribute that contains *all* the parsed data. -#### The parser API. +Having seperate 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. -**TODO** +You may now pass all the request data to a serializer class in a single argument: + + ExampleSerializer(data=request.data) + +Instead of passing the files argument seperately: + + # Don't do this... + ExampleSerializer(data=request.DATA, files=request.FILES) + + +The usage of `request.QUERY_PARAMS` is now discouraged in favor of the lowercased `request.query_params`. ## Serializers #### Single-step object creation. +#### The `.create()` and `.update()` methods. + **TODO**: Drop `.restore_object()`, use `.create()` and `.update()` which should save the instance. -**TODO**: Drop`.object`, use `.validated_data` or get the instance with `.save()`. +#### Use `.validated_data` instead of `.object`. -#### The `BaseSerializer` class. +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. -**TODO** +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 using the `extras` keyword argument. + +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) + extras = {'user': request.user} # Include the user when saving. + serializer.save(extras=extras) + #### Always use `fields`, not `exclude`. The `exclude` option is no longer available. You should use the more explicit `fields` option instead. @@ -111,42 +140,287 @@ These fields will be mapped to `serializers.ReadOnlyField()` instances. 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 seperated 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 primative 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 mathods that can be overriding, 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 serializers. + +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 primative 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 serializers. + +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 base classes 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 different storage backends. + +The following class is an example of a generic serializer that can handle coercing aribitrary objects into primative representations. + + class ObjectSerializer(serializers.BaseSerializer): + """ + A read-only serializer that coerces arbitrary complex objects + into primative 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))): + # Primative 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. -**TODO** +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. +* `WriteableField` 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 explict and clear control over validating empty values for fields. + +Previously the meaning of the `required=False` keyword argument was underspecified. In practice it's use meant that a field could either be not included in the input, or it could be included, but be `None`. + +We now have a better seperation, with seperate `required` and `allow_none` 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=`: 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 there if you need it, but you'll more typically want defaults to be set on model fields, rather than serializer fields. #### Coercing output types. -**TODO** +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 `ListSerializer` class. +#### The `ListField` class. -**TODO** +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. -**TODO** +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. -**TODO** `to_representation`, `to_internal_value`. +The `from_native(self, value)` and `to_native(self, data)` method names have been replaced with the more obviously named `to_representation(self, value)` and `to_internal_value(self, data)`. -#### Explicit `querysets` required on relational fields. +The `field_from_native()` and `field_to_native()` methods are removed. -**TODO** +#### 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): + organisations = 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): + organisations = 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 implict `ModelSerializer` classes and explicit `Serializer` classes. + + class AccountSerializer(serializers.ModelSerializer): + organisations = serializers.SlugRelatedField( + slug_field='name', + queryset=Organisation.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`. -**TODO** +The argument to `SerializerMethodField` is now optional, and defaults to `get_`. 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 unneccessarily 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') ## Generic views #### Simplification of view logic. -**TODO** +The view logic for the default method handlers has been significantly simplified, due to the new serializers API. #### Removal of pre/post save hooks. @@ -169,6 +443,20 @@ I would personally recommend that developers treat view instances as immutable o #### PUT as create. +Allowing `PUT` as create operations is problematic, as it neccessarily exposes information about the existence or non-existance of objects. It's also not obvious that transparently allowing re-creating of previously deleted instances is neccessarily 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 can include the `AllowPUTAsCreateMixin` class in your view. This class can be imported from `rest_framework.mixins`. + +#### Customizing error responses. + +The generic views now raise `ValidationError` 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 cusomize the style of error responses across your entire API, without having to modify any of the generic views. + +## The metadata API + **TODO** ## API style @@ -241,3 +529,17 @@ Or modify it on an individual serializer field, using the `corece_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. + +## 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: + +* 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). -- cgit v1.2.3 From 43e80c74b225e17edfe8a90da893823bf50b946f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 11:56:29 +0100 Subject: Release notes --- docs/topics/3.0-announcement.md | 79 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 7 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 1795611c..35d725ff 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -4,12 +4,25 @@ See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-fr # REST framework 3.0 -**TODO**: Note incremental nature, discuss upgrading, motivation, features. +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. -* Serializer reprs. -* Non-magical model serializers. -* Base serializer class. -* Clean logic in views, serializers, fields. +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 plus new `ListField` and `MultipleChoiceField` classes. +* Super simple default implementations 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. + +Below is an in-depth guide to the API changes and migration notes for 3.0. --- @@ -37,9 +50,59 @@ The usage of `request.QUERY_PARAMS` is now discouraged in favor of the lowercase #### 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 save. This type of data needed to be hidden in some undocumentated 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, eg `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. -**TODO**: Drop `.restore_object()`, use `.create()` and `.update()` which should save the instance. +The `.restore_object()` method is now replaced with two seperate methods, `.create()` and `.update()`. + +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 seperate methods. + + def update(self, instance, validated_attrs) + instance.title = validated_attrs.get('title', instance.title) + instance.code = validated_attrs.get('code', instance.code) + instance.linenos = validated_attrs.get('linenos', instance.linenos) + instance.language = validated_attrs.get('language', instance.language) + instance.style = validated_attrs.get('style', instance.style) + instance.save() + + def create(self, validated_attrs): + return Snippet.objects.create(**validated_attrs) + +Note that the `.create` method should return the newly created object instance. #### Use `.validated_data` instead of `.object`. @@ -457,7 +520,9 @@ This change means that you can now easily cusomize the style of error responses ## The metadata API -**TODO** +Behavior for dealing with `OPTIONS` requests was previously built directly into the class based views. This has now been properly seperated 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. ## API style -- cgit v1.2.3 From e8af73d144d73a55aecde6a1fda8516f15f027c1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 12:17:20 +0100 Subject: Release notes --- docs/topics/3.0-announcement.md | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 35d725ff..144d3550 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -125,14 +125,41 @@ The corresponding code would now look like this: logging.info('Creating ticket "%s"' % name) extras = {'user': request.user} # Include the user when saving. serializer.save(extras=extras) - + +#### Printable serializer reprensentations. + +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 c. + + class LocationRatingSerializer(serializer.ModelSerializer): + class Meta: + model = LocationRating + +We can now inspect its 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()) + #### Always use `fields`, not `exclude`. -The `exclude` option is no longer available. You should use the more explicit `fields` option instead. +The `exclude` option on `ModelSerializer` is no longer available. You should use the more explicit `fields` option instead. #### The `extra_kwargs` option. -The `read_only_fields` and `write_only_fields` options have been removed and replaced with a more generic `extra_kwargs`. +The `read_only_fields` and `write_only_fields` options on `ModelSerializer` have been removed and replaced with a more generic `extra_kwargs`. class MySerializer(serializer.ModelSerializer): class Meta: @@ -177,7 +204,7 @@ Alternatively, specify the field explicitly on the serializer class: #### Fields for model methods and properties. -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: +With `ModelSerilizer` 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() -- cgit v1.2.3 From 90311357add780433c79e97346ed85f1f4224877 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 12:18:27 +0100 Subject: Release notes --- docs/topics/3.0-announcement.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 144d3550..daacbba0 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -137,13 +137,13 @@ For instance, given the following example model: rating = models.IntegerField() created_by = models.ForeignKey(User) -Let's create a simple `ModelSerializer` class c. +Let's create a simple `ModelSerializer` class corresponding to the `LocationRating` model. class LocationRatingSerializer(serializer.ModelSerializer): class Meta: model = LocationRating -We can now inspect its representation in the Django shell, using `python manage.py shell`... +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 -- cgit v1.2.3 From fde934d33c8692bab5e0e7b6009d358101a25dd7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 12:21:05 +0100 Subject: Release notes --- docs/topics/3.0-announcement.md | 1 + 1 file changed, 1 insertion(+) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index daacbba0..a4e4db14 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -21,6 +21,7 @@ Notable features of this new release include: * Super simple default implementations 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. -- cgit v1.2.3 From 8b8623c5f84d443d26804cac52a793a3037a1dd0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 12:48:20 +0100 Subject: Allow many, partial and context in BaseSerializer --- docs/topics/3.0-announcement.md | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index a4e4db14..faba2d35 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -2,6 +2,13 @@ See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-framework/pull/1800) for more details. +Most notable outstanding issues still to resolved on the `version-3.0` branch. + +* `FileField` and `ImageField` support. +* Forms support for serializers and in the browsable API. +* Enforcing uniqueness on `unique=True` and `unique_together` fields. +* Optimisations for serialializing primary keys. + # 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. -- cgit v1.2.3 From 0eb6a4de8a3293c3b326fadccf7aa0be67c2f5b5 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 13:10:58 +0100 Subject: Release notes --- docs/topics/3.0-announcement.md | 1 + 1 file changed, 1 insertion(+) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index faba2d35..9769e884 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -8,6 +8,7 @@ Most notable outstanding issues still to resolved on the `version-3.0` branch. * Forms support for serializers and in the browsable API. * Enforcing uniqueness on `unique=True` and `unique_together` fields. * Optimisations for serialializing primary keys. +* Refine style of validation errors in some cases, such as validation errors in `ListField`. # REST framework 3.0 -- cgit v1.2.3 From 24f7db2fc156b0af7749a5cc17c1df3f5522bf88 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 13:12:10 +0100 Subject: Release notes --- docs/topics/3.0-announcement.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 9769e884..a48d22ea 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -2,13 +2,15 @@ See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-framework/pull/1800) for more details. -Most notable outstanding issues still to resolved on the `version-3.0` branch. +The most notable outstanding issues still to resolved on the `version-3.0` branch are as follows: * `FileField` and `ImageField` support. * Forms support for serializers and in the browsable API. * Enforcing uniqueness on `unique=True` and `unique_together` fields. * Optimisations for serialializing primary keys. * Refine style of validation errors in some cases, such as validation errors in `ListField`. +* `.validate()` method on fields. +* `.transform_()` method on serializers. # REST framework 3.0 -- cgit v1.2.3 From ee79b453974f3dc8ead83bff86784366d59a4fb1 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 13:19:32 +0100 Subject: Prepend some pre-release notes --- docs/topics/3.0-announcement.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index a48d22ea..21052ca0 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -1,6 +1,10 @@ -**THIS DOCUMENT IS CURRENTLY A WORK IN PROGRESS** +## Pre-release notes: -See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-framework/pull/1800) for more details. +The 3.0 release is now ready for some tentative testing and upgrades for super keen early adopters. You can install the development version directly from GitHub like so: + + pip install https://github.com/tomchristie/django-rest-framework/archive/version-3.0.zip + +See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-framework/pull/1800) for more details on remaining work. The most notable outstanding issues still to resolved on the `version-3.0` branch are as follows: @@ -12,6 +16,12 @@ The most notable outstanding issues still to resolved on the `version-3.0` branc * `.validate()` method on fields. * `.transform_()` method on serializers. +**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. -- cgit v1.2.3 From ce04d59a53df45715c4805831406b2105c9594a8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 26 Sep 2014 17:07:47 +0100 Subject: Update release notes --- docs/topics/3.0-announcement.md | 1 - 1 file changed, 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 21052ca0..24f4ed4c 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -8,7 +8,6 @@ See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-fr The most notable outstanding issues still to resolved on the `version-3.0` branch are as follows: -* `FileField` and `ImageField` support. * Forms support for serializers and in the browsable API. * Enforcing uniqueness on `unique=True` and `unique_together` fields. * Optimisations for serialializing primary keys. -- cgit v1.2.3 From 4798df52df5d59cc570043e3eb7e26f7ce57b54f Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 29 Sep 2014 12:57:05 +0100 Subject: Update release notes --- docs/topics/3.0-announcement.md | 44 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 24f4ed4c..92062552 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -146,7 +146,41 @@ The corresponding code would now look like this: extras = {'user': request.user} # Include the user when saving. serializer.save(extras=extras) -#### Printable serializer reprensentations. +#### Limitations of ModelSerializer validation. + +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 seperation, and ensures that there's no automatic validation behavior on `ModelSerializer` classes that can't also be easily replicated on regular `Serializer` classes. + +This change comes with the following limitations: + +* The model `.clean()` method will not be called as part of serializer validation. Use the serializer `.validate()` method to perform a final validation step on incoming data where required. +* The `.unique_for_date`, `.unique_for_month` and `.unique_for_year` options on model fields are not automatically validated. Again, you'll need to handle these explicitly on the serializer if 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 dependancies 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. 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 = 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. @@ -279,7 +313,7 @@ There are four mathods that can be overriding, depending on what functionality y * `.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 serializers. +##### 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: @@ -313,7 +347,7 @@ Or use it to serialize multiple instances: serializer = HighScoreSerializer(queryset, many=True) return Response(serializer.data) -##### Read-write serializers. +##### 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. @@ -358,9 +392,9 @@ Here's a complete example of our previous `HighScoreSerializer`, that's been upd def create(self, validated_data): return HighScore.objects.create(**validated_data) -#### Creating new base classes with `BaseSerializer`. +#### 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 different storage backends. +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 primative representations. -- cgit v1.2.3 From 657d1de032bfa392609d53751e89366b972cd678 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 29 Sep 2014 14:12:09 +0100 Subject: Latest release notes --- docs/topics/3.0-announcement.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 92062552..584c4979 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -560,6 +560,35 @@ The following usage will *now raise an error*: email = serializers.EmailField(source='email') +#### The `UniqueValidator` and `UniqueTogetherValidator` classes. + +REST framework now provides two 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='organisation_detail') + created = serializers.DateTimeField(read_only=True) + name = serializers.CharField( + max_length=100, + validators=UniqueValidator(queryset=Organisation.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') + )] + ## Generic views #### Simplification of view logic. @@ -633,6 +662,16 @@ The `COMPACT_JSON` setting has been added, and can be used to revert this behavi '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 appropriately. + +You can revert this behavior, and display filenames as the representation, using the `UPLOADED_FILES_USE_URL` settings key: + + REST_FRAMEWORK = { + 'UPLOADED_FILES_USE_URL': False + } + #### 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. -- cgit v1.2.3 From a8622adcd9f940131b63e91d53d2c49fcb89ee6a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 29 Sep 2014 16:57:40 +0100 Subject: Update index.md --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/index.md b/docs/index.md index e4c971f9..b18b71d2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -49,7 +49,7 @@ Some reasons you might want to use REST framework: REST framework requires the following: -* Python (2.6.5+, 2.7, 3.2, 3.3) +* Python (2.6.5+, 2.7, 3.2, 3.3, 3.4) * Django (1.4.2+, 1.5, 1.6, 1.7) The following packages are optional: -- cgit v1.2.3 From 83a5ea8db27b9452a5539f1e0574f493392a91ad Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 29 Sep 2014 21:17:13 +0100 Subject: Update release notes --- docs/topics/3.0-announcement.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 584c4979..12ab5a0d 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -664,14 +664,26 @@ The `COMPACT_JSON` setting has been added, and can be used to revert this behavi #### 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 appropriately. +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 as the representation, using the `UPLOADED_FILES_USE_URL` settings key: +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(user_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. -- cgit v1.2.3 From da4900a353bd1136aa96cb5444b34f7beefa8e85 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 30 Sep 2014 11:10:13 +0100 Subject: Update 3.0-announcement.md --- docs/topics/3.0-announcement.md | 2 -- 1 file changed, 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 12ab5a0d..1c7e016e 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -9,10 +9,8 @@ See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-fr The most notable outstanding issues still to resolved on the `version-3.0` branch are as follows: * Forms support for serializers and in the browsable API. -* Enforcing uniqueness on `unique=True` and `unique_together` fields. * Optimisations for serialializing primary keys. * Refine style of validation errors in some cases, such as validation errors in `ListField`. -* `.validate()` method on fields. * `.transform_()` method on serializers. **Your feedback on the upgrade process and 3.0 changes is hugely important!** -- cgit v1.2.3 From 770d63fb046917f9fe1f08449f07bf13f1adfa4f Mon Sep 17 00:00:00 2001 From: Omer Katz Date: Wed, 1 Oct 2014 13:12:33 +0300 Subject: Fixed documentation typo. --- docs/topics/3.0-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 1c7e016e..d2505a1b 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -75,7 +75,7 @@ Previously the serializers used a two-step object creation, as follows: 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 save. This type of data needed to be hidden in some undocumentated state on the object instance, or kept as state on the serializer instance so that it could be used when `.save()` is called. +* 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 undocumentated 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, eg `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? -- cgit v1.2.3 From dfab9af294972720f59890967cd9ae1a6c0796b6 Mon Sep 17 00:00:00 2001 From: Craig de Stigter Date: Fri, 3 Oct 2014 08:41:18 +1300 Subject: Minor: fix spelling and grammar, mostly in 3.0 announcement --- docs/api-guide/fields.md | 2 +- docs/api-guide/renderers.md | 2 +- docs/topics/2.4-announcement.md | 2 +- docs/topics/3.0-announcement.md | 42 +++++++++++++++--------------- docs/topics/contributing.md | 2 +- docs/topics/release-notes.md | 2 +- docs/topics/writable-nested-serializers.md | 2 +- 7 files changed, 27 insertions(+), 27 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index f0778318..292a51d8 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -286,7 +286,7 @@ For example, to validate numbers up to 999 with a resolution of 2 decimal places serializers.DecimalField(max_digits=5, decimal_places=2) -And to validate numbers up to anything lesss than one billion with a resolution of 10 decimal places: +And to validate numbers up to anything less than one billion with a resolution of 10 decimal places: serializers.DecimalField(max_digits=19, decimal_places=10) diff --git a/docs/api-guide/renderers.md b/docs/api-guide/renderers.md index 2e1c892f..db7436c2 100644 --- a/docs/api-guide/renderers.md +++ b/docs/api-guide/renderers.md @@ -74,7 +74,7 @@ If your API includes views that can serve both regular webpages and API response Renders the request data into `JSON`, using utf-8 encoding. -Note that the default style is to include unicode characters, and render the response using a compact style with no uneccessary whitespace: +Note that the default style is to include unicode characters, and render the response using a compact style with no unnecessary whitespace: {"unicode black star":"★","value":999} diff --git a/docs/topics/2.4-announcement.md b/docs/topics/2.4-announcement.md index 09294b91..d8aa5b10 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 index d2505a1b..5242be57 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -6,10 +6,10 @@ The 3.0 release is now ready for some tentative testing and upgrades for super k See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-framework/pull/1800) for more details on remaining work. -The most notable outstanding issues still to resolved on the `version-3.0` branch are as follows: +The most notable outstanding issues still to be resolved on the `version-3.0` branch are as follows: * Forms support for serializers and in the browsable API. -* Optimisations for serialializing primary keys. +* Optimisations for serializing primary keys. * Refine style of validation errors in some cases, such as validation errors in `ListField`. * `.transform_()` method on serializers. @@ -50,13 +50,13 @@ Below is an in-depth guide to the API changes and migration notes for 3.0. The usage of `request.DATA` and `request.FILES` is now discouraged in favor of a single `request.data` attribute that contains *all* the parsed data. -Having seperate 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. +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: ExampleSerializer(data=request.data) -Instead of passing the files argument seperately: +Instead of passing the files argument separately: # Don't do this... ExampleSerializer(data=request.DATA, files=request.FILES) @@ -75,7 +75,7 @@ Previously the serializers used a two-step object creation, as follows: 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 undocumentated state on the object instance, or kept as state on the serializer instance so that it could be used when `.save()` is called. +* 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, eg `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? @@ -88,7 +88,7 @@ The resulting API changes are further detailed below. #### The `.create()` and `.update()` methods. -The `.restore_object()` method is now replaced with two seperate methods, `.create()` and `.update()`. +The `.restore_object()` method is now replaced with two separate methods, `.create()` and `.update()`. 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. @@ -107,7 +107,7 @@ The following example from the tutorial previously used `restore_object()` to ha # Create new instance return Snippet(**attrs) -This would now be split out into two seperate methods. +This would now be split out into two separate methods. def update(self, instance, validated_attrs) instance.title = validated_attrs.get('title', instance.title) @@ -146,7 +146,7 @@ The corresponding code would now look like this: #### Limitations of ModelSerializer validation. -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 seperation, and ensures that there's no automatic validation behavior on `ModelSerializer` classes that can't also be easily replicated on regular `Serializer` classes. +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. This change comes with the following limitations: @@ -157,7 +157,7 @@ This change comes with the following limitations: 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 dependancies involved in order of saving multiple related model instances. +* 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. @@ -289,7 +289,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 seperated 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. 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. @@ -299,7 +299,7 @@ REST framework now includes a simple `BaseSerializer` class that can be used to This class implements the same basic API as the `Serializer` class: -* `.data` - Returns the outgoing primative representation. +* `.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. @@ -320,7 +320,7 @@ To implement a read-only serializer using the `BaseSerializer` class, we just ne player_name = models.CharField(max_length=10) score = models.IntegerField() -It's simple to create a read-only serializer for converting `HighScore` instances into primative data types. +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): @@ -394,12 +394,12 @@ 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 primative representations. +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 primative representations. + into primitive representations. """ def to_representation(self, obj): for attribute_name in dir(obj): @@ -411,7 +411,7 @@ The following class is an example of a generic serializer that can handle coerci # Ignore methods and other callables. pass elif isinstance(attribute, (str, int, bool, float, type(None))): - # Primative types can be passed through unmodified. + # primitive types can be passed through unmodified. output[attribute_name] = attribute elif isinstance(attribute, list): # Recursivly deal with items in lists. @@ -437,7 +437,7 @@ 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. -* `WriteableField` as the base class for read-write fields. +* `WritableField` as the base class for read-write fields. We now use the following: @@ -448,9 +448,9 @@ We now use the following: REST framework now has more explict and clear control over validating empty values for fields. -Previously the meaning of the `required=False` keyword argument was underspecified. In practice it's use meant that a field could either be not included in the input, or it could be included, but be `None`. +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`. -We now have a better seperation, with seperate `required` and `allow_none` arguments. +We now have a better separation, with separate `required` and `allow_none` arguments. The following set of arguments are used to control validation of empty values: @@ -552,7 +552,7 @@ In order to ensure a consistent code style an assertion error will be raised if #### Enforcing consistent `source` usage. -I've see several codebases that unneccessarily 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. +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*: @@ -614,7 +614,7 @@ I would personally recommend that developers treat view instances as immutable o #### PUT as create. -Allowing `PUT` as create operations is problematic, as it neccessarily exposes information about the existence or non-existance of objects. It's also not obvious that transparently allowing re-creating of previously deleted instances is neccessarily a better default behavior than simply returning `404` responses. +Allowing `PUT` as create operations is problematic, as it necessarily exposes information about the existence or non-existance 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. @@ -628,7 +628,7 @@ This change means that you can now easily cusomize the style of error responses ## The metadata API -Behavior for dealing with `OPTIONS` requests was previously built directly into the class based views. This has now been properly seperated out into a Metadata API that allows the same pluggable style as other API policies in REST framework. +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. diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md index 3400bc8f..50b8ded1 100644 --- a/docs/topics/contributing.md +++ b/docs/topics/contributing.md @@ -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. diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 16589f3b..4fa3d627 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -149,7 +149,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/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 -- cgit v1.2.3 From 857a8486b1534f89bd482de86d39ff717b6618eb Mon Sep 17 00:00:00 2001 From: Craig de Stigter Date: Fri, 3 Oct 2014 09:00:33 +1300 Subject: More spelling tweaks --- docs/topics/3.0-announcement.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 5242be57..fcae79e1 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -411,7 +411,7 @@ The following class is an example of a generic serializer that can handle coerci # Ignore methods and other callables. pass elif isinstance(attribute, (str, int, bool, float, type(None))): - # primitive types can be passed through unmodified. + # Primitive types can be passed through unmodified. output[attribute_name] = attribute elif isinstance(attribute, list): # Recursivly deal with items in lists. @@ -446,7 +446,7 @@ We now use the following: #### The `required`, `allow_none`, `allow_blank` and `default` arguments. -REST framework now has more explict and clear control over validating empty values for fields. +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`. @@ -522,7 +522,7 @@ However this code *would not be valid* in `2.4.3`: # ... 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 implict `ModelSerializer` classes and explicit `Serializer` classes. +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): organisations = serializers.SlugRelatedField( -- cgit v1.2.3 From 3a3e2bf57d5443dc0b058d5beb3111f87c418947 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 3 Oct 2014 13:42:06 +0100 Subject: Serializer.save() takes keyword arguments, not 'extras' argument --- docs/topics/3.0-announcement.md | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index fcae79e1..4a781503 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -109,16 +109,16 @@ The following example from the tutorial previously used `restore_object()` to ha This would now be split out into two separate methods. - def update(self, instance, validated_attrs) - instance.title = validated_attrs.get('title', instance.title) - instance.code = validated_attrs.get('code', instance.code) - instance.linenos = validated_attrs.get('linenos', instance.linenos) - instance.language = validated_attrs.get('language', instance.language) - instance.style = validated_attrs.get('style', instance.style) + 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() - def create(self, validated_attrs): - return Snippet.objects.create(**validated_attrs) + def create(self, validated_data): + return Snippet.objects.create(**validated_data) Note that the `.create` method should return the newly created object instance. @@ -134,15 +134,14 @@ For example the following code *is no longer valid*: 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 using the `extras` keyword argument. +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) - extras = {'user': request.user} # Include the user when saving. - serializer.save(extras=extras) + serializer.save(user=request.user) # Include the user when saving. #### Limitations of ModelSerializer validation. -- cgit v1.2.3 From 0803716ed034389a09305b7f037cb05d9ff5c57d Mon Sep 17 00:00:00 2001 From: Kevin London Date: Sat, 4 Oct 2014 17:34:27 -0700 Subject: Update links in 2.4-announcement.md The links to Django Rest Framework pages were 404ing because the URLs include a slash.--- docs/topics/2.4-announcement.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/2.4-announcement.md b/docs/topics/2.4-announcement.md index 09294b91..8e4f3bb2 100644 --- a/docs/topics/2.4-announcement.md +++ b/docs/topics/2.4-announcement.md @@ -164,8 +164,8 @@ Once again, many thanks to all the generous [backers and sponsors][kickstarter-s [lts-releases]: https://docs.djangoproject.com/en/dev/internals/release-process/#long-term-support-lts-releases [2-4-release-notes]: release-notes#240 -[view-name-and-description-settings]: ../api-guide/settings/#view-names-and-descriptions -[client-ip-identification]: ../api-guide/throttling/#how-clients-are-identified +[view-name-and-description-settings]: ../api-guide/settings#view-names-and-descriptions +[client-ip-identification]: ../api-guide/throttling#how-clients-are-identified [2-3-announcement]: 2.3-announcement [github-labels]: https://github.com/tomchristie/django-rest-framework/issues [github-milestones]: https://github.com/tomchristie/django-rest-framework/milestones -- cgit v1.2.3 From 0cbb57b40fdb073c7ca09c9d1078926260c646db Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 8 Oct 2014 12:17:30 +0100 Subject: Tweak pre/post save hooks. Return instance in .update(). --- docs/topics/3.0-announcement.md | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 4a781503..89817ea5 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -116,11 +116,12 @@ This would now be split out into two separate methods. 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 the `.create` method should return the newly created object instance. +Note that these methods should return the newly created object instance. #### Use `.validated_data` instead of `.object`. @@ -592,18 +593,27 @@ 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. -#### Removal of pre/post save hooks. +#### Changes to pre/post save hooks. -The following method hooks no longer exist on the new, simplified, generic views: `pre_save`, `post_save`, `pre_delete`, `post_delete`. +The `pre_save` and `post_save` hooks no longer exist, but are replaced with `perform_create(self, serializer)` and `perform_update(self, serializer)`. -If you do need custom behavior, you might choose to instead override the `.save()` method on your serializer class. For example: +These method should save the object instance by calling `serializer.save()`, adding in any explicit additional arguments as required. They may also perform any custom pre-save or post-save behavior. - def save(self, *args, **kwargs): - instance = super(MySerializer).save(*args, **kwarg) +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) - return instance -Alternatively write your view logic exlpicitly, or tie your pre/post save behavior into the model class or model manager. +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. -- cgit v1.2.3 From 14ae52a24e93063f77c6010269bf9cd3316627fe Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 8 Oct 2014 16:09:37 +0100 Subject: More gradual deprecation --- docs/topics/3.0-announcement.md | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 89817ea5..6520f2bd 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -8,10 +8,9 @@ See the [Version 3.0 GitHub issue](https://github.com/tomchristie/django-rest-fr The most notable outstanding issues still to be resolved on the `version-3.0` branch are as follows: -* Forms support for serializers and in the browsable API. +* Finish forms support for serializers and in the browsable API. * Optimisations for serializing primary keys. * Refine style of validation errors in some cases, such as validation errors in `ListField`. -* `.transform_()` method on serializers. **Your feedback on the upgrade process and 3.0 changes is hugely important!** @@ -48,12 +47,13 @@ Below is an in-depth guide to the API changes and migration notes for 3.0. #### The `.data` and `.query_params` properties. -The usage of `request.DATA` and `request.FILES` is now discouraged in favor of a single `request.data` attribute that contains *all* the parsed data. +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. +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: @@ -62,7 +62,7 @@ Instead of passing the files argument separately: ExampleSerializer(data=request.DATA, files=request.FILES) -The usage of `request.QUERY_PARAMS` is now discouraged in favor of the lowercased `request.query_params`. +The usage of `request.QUERY_PARAMS` is now pending deprecation in favor of the lowercased `request.query_params`. ## Serializers @@ -73,7 +73,7 @@ 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: +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, eg `ExampleModel.objects.create(...)`. Manager classes are an excellent layer at which to enforce business logic and application-level data constraints. @@ -109,7 +109,7 @@ The following example from the tutorial previously used `restore_object()` to ha This would now be split out into two separate methods. - def update(self, instance, validated_data) + 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) @@ -211,28 +211,30 @@ The `exclude` option on `ModelSerializer` is no longer available. You should use #### The `extra_kwargs` option. -The `read_only_fields` and `write_only_fields` options on `ModelSerializer` have been removed and replaced with a more generic `extra_kwargs`. +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': {'read_only': True} + 'is_admin': {'write_only': True} } Alternatively, specify the field explicitly on the serializer class: class MySerializer(serializer.ModelSerializer): - is_admin = serializers.BooleanField(read_only=True) + 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 removed. They are no longer required, as you can use the `extra_kwargs` argument instead: +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: @@ -633,7 +635,7 @@ If you need to restore the previous behavior you can include the `AllowPUTAsCrea The generic views now raise `ValidationError` 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 cusomize the style of error responses across your entire API, without having to modify any of the generic views. +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 -- cgit v1.2.3 From 4c015df28cfb7dc7cf29f6dc4985c57e1f5cdc5d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 8 Oct 2014 16:43:33 +0100 Subject: Tweaks --- docs/topics/3.0-announcement.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 6520f2bd..26d261ed 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -144,6 +144,24 @@ The corresponding code would now look like this: logging.info('Creating ticket "%s"' % name) serializer.save(user=request.user) # Include the user when saving. +#### Change to `validate_`. + +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: + raise 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 it's validated value. + + def validate_score(self, value): + if value % 10 != 0: + raise 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. + #### Limitations of ModelSerializer validation. 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. -- cgit v1.2.3 From a58cfe167d837d34994b50f52098c552f6b0860e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 9 Oct 2014 09:38:03 +0100 Subject: Update tutorial for 3.0 --- docs/tutorial/1-serialization.md | 79 ++++++++++++----------- docs/tutorial/4-authentication-and-permissions.md | 14 ++-- 2 files changed, 50 insertions(+), 43 deletions(-) (limited to 'docs') diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md index b0565d91..db5b9ea7 100644 --- a/docs/tutorial/1-serialization.md +++ b/docs/tutorial/1-serialization.md @@ -41,20 +41,7 @@ Once that's done we can create an app that we'll use to create a simple Web API. python manage.py startapp snippets -The simplest way to get up and running will probably be to use an `sqlite3` database for the tutorial. Edit the `tutorial/settings.py` file, and set the default database `"ENGINE"` to `"sqlite3"`, and `"NAME"` to `"tmp.db"`. - - DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'tmp.db', - 'USER': '', - 'PASSWORD': '', - 'HOST': '', - 'PORT': '', - } - } - -We'll also need to add our new `snippets` app and the `rest_framework` app to `INSTALLED_APPS`. +We'll need to add our new `snippets` app and the `rest_framework` app to `INSTALLED_APPS`. Let's edit the `tutorial/settings.py` file: INSTALLED_APPS = ( ... @@ -72,7 +59,7 @@ Okay, we're ready to roll. ## Creating a model to work with -For the purposes of this tutorial we're going to start by creating a simple `Snippet` model that is used to store code snippets. Go ahead and edit the `snippets` app's `models.py` file. Note: Good programming practices include comments. Although you will find them in our repository version of this tutorial code, we have omitted them here to focus on the code itself. +For the purposes of this tutorial we're going to start by creating a simple `Snippet` model that is used to store code snippets. Go ahead and edit the `snippets/models.py` file. Note: Good programming practices include comments. Although you will find them in our repository version of this tutorial code, we have omitted them here to focus on the code itself. from django.db import models from pygments.lexers import get_all_lexers @@ -98,9 +85,10 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni class Meta: ordering = ('created',) -Don't forget to sync the database for the first time. +We'll also need to create an initial migration for our snippet model, and sync the database for the first time. - python manage.py syncdb + python manage.py makemigrations snippets + python manage.py migrate ## Creating a Serializer class @@ -112,40 +100,39 @@ The first thing we need to get started on our Web API is to provide a way of ser class SnippetSerializer(serializers.Serializer): - pk = serializers.Field() # Note: `Field` is an untyped read-only field. + pk = serializers.IntegerField(read_only=True) title = serializers.CharField(required=False, max_length=100) - code = serializers.CharField(widget=widgets.Textarea, - max_length=100000) + code = serializers.CharField(style={'type': 'textarea'}) linenos = serializers.BooleanField(required=False) language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python') style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly') - def restore_object(self, attrs, instance=None): + def create(self, validated_attrs): """ - Create or update a new snippet instance, given a dictionary - of deserialized field values. + Create and return a new `Snippet` instance, given the validated data. + """ + return Snippet.objects.create(**validated_attrs) - Note that if we don't define this method, then deserializing - data will simply return a dictionary of items. + def update(self, instance, validated_attrs): + """ + Update and return an existing `Snippet` instance, given the validated data. """ - 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 + instance.title = validated_attrs.get('title', instance.title) + instance.code = validated_attrs.get('code', instance.code) + instance.linenos = validated_attrs.get('linenos', instance.linenos) + instance.language = validated_attrs.get('language', instance.language) + instance.style = validated_attrs.get('style', instance.style) + instance.save() + return instance - # Create new instance - return Snippet(**attrs) +The first part of the serializer class defines the fields that get serialized/deserialized. The `create()` and `update()` methods define how fully fledged instances are created or modified when calling `serializer.save()` -The first part of the serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data. +A serializer class is very similar to a Django `Form` class, and includes similar validation flags on the various fields, such as `required`, `max_length` and `default`. -Notice that we can also use various attributes that would typically be used on form fields, such as `widget=widgets.Textarea`. These can be used to control how the serializer should render when displayed as an HTML form. This is particularly useful for controlling how the browsable API should be displayed, as we'll see later in the tutorial. +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. @@ -219,6 +206,24 @@ Open the file `snippets/serializers.py` again, and edit the `SnippetSerializer` model = Snippet fields = ('id', 'title', 'code', 'linenos', 'language', 'style') +Once nice property that serializers have is that you can inspect all the fields an serializer instance, by printing it's representation. Open the Django shell with `python manange.py shell`, then try the following: + + >>> from snippets.serializers import SnippetSerializer + >>> serializer = SnippetSerializer() + >>> print repr(serializer) # In python 3 use `print(repr(serializer))` + SnippetSerializer(): + id = IntegerField(label='ID', read_only=True) + title = CharField(allow_blank=True, max_length=100, required=False) + code = CharField(style={'type': 'textarea'}) + linenos = BooleanField(required=False) + language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')... + style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')... + +It's important to remember that `ModelSerializer` classes don't do anything particularly magically, they are simply a shortcut to creating a serializer class with: + +* An automatically determined set of fields. +* Simple default implementations for the `create()` and `update()` methods. + ## Writing regular Django views using our Serializer Let's see how we can write some API views using our new Serializer class. diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index 9120e254..adab1b55 100644 --- a/docs/tutorial/4-authentication-and-permissions.md +++ b/docs/tutorial/4-authentication-and-permissions.md @@ -92,24 +92,26 @@ Finally we need to add those views into the API, by referencing them from the UR Right now, if we created a code snippet, there'd be no way of associating the user that created the snippet, with the snippet instance. The user isn't sent as part of the serialized representation, but is instead a property of the incoming request. -The way we deal with that is by overriding a `.pre_save()` method on our snippet views, that allows us to handle any information that is implicit in the incoming request or requested URL. +The way we deal with that is by overriding a `.perform_create()` method on our snippet views, that allows us to modify how the instance save is managed, and handle any information that is implicit in the incoming request or requested URL. -On **both** the `SnippetList` and `SnippetDetail` view classes, add the following method: +On the `SnippetList` view class, add the following method: - def pre_save(self, obj): - obj.owner = self.request.user + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + +The `create()` method of our serializer will now be passed an additional `'owner'` field, along with the validated data from the request. ## Updating our serializer Now that snippets are associated with the user that created them, let's update our `SnippetSerializer` to reflect that. Add the following field to the serializer definition in `serializers.py`: - owner = serializers.Field(source='owner.username') + owner = serializers.ReadOnlyField(source='owner.username') **Note**: Make sure you also add `'owner',` to the list of fields in the inner `Meta` class. This field is doing something quite interesting. The `source` argument controls which attribute is used to populate a field, and can point at any attribute on the serialized instance. It can also take the dotted notation shown above, in which case it will traverse the given attributes, in a similar way as it is used with Django's template language. -The field we've added is the untyped `Field` class, in contrast to the other typed fields, such as `CharField`, `BooleanField` etc... The untyped `Field` is always read-only, and will be used for serialized representations, but will not be used for updating model instances when they are deserialized. +The field we've added is the untyped `ReadOnlyField` class, in contrast to the other typed fields, such as `CharField`, `BooleanField` etc... The untyped `ReadOnlyField` is always read-only, and will be used for serialized representations, but will not be used for updating model instances when they are deserialized. We could have also used `CharField(read_only=True)` here. ## Adding required permissions to views -- cgit v1.2.3 From 6637b2fae0dab65447ff0bfd5ac0ba68644446eb Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 9 Oct 2014 11:08:26 +0100 Subject: Document the Metadata API --- docs/api-guide/metadata.md | 103 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/api-guide/metadata.md (limited to 'docs') diff --git a/docs/api-guide/metadata.md b/docs/api-guide/metadata.md new file mode 100644 index 00000000..c3f036b7 --- /dev/null +++ b/docs/api-guide/metadata.md @@ -0,0 +1,103 @@ + + +# Metadata + +> [The `OPTIONS`] method allows a client to determine the options and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action or initiating a resource retrieval. +> +> — [RFC7231, Section 4.3.7.][cite] + +REST framework includes a configurable mechanism for determining how your API should respond to `OPTIONS` requests. This allows you to return API schema or other resource information. + +There are not currently any widely adopted conventions for exactly what style of response should be returned for HTTP `OPTIONS` requests, so we provide an ad-hoc style that returns some useful information. + +Here's an example response that demonstrates the information that is returned by default. + + HTTP 200 OK + Allow: GET, POST, HEAD, OPTIONS + Content-Type: application/json + + { + "name": "To Do List", + "description": "List existing 'To Do' items, or create a new item.", + "renders": [ + "application/json", + "text/html" + ], + "parses": [ + "application/json", + "application/x-www-form-urlencoded", + "multipart/form-data" + ], + "actions": { + "POST": { + "note": { + "type": "string", + "required": false, + "read_only": false, + "label": "title", + "max_length": 100 + } + } + } + } + +## Setting the metadata scheme + +You can set the metadata class globally using the `'DEFAULT_METADATA_CLASS'` settings key: + + REST_FRAMEWORK = { + 'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata' + } + +Or you can set the metadata class individually for a view: + + class APIRoot(APIView): + metadata_class = APIRootMetadata + + def get(self, request, format=None): + return Response({ + ... + }) + +The REST framework package only includes a single metadata class implementation, named `SimpleMetadata`. If you want to use an alternative style you'll need to implement a custom metadata class. + +## Creating schema endpoints + +If you have specific requirements for creating schema endpoints that are accessed with regular `GET` requests, you might consider re-using the metadata API for doing so. + +For example, the following additional route could be used on a viewset to provide a linkable schema endpoint. + + @list_route(methods=['GET']) + def schema(self, request): + meta = self.metadata_class() + data = meta.determine_metadata(request, self) + return Response(data) + +There are a couple of reasons that you might choose to take this approach, including that `OPTIONS` responses [are not cacheable][no-options]. + +--- + +# Custom metadata classes + +If you want to provide a custom metadata class you should override `BaseMetadata` and implement the `determine_metadata(self, request, view)` method. + +Useful things that you might want to do could include returning schema information, using a format such as [JSON schema][json-schema], or returning debug information to admin users. + +## Example + +The following class could be used to limit the information that is returned to `OPTIONS` requests. + + class MinimalMetadata(BaseMetadata): + """ + Don't include field and other information for `OPTIONS` requests. + Just return the name and description. + """ + def determine_metadata(self, request, view): + return { + 'name': view.get_view_name(), + 'description': view.get_view_description() + } + +[cite]: http://tools.ietf.org/html/rfc7231#section-4.3.7 +[no-options]: https://www.mnot.net/blog/2012/10/29/NO_OPTIONS +[json-schema]: http://json-schema.org/ -- cgit v1.2.3 From babdc78e61ac915fa4a01bdfb04e11a32dbf5d79 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 9 Oct 2014 11:39:01 +0100 Subject: Typo --- docs/api-guide/validators.md | 0 docs/topics/3.0-announcement.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 docs/api-guide/validators.md (limited to 'docs') diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 26d261ed..bffc608a 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -617,7 +617,7 @@ The view logic for the default method handlers has been significantly simplified The `pre_save` and `post_save` hooks no longer exist, but are replaced with `perform_create(self, serializer)` and `perform_update(self, serializer)`. -These method should save the object instance by calling `serializer.save()`, adding in any explicit additional arguments as required. They may also perform any custom pre-save or post-save behavior. +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: -- cgit v1.2.3 From 5d247a65c89594a7ab5ce2333612f23eadc6828d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 9 Oct 2014 15:11:19 +0100 Subject: First pass on nested serializers in HTML --- docs/tutorial/quickstart.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index 813e9872..c2dc4bea 100644 --- a/docs/tutorial/quickstart.md +++ b/docs/tutorial/quickstart.md @@ -26,11 +26,13 @@ Create a new Django project named `tutorial`, then start a new app called `quick Now sync your database for the first time: - python manage.py syncdb + python manage.py migrate -Make sure to create an initial user named `admin` with a password of `password`. We'll authenticate as that user later in our example. +We'll also create an initial user named `admin` with a password of `password`. We'll authenticate as that user later in our example. -Once you've set up a database and got everything synced and ready to go, open up the app's directory and we'll get coding... + python manage.py createsuperuser + +Once you've set up a database and initial user created and ready to go, open up the app's directory and we'll get coding... ## Serializers -- cgit v1.2.3 From d9a199ca0ddf92f999aa37b396596d0e3e0a26d9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 10 Oct 2014 14:16:09 +0100 Subject: exceptions.ValidationFailed, not Django's ValidationError --- docs/topics/3.0-announcement.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index bffc608a..b28670cf 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -191,7 +191,7 @@ Using the `depth` option on `ModelSerializer` will now create **read-only nested def create(self, validated_data): profile_data = validated_data.pop['profile'] user = User.objects.create(**validated_data) - profile = Profile.objects.create(user=user, **profile_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. @@ -223,10 +223,6 @@ We can now inspect the serializer representation in the Django shell, using `pyt rating = IntegerField() created_by = PrimaryKeyRelatedField(queryset=User.objects.all()) -#### Always use `fields`, not `exclude`. - -The `exclude` option on `ModelSerializer` is no longer available. You should use the more explicit `fields` option instead. - #### The `extra_kwargs` option. The `write_only_fields` option on `ModelSerializer` has been moved to `PendingDeprecation` and replaced with a more generic `extra_kwargs`. -- cgit v1.2.3 From d8a8987ab1eb6abbaee1a0de8cfea38eafe21293 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 10 Oct 2014 14:32:02 +0100 Subject: Tweaks --- docs/topics/3.0-announcement.md | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index b28670cf..aa0e0c7e 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -144,6 +144,12 @@ The corresponding code would now look like this: logging.info('Creating ticket "%s"' % name) serializer.save(user=request.user) # Include the user when saving. +#### Use `rest_framework.exceptions.ValidationFailed`. + +Django's `ValidationError` class is intended for use with HTML forms and it's API makes its use slightly awkward with nested validation errors as can occur in serializers. + +We now include a simpler `ValidationFailed` exception class in REST framework that you should use when raising validation failures. + #### Change to `validate_`. 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: -- cgit v1.2.3 From f8f101268e1d3ff0621c61299c13d78914874a2b Mon Sep 17 00:00:00 2001 From: wolfe Date: Tue, 14 Oct 2014 18:58:25 -0300 Subject: Update 3.0-announcement.md Swap order of custom field API changes so the two "and" clauses are in the same order.--- docs/topics/3.0-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index aa0e0c7e..dcd6d90c 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -518,7 +518,7 @@ The `MultipleChoiceField` class has been added. This field acts like `ChoiceFiel #### 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_representation(self, value)` and `to_internal_value(self, data)`. +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. -- cgit v1.2.3 From 113a28ed8ad65d912180e8be7a92b679251b0249 Mon Sep 17 00:00:00 2001 From: konradhalas Date: Thu, 16 Oct 2014 13:48:18 +0200 Subject: Add djoser to authentication docs. --- docs/api-guide/authentication.md | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'docs') diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 0ec5bad1..b355012e 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -415,6 +415,10 @@ The [HawkREST][hawkrest] library builds on the [Mohawk][mohawk] library to let y HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a way to achieve origin authentication and message integrity for HTTP messages. Similar to [Amazon's HTTP Signature scheme][amazon-http-signature], used by many of its services, it permits stateless, per-request authentication. [Elvio Toccalino][etoccalino] maintains the [djangorestframework-httpsignature][djangorestframework-httpsignature] package which provides an easy to use HTTP Signature Authentication mechanism. +## Djoser + +[Djoser][djoser] library provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. The package works with a custom user model and it uses token based authentication. This is a ready to use REST implementation of Django authentication system. + [cite]: http://jacobian.org/writing/rest-worst-practices/ [http401]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.2 [http403]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4 @@ -449,3 +453,4 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a [hawk]: https://github.com/hueniverse/hawk [mohawk]: http://mohawk.readthedocs.org/en/latest/ [mac]: http://tools.ietf.org/html/draft-hammer-oauth-v2-mac-token-05 +[djoser]: https://github.com/sunscrapers/djoser -- cgit v1.2.3 From 5e89994a10105b03b89abf0d0c9fee766942660e Mon Sep 17 00:00:00 2001 From: konradhalas Date: Thu, 16 Oct 2014 15:16:13 +0200 Subject: Add to main 3rd party resources list. --- docs/topics/third-party-resources.md | 1 + 1 file changed, 1 insertion(+) (limited to 'docs') diff --git a/docs/topics/third-party-resources.md b/docs/topics/third-party-resources.md index 1ca91742..0317dd64 100644 --- a/docs/topics/third-party-resources.md +++ b/docs/topics/third-party-resources.md @@ -16,6 +16,7 @@ To submit new content, [open an issue](https://github.com/tomchristie/django-res * [djangorestframework-jwt](https://github.com/GetBlimp/django-rest-framework-jwt) - Provides JSON Web Token Authentication support. * [hawkrest](https://github.com/kumar303/hawkrest) - Provides Hawk HTTP Authorization. * [djangorestframework-httpsignature](https://github.com/etoccalino/django-rest-framework-httpsignature) - Provides an easy to use HTTP Signature Authentication mechanism. +* [djoser](https://github.com/sunscrapers/djoser) - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation. ### Permissions -- cgit v1.2.3 From d0f610be4457947f78d20da4f2bdb04ef52d0db3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 16 Oct 2014 20:43:35 +0100 Subject: Update release notes --- docs/topics/3.0-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index dcd6d90c..f868b344 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -653,7 +653,7 @@ If you need to restore the previous behavior you can include the `AllowPUTAsCrea #### Customizing error responses. -The generic views now raise `ValidationError` for invalid data. This exception is then dealt with by the exception handler, rather than the view returning a `400 Bad Request` response directly. +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. -- cgit v1.2.3 From 05cbec9dd7f9f0b6a9b59b29ac6c9272b6ae50d8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 17 Oct 2014 13:23:14 +0100 Subject: Use serializers.ValidationError --- docs/topics/3.0-announcement.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index f868b344..658b50d3 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -144,11 +144,15 @@ The corresponding code would now look like this: logging.info('Creating ticket "%s"' % name) serializer.save(user=request.user) # Include the user when saving. -#### Use `rest_framework.exceptions.ValidationFailed`. +#### Using `serializers.ValidationError`. -Django's `ValidationError` class is intended for use with HTML forms and it's API makes its use slightly awkward with nested validation errors as can occur in serializers. +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. -We now include a simpler `ValidationFailed` exception class in REST framework that you should use when raising validation failures. +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_`. @@ -156,14 +160,14 @@ The `validate_` method hooks that can be attached to serializer clas def validate_score(self, attrs, source): if attrs[score] % 10 != 0: - raise ValidationError('This field should be a multiple of ten.') + 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 it's validated value. def validate_score(self, value): if value % 10 != 0: - raise ValidationError('This field should be a multiple of ten.') + 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. -- cgit v1.2.3 From 11edd01c34debbe3b689da91efd63cddab7b28c4 Mon Sep 17 00:00:00 2001 From: José Padilla Date: Fri, 17 Oct 2014 08:58:31 -0400 Subject: Add docs regarding linking third party packages --- docs/topics/contributing.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md index 3400bc8f..4fafb1b1 100644 --- a/docs/topics/contributing.md +++ b/docs/topics/contributing.md @@ -210,7 +210,9 @@ We recommend the [`django-reusable-app`][django-reusable-app] template as a good ## Linking to your package -Once your package is decently documented and available on PyPI open a pull request or issue, and we'll add a link to it from the main REST framework documentation. +Once your package is decently documented and available on PyPI open a pull request or issue, and we'll add a link to it from the main REST framework documentation. You can add your package under **Third party packages** of the API Guide section that best applies, like [Authentication][authentication] or [Permissions][permissions]. You can also link your package under the [Third Party Resources][third-party-resources] section. + +We also suggest adding it to the [REST Framework][rest-framework-grid] grid on Django Packages. [cite]: http://www.w3.org/People/Berners-Lee/FAQ.html [code-of-conduct]: https://www.djangoproject.com/conduct/ @@ -225,3 +227,7 @@ Once your package is decently documented and available on PyPI open a pull reque [docs]: https://github.com/tomchristie/django-rest-framework/tree/master/docs [mou]: http://mouapp.com/ [django-reusable-app]: https://github.com/dabapps/django-reusable-app +[authentication]: ../api-guide/authentication.md +[permissions]: ../api-guide/permissions.md +[third-party-resources]: third-party-resources.md +[rest-framework-grid]: https://www.djangopackages.com/grids/g/django-rest-framework/ -- cgit v1.2.3 From c5d1be8eac6cdb5cce000ec7c55e1847bfcf2359 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 22 Oct 2014 10:32:32 +0100 Subject: .validate() can raise field errors or non-field errors --- docs/topics/3.0-announcement.md | 45 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 658b50d3..9aeb5df6 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -163,7 +163,7 @@ The `validate_` method hooks that can be attached to serializer clas 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 it's validated value. +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: @@ -172,6 +172,22 @@ This is now simplified slightly, and the method hooks simply take the value to b Any ad-hoc validation that applies to more than one field should go in the `.validate(self, attrs)` method as usual. +Because `.validate_` 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. + #### Limitations of ModelSerializer validation. 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. @@ -189,7 +205,32 @@ REST framework 2.x attempted to automatically support writable nested serializat * 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. 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. +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 suport 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() -- cgit v1.2.3 From 702f47700de2c10f26f06b23099740c408ffe797 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 24 Oct 2014 12:12:01 +0100 Subject: Updated requirements --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/index.md b/docs/index.md index e4c971f9..9d436ad5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -50,7 +50,7 @@ Some reasons you might want to use REST framework: REST framework requires the following: * Python (2.6.5+, 2.7, 3.2, 3.3) -* Django (1.4.2+, 1.5, 1.6, 1.7) +* Django (1.4.11+, 1.5.5+, 1.6, 1.7) The following packages are optional: -- cgit v1.2.3 From f25f05dde58908ca6885c88499e4d5984f0f3502 Mon Sep 17 00:00:00 2001 From: Andrew Conti Date: Wed, 29 Oct 2014 16:31:36 -0400 Subject: Update authentication.md Based on the [new documentation](https://docs.djangoproject.com/en/dev/topics/auth/customizing/#django.contrib.auth.get_user_model) for Django 1.7 we should now use: ```python @receiver(post_save, sender=settings.AUTH_USER_MODEL) ``` instead of: ```python @receiver(post_save, sender=get_user_model()) ``` because `get_user_model()` only works once Django has imported all models. Otherwise you'll get: ```python django.core.exceptions.AppRegistryNotReady: Models aren't loaded yet. ``` When trying to start your sever after adding token authentication. From the Docs, ( linked above as well ) : >New in Django 1.7: >When connecting to signals sent by the User model, you should specify the custom model using the AUTH_USER_MODEL setting.--- docs/api-guide/authentication.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index b355012e..3a5156fd 100755 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -168,12 +168,13 @@ The `curl` command line tool may be useful for testing token authenticated APIs. If you want every user to have an automatically generated Token, you can simply catch the User's `post_save` signal. + from django.conf import settings from django.contrib.auth import get_user_model from django.db.models.signals import post_save from django.dispatch import receiver from rest_framework.authtoken.models import Token - @receiver(post_save, sender=get_user_model()) + @receiver(post_save, sender=settings.AUTH_USER_MODEL) def create_auth_token(sender, instance=None, created=False, **kwargs): if created: Token.objects.create(user=instance) -- cgit v1.2.3 From 27622058872c00e357deb7d7e86619a793ef4b41 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 31 Oct 2014 13:47:36 +0000 Subject: Validator documentation and tweaks --- docs/api-guide/validators.md | 183 +++++++++++++++++++++++++++++++++++++++++++ docs/index.md | 2 + docs/template.html | 1 + 3 files changed, 186 insertions(+) (limited to 'docs') diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index e69de29b..52c9e082 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -0,0 +1,183 @@ + + +# Validators + +> Validators can be useful for re-using validation logic between different types of fields. +> +> — [Django documentation][cite] + +Most of the time you're dealing with validation in REST framework you'll simply be relying on the default field validation, or writing explicit validation methods on serializer or field classes. + +Sometimes you'll want to place your validation logic into reusable components, so that it can easily be reused throughout your codebase. This can be achieved by using validator functions and validator classes. + +## Validation in REST framework + +Validation in Django REST framework serializers is handled a little differently to how validation works in Django's `ModelForm` class. + +With `ModelForm` the validation is performed partially on the form, and partially on the model instance. With REST framework the validation is performed entirely on the serializer class. This is advantageous for the following reasons: + +* It introduces a proper separation of concerns, making your code behaviour more obvious. +* It is easy to switch between using shortcut `ModelSerializer` classes and using explicit `Serializer` classes. Any validation behaviour being used for `ModelSerializer` is simple to replicate. +* Printing the `repr` of a serializer instance will show you exactly what validation rules it applies. There's no extra hidden validation behaviour being called on the model instance. + +When you're using `ModelSerializer` all of this is handled automatically for you. If you want to drop down to using a `Serializer` classes instead, then you need to define the validation rules explicitly. + +#### Example + +As an example of how REST framework uses explicit validation, we'll take a simple model class that has a field with a uniqueness constraint. + + class CustomerReportRecord(models.Model): + time_raised = models.DateTimeField(default=timezone.now, editable=False) + reference = models.CharField(unique=True, max_length=20) + description = models.TextField() + +Here's a basic `ModelSerializer` that we can use for creating or updating instances of `CustomerReportRecord`: + + class CustomerReportSerializer(serializers.ModelSerializer): + class Meta: + model = CustomerReportRecord + +If we open up the Django shell using `manage.py shell` we can now + + >>> from project.example.serializers import CustomerReportSerializer + >>> serializer = CustomerReportSerializer() + >>> print(repr(serializer)) + CustomerReportSerializer(): + id = IntegerField(label='ID', read_only=True) + time_raised = DateTimeField(read_only=True) + reference = CharField(max_length=20, validators=[]) + description = CharField(style={'type': 'textarea'}) + +The interesting bit here is the `reference` field. We can see that the uniqueness constraint is being explicitly enforced by a validator on the serializer field. + +Because of this more explicit style REST framework includes a few validator classes that are not available in core Django. These classes are detailed below. + +--- + +## UniqueValidator + +This validator can be used to enforce the `unique=True` constraint on model fields. +It takes a single required argument, and an optional `messages` argument: + +* `queryset` *required* - This is the queryset against which uniqueness should be enforced. +* `message` - The error message that should be used when validation fails. + +This validator should be applied to *serializer fields*, like so: + + slug = SlugField( + max_length=100, + validators=[UniqueValidator(queryset=BlogPost.objects.all())] + ) + +## UniqueTogetherValidator + +This validator can be used to enforce `unique_together` constraints on model instances. +It has two required arguments, and a single optional `messages` argument: + +* `queryset` *required* - This is the queryset against which uniqueness should be enforced. +* `fields` *required* - A list or tuple of field names which should make a unique set. These must exist as fields on the serializer class. +* `message` - The error message that should be used when validation fails. + +The validator should be applied to *serializer classes*, like so: + + class ExampleSerializer(serializers.Serializer): + # ... + class Meta: + # ToDo items belong to a parent list, and have an ordering defined + # by the 'position' field. No two items in a given list may share + # the same position. + validators = [ + UniqueTogetherValidator( + queryset=ToDoItem.objects.all(), + fields=('list', 'position') + ) + ] + +## UniqueForDateValidator + +## UniqueForMonthValidator + +## UniqueForYearValidator + +These validators can be used to enforce the `unique_for_date`, `unique_for_month` and `unique_for_year` constraints on model instances. They take the following arguments: + +* `queryset` *required* - This is the queryset against which uniqueness should be enforced. +* `field` *required* - A field name against which uniqueness in the given date range will be validated. This must exist as a field on the serializer class. +* `date_field` *required* - A field name which will be used to determine date range for the uniqueness constrain. This must exist as a field on the serializer class. +* `message` - The error message that should be used when validation fails. + +The validator should be applied to *serializer classes*, like so: + + class ExampleSerializer(serializers.Serializer): + # ... + class Meta: + # Blog posts should have a slug that is unique for the current year. + validators = [ + UniqueForYearValidator( + queryset=BlogPostItem.objects.all(), + field='slug', + date_field='published' + ) + ] + +The date field that is used for the validation is always required to be present on the serializer class. You can't simply rely on a model class `default=...`, because the value being used for the default wouldn't be generated until after the validation has run. + +There are a couple of styles you may want to use for this depending on how you want your API to behave. If you're using `ModelSerializer` you'll probably simply rely on the defaults that REST framework generates for you, but if you are using `Serializer` or simply want more explicit control, use on of the styles demonstrated below. + +#### Using with a writable date field. + +If you want the date field to be writable the only thing worth noting is that you should ensure that it is always available in the input data, either by setting a `default` argument, or by setting `required=True`. + + published = serializers.DateTimeField(required=True) + +#### Using with a read-only date field. + +If you want the date field to be visible, but not editable by the user, then set `read_only=True` and additionally set a `default=...` argument. + + published = serializers.DateTimeField(read_only=True, default=timezone.now) + +The field will not be writable to the user, but the default value will still be passed through to the `validated_data`. + +#### Using with a hidden date field. + +If you want the date field to be entirely hidden from the user, then use `HiddenField`. This field type does not accept user input, but instead always returns it's default value to the `validated_data` in the serializer. + + published = serializers.HiddenField(default=timezone.now) + +--- + +# Writing custom validators + +You can use any of Django's existing validators, or write your own custom validators. + +## Function based + +A validator may be any callable that raises a `serializers.ValidationError` on failure. + + def even_number(value): + if value % 2 != 0: + raise serializers.ValidationError('This field must be an even number.') + +## Class based + +To write a class based validator, use the `__call__` method. Class based validators are useful as they allow you to parameterize and reuse behavior. + + class MultipleOf: + def __init__(self, base): + self.base = base + + def __call__(self, value): + if value % self.base != 0 + message = 'This field must be a multiple of %d.' % self.base + raise serializers.ValidationError(message) + +#### Using `set_context()` + +In some advanced cases you might want a validator to be passed the serializer field it is being used with as additional context. You can do so by declaring a `set_context` method on a class based validator. + + def set_context(self, serializer_field): + # Determine if this is an update or a create operation. + # In `__call__` we can then use that information to modify the validation behavior. + self.is_update = serializer_field.parent.instance is not None + +[cite]: https://docs.djangoproject.com/en/dev/ref/validators/ diff --git a/docs/index.md b/docs/index.md index 9d436ad5..cff7b835 100644 --- a/docs/index.md +++ b/docs/index.md @@ -173,6 +173,7 @@ The API guide is your complete reference manual to all the functionality provide * [Serializers][serializers] * [Serializer fields][fields] * [Serializer relations][relations] +* [Validation][validation] * [Authentication][authentication] * [Permissions][permissions] * [Throttling][throttling] @@ -294,6 +295,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [serializers]: api-guide/serializers.md [fields]: api-guide/fields.md [relations]: api-guide/relations.md +[validation]: api-guide/validation.md [authentication]: api-guide/authentication.md [permissions]: api-guide/permissions.md [throttling]: api-guide/throttling.md diff --git a/docs/template.html b/docs/template.html index bb3ae221..0d2fb16e 100644 --- a/docs/template.html +++ b/docs/template.html @@ -95,6 +95,7 @@ a.fusion-poweredby {
  • Serializers
  • Serializer fields
  • Serializer relations
  • +
  • Validation
  • Authentication
  • Permissions
  • Throttling
  • -- cgit v1.2.3 From 254701230d85612cf0210d4549c2d74f410a181d Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 31 Oct 2014 13:58:40 +0000 Subject: Fix up validators docs --- docs/api-guide/validators.md | 2 +- docs/index.md | 2 +- docs/template.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index 52c9e082..6a0ef4ff 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -8,7 +8,7 @@ Most of the time you're dealing with validation in REST framework you'll simply be relying on the default field validation, or writing explicit validation methods on serializer or field classes. -Sometimes you'll want to place your validation logic into reusable components, so that it can easily be reused throughout your codebase. This can be achieved by using validator functions and validator classes. +However, sometimes you'll want to place your validation logic into reusable components, so that it can easily be reused throughout your codebase. This can be achieved by using validator functions and validator classes. ## Validation in REST framework diff --git a/docs/index.md b/docs/index.md index cff7b835..161886a4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -173,7 +173,7 @@ The API guide is your complete reference manual to all the functionality provide * [Serializers][serializers] * [Serializer fields][fields] * [Serializer relations][relations] -* [Validation][validation] +* [Validators][validators] * [Authentication][authentication] * [Permissions][permissions] * [Throttling][throttling] diff --git a/docs/template.html b/docs/template.html index 0d2fb16e..f36cffc6 100644 --- a/docs/template.html +++ b/docs/template.html @@ -95,7 +95,7 @@ a.fusion-poweredby {
  • Serializers
  • Serializer fields
  • Serializer relations
  • -
  • Validation
  • +
  • Validators
  • Authentication
  • Permissions
  • Throttling
  • -- cgit v1.2.3 From 964b3401fbcf27afcc2f1e41c5b0e7c538b1c68f Mon Sep 17 00:00:00 2001 From: Andy Baker Date: Sat, 1 Nov 2014 10:25:59 +0000 Subject: typo fix --- docs/topics/3.0-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 9aeb5df6..f69c0b25 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -323,7 +323,7 @@ Alternatively, specify the field explicitly on the serializer class: #### Fields for model methods and properties. -With `ModelSerilizer` 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: +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() -- cgit v1.2.3 From 96bb77dc35a75df0fd9ad2fabb0f4661b2485600 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 3 Nov 2014 11:20:18 +0000 Subject: Version 2.4.4 --- docs/topics/release-notes.md | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'docs') diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 16589f3b..11d12ae3 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+). -- cgit v1.2.3 From d27b8cc09b83bac10346effa1021493d2835b794 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 3 Nov 2014 12:00:19 +0000 Subject: PUT as create docs, and move mixin out to external gist --- docs/api-guide/generic-views.md | 14 ++++++++++++++ docs/topics/3.0-announcement.md | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 49be0cae..d2836fa6 100755 --- a/docs/api-guide/generic-views.md +++ b/docs/api-guide/generic-views.md @@ -368,6 +368,20 @@ If you are using a mixin across multiple views, you can take this a step further Using custom base classes is a good option if you have custom behavior that consistently needs to be repeated across a large number of views throughout your project. +--- + +# PUT as create + +Prior to version 3.0 the REST framework mixins treated `PUT` as either an update or a create operation, depending on if the object already existed or not. + +Allowing `PUT` as create operations is problematic, as it necessarily exposes information about the existence or non-existance 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 from version 3.0 onwards we now use 404 behavior as the default, due to it being simpler and more obvious. + +If you need to generic PUT-as-create behavior you may want to include something like [this `AllowPUTAsCreateMixin` class](https://gist.github.com/tomchristie/a2ace4577eff2c603b1b) as a mixin to your views. + +--- + # Third party packages The following third party packages provide additional generic view implementations. diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index f69c0b25..97ffdcf3 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -694,7 +694,7 @@ Allowing `PUT` as create operations is problematic, as it necessarily exposes in 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 can include the `AllowPUTAsCreateMixin` class in your view. This class can be imported from `rest_framework.mixins`. +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. -- cgit v1.2.3 From 9ce130d1b56039b0b7a7b04024f95c8c49c8fff7 Mon Sep 17 00:00:00 2001 From: Rocky Meza Date: Mon, 3 Nov 2014 06:56:42 -0700 Subject: Fixed some typos in 3.0 release notes --- docs/topics/3.0-announcement.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 97ffdcf3..f9987ad0 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -748,7 +748,7 @@ You can revert this behavior, and display filenames in the representation by usi You can also modify serializer fields individually, using the `use_url` argument: - uploaded_file = serializers.FileField(user_url=False) + 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: @@ -790,7 +790,7 @@ You can modify this behavior globally by using the `COERCE_DECIMAL_TO_STRING` se 'COERCE_DECIMAL_TO_STRING': False } -Or modify it on an individual serializer field, using the `corece_to_string` keyword argument. +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( -- cgit v1.2.3 From 0da30e6d1dea68d7c075067dd997fb1e0e26f27b Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 3 Nov 2014 14:43:00 +0000 Subject: Add note about 3.0 being merged to master --- docs/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/index.md b/docs/index.md index 5b31cc83..6288efa3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,6 +9,10 @@ --- +**Note**: The incoming 3.0 version has now been merged to the `master` branch on GitHub. For the source of the currently available PyPI version, please see the `2.4.4` tag. + +--- +

    -detail`. **required**. +* `view_name` - The view name that should be used as the target of the relationship. If you're using [the standard router classes][routers] this will be a string with the format `-detail`. **required**. * `lookup_field` - The field on the target that should be used for the lookup. Should correspond to a URL keyword argument on the referenced view. Default is `'pk'`. * `format` - If using format suffixes, hyperlinked fields will use the same format suffix for the target unless overridden by using the `format` argument. diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 2144a2ed..48af30a5 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -154,13 +154,13 @@ Default: `None` ### SEARCH_PARAM -The name of a query paramater, which can be used to specify the search term used by `SearchFilter`. +The name of a query parameter, which can be used to specify the search term used by `SearchFilter`. Default: `search` #### ORDERING_PARAM -The name of a query paramater, which can be used to specify the ordering of results returned by `OrderingFilter`. +The name of a query parameter, which can be used to specify the ordering of results returned by `OrderingFilter`. Default: `ordering` @@ -321,7 +321,7 @@ When set to `False`, JSON responses will escape non-ascii characters, like so: {"unicode black star":"\u2605"} -Both styles conform to [RFC 4627][rfc4627], and are syntactically valid JSON. The unicode style is prefered as being more user-friendly when inspecting API responses. +Both styles conform to [RFC 4627][rfc4627], and are syntactically valid JSON. The unicode style is preferred as being more user-friendly when inspecting API responses. Default: `True` diff --git a/docs/api-guide/throttling.md b/docs/api-guide/throttling.md index 16a7457b..147c16ff 100644 --- a/docs/api-guide/throttling.md +++ b/docs/api-guide/throttling.md @@ -74,7 +74,7 @@ If you need to strictly identify unique client IP addresses, you'll need to firs It is important to understand that if you configure the `NUM_PROXIES` setting, then all clients behind a unique [NAT'd](http://en.wikipedia.org/wiki/Network_address_translation) gateway will be treated as a single client. -Further context on how the `X-Forwarded-For` header works, and identifing a remote client IP can be [found here][identifing-clients]. +Further context on how the `X-Forwarded-For` header works, and identifying a remote client IP can be [found here][identifing-clients]. ## Setting up the cache @@ -85,7 +85,7 @@ If you need to use a cache other than `'default'`, you can do so by creating a c class CustomAnonRateThrottle(AnonRateThrottle): cache = get_cache('alternate') -You'll need to rememeber to also set your custom throttle class in the `'DEFAULT_THROTTLE_CLASSES'` settings key, or using the `throttle_classes` view attribute. +You'll need to remember to also set your custom throttle class in the `'DEFAULT_THROTTLE_CLASSES'` settings key, or using the `throttle_classes` view attribute. --- -- cgit v1.2.3 From 59b30307e80fb26f2c7caa82492c78fe265b537e Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 7 Nov 2014 20:10:15 +0000 Subject: Fixes to release notes --- docs/topics/3.0-announcement.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index e8428bc2..272b730c 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -1,17 +1,11 @@ ## Pre-release notes: -The 3.0 release is now ready for some tentative testing and upgrades for super keen early adopters. You can install the development version directly from GitHub like so: +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/version-3.0.zip + 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. -The most notable outstanding issues still to be resolved on the `version-3.0` branch are as follows: - -* Finish forms support for serializers and in the browsable API. -* Optimisations for serializing primary keys. -* Refine style of validation errors in some cases, such as validation errors in `ListField`. - **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. @@ -237,7 +231,7 @@ To use writable nested serialization you'll want to declare a nested field on th fields = ('username', 'email', 'profile') def create(self, validated_data): - profile_data = validated_data.pop['profile'] + profile_data = validated_data.pop('profile') user = User.objects.create(**validated_data) Profile.objects.create(user=user, **profile_data) return user -- cgit v1.2.3 From 93633c297c69a1eefda5e153553c4f021cf10bd8 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Fri, 7 Nov 2014 20:44:12 +0000 Subject: Tweaks to release notes --- docs/topics/3.0-announcement.md | 43 ++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 16 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 272b730c..d3da0607 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -27,8 +27,8 @@ 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 plus new `ListField` and `MultipleChoiceField` classes. -* Super simple default implementations for the generic views. +* 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. @@ -70,7 +70,7 @@ Previously the serializers used a two-step object creation, as follows: 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, eg `ExampleModel.objects.create(...)`. Manager classes are an excellent layer at which to enforce business logic and application-level data constraints. +* 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: @@ -184,7 +184,7 @@ This ensures you can still write validation that compares all the input fields, #### 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 behaviour on `ModelSerializer` classes that can't also be easily replicated on regular `Serializer` classes. +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. @@ -219,7 +219,7 @@ If you try to use a writable nested serializer without writing a custom `create( >>> >>> serializer = UserSerializer(data=data) >>> serializer.save() - AssertionError: The `.create()` method does not suport nested writable fields by default. Write an explicit `.create()` method for serializer `UserSerializer`, or set `read_only=True` on nested serializer fields. + 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. @@ -274,7 +274,7 @@ The `write_only_fields` option on `ModelSerializer` has been moved to `PendingDe model = MyModel fields = ('id', 'email', 'notes', 'is_admin') extra_kwargs = { - 'is_admin': {'write_only': True} + 'is_admin': {'write_only': True} } Alternatively, specify the field explicitly on the serializer class: @@ -321,8 +321,8 @@ With `ModelSerializer` you can now specify field names in the `fields` option th to_email = models.EmailField() message = models.CharField(max_length=1000) - def expiry_date(self): - return self.created + datetime.timedelta(days=30) + def expiry_date(self): + return self.created + datetime.timedelta(days=30) You can include `expiry_date` as a field option on a `ModelSerializer` class. @@ -584,7 +584,7 @@ Previously relational fields that were explicitly declared on a serializer class This code *would be valid* in `2.4.3`: class AccountSerializer(serializers.ModelSerializer): - organisations = serializers.SlugRelatedField(slug_field='name') + organizations = serializers.SlugRelatedField(slug_field='name') class Meta: model = Account @@ -593,7 +593,7 @@ However this code *would not be valid* in `2.4.3`: # Missing `queryset` class AccountSerializer(serializers.Serializer): - organisations = serializers.SlugRelatedField(slug_field='name') + organizations = serializers.SlugRelatedField(slug_field='name') def restore_object(self, attrs, instance=None): # ... @@ -602,9 +602,9 @@ 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): - organisations = serializers.SlugRelatedField( + organizations = serializers.SlugRelatedField( slug_field='name', - queryset=Organisation.objects.all() + queryset=Organization.objects.all() ) class Meta: @@ -637,7 +637,7 @@ The following usage will *now raise an error*: #### The `UniqueValidator` and `UniqueTogetherValidator` classes. -REST framework now provides two new validators that allow you to ensure field uniqueness, while still using a completely explicit `Serializer` class instead of using `ModelSerializer`. +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. @@ -645,11 +645,11 @@ The `UniqueValidator` should be applied to a serializer field, and takes a singl from rest_framework.validators import UniqueValidator class OrganizationSerializer(serializers.Serializer): - url = serializers.HyperlinkedIdentityField(view_name='organisation_detail') + url = serializers.HyperlinkedIdentityField(view_name='organization_detail') created = serializers.DateTimeField(read_only=True) name = serializers.CharField( max_length=100, - validators=UniqueValidator(queryset=Organisation.objects.all()) + 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. @@ -664,6 +664,10 @@ The `UniqueTogetherValidator` should be applied to a serializer, and takes a `qu fields=('category', 'position') )] +#### The `UniqueForDateValidator` classes. + +**TODO: Needs documenting.** + ## Generic views #### Simplification of view logic. @@ -700,7 +704,7 @@ I would personally recommend that developers treat view instances as immutable o #### PUT as create. -Allowing `PUT` as create operations is problematic, as it necessarily exposes information about the existence or non-existance 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. +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. @@ -718,6 +722,10 @@ Behavior for dealing with `OPTIONS` requests was previously built directly into 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. @@ -817,6 +825,7 @@ The default JSON renderer will return float objects for uncoerced `Decimal` inst 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. @@ -824,3 +833,5 @@ The 3.1 release is planned to address improvements in the following components: 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 -- cgit v1.2.3 From 058fea23d3a9776e5fb23b77e81c087d0d8d7e6a Mon Sep 17 00:00:00 2001 From: José Padilla Date: Fri, 7 Nov 2014 23:55:42 -0400 Subject: Fix csrf-ajax link. Closes #2045 --- docs/topics/ajax-csrf-cors.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs') 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/ -- cgit v1.2.3 From 440d61ab364da971973ae8d784e77e21e5cdaa7f Mon Sep 17 00:00:00 2001 From: Jochen Van de Velde Date: Sat, 8 Nov 2014 11:02:48 +0100 Subject: Fix some typos and other errors --- docs/topics/contributing.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'docs') diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md index 96d9a98c..6c289938 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 @@ -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 -- cgit v1.2.3 From f387cd89da55ef88fcac504f5795ea9b591f3fba Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 10 Nov 2014 12:21:27 +0000 Subject: Uniqueness constraints imply a forced 'required=True'. Refs #1945 --- docs/api-guide/validators.md | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'docs') diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index 6a0ef4ff..bb073f57 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -93,6 +93,12 @@ The validator should be applied to *serializer classes*, like so: ) ] +--- + +**Note**: The `UniqueTogetherValidation` class always imposes an implicit constraint that all the fields it applies to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input. + +--- + ## UniqueForDateValidator ## UniqueForMonthValidator @@ -146,6 +152,10 @@ If you want the date field to be entirely hidden from the user, then use `Hidden --- +**Note**: The `UniqueForValidation` classes always imposes an implicit constraint that the fields they are applied to are always treated as required. Fields with `default` values are an exception to this as they always supply a value even when omitted from user input. + +--- + # Writing custom validators You can use any of Django's existing validators, or write your own custom validators. -- cgit v1.2.3 From fd7db776addbd5e30f132fe6846ec5c5caab5c40 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 10 Nov 2014 12:32:03 +0000 Subject: Bring UniqueValidator implementation in line with other uniquness validators. --- docs/api-guide/validators.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index bb073f57..a50636bc 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -16,9 +16,9 @@ Validation in Django REST framework serializers is handled a little differently With `ModelForm` the validation is performed partially on the form, and partially on the model instance. With REST framework the validation is performed entirely on the serializer class. This is advantageous for the following reasons: -* It introduces a proper separation of concerns, making your code behaviour more obvious. -* It is easy to switch between using shortcut `ModelSerializer` classes and using explicit `Serializer` classes. Any validation behaviour being used for `ModelSerializer` is simple to replicate. -* Printing the `repr` of a serializer instance will show you exactly what validation rules it applies. There's no extra hidden validation behaviour being called on the model instance. +* It introduces a proper separation of concerns, making your code behavior more obvious. +* It is easy to switch between using shortcut `ModelSerializer` classes and using explicit `Serializer` classes. Any validation behavior being used for `ModelSerializer` is simple to replicate. +* Printing the `repr` of a serializer instance will show you exactly what validation rules it applies. There's no extra hidden validation behavior being called on the model instance. When you're using `ModelSerializer` all of this is handled automatically for you. If you want to drop down to using a `Serializer` classes instead, then you need to define the validation rules explicitly. -- cgit v1.2.3 From ea98de9b889173235a908ee2ce5a2aba5d8223c7 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 13 Nov 2014 19:28:57 +0000 Subject: Model fields with .blank or .null now map to required=False. Closes #2017. Closes #2021. --- docs/topics/3.0-announcement.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index d3da0607..885fc183 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -506,9 +506,9 @@ We now use the following: 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`. +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` and `allow_none` arguments. +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: @@ -519,7 +519,7 @@ The following set of arguments are used to control validation of empty values: 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 there if you need it, but you'll more typically want defaults to be set on model fields, rather than serializer fields. +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. -- cgit v1.2.3 From d08cfb0559033acaefbc23863c43ef858c2c0e1c Mon Sep 17 00:00:00 2001 From: José Padilla Date: Mon, 17 Nov 2014 08:17:40 -0400 Subject: Fix runtests examples Correctly rename runtests examples to work with current runtests.py file.--- docs/topics/contributing.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'docs') diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md index 6c289938..52f6e287 100644 --- a/docs/topics/contributing.md +++ b/docs/topics/contributing.md @@ -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. -- cgit v1.2.3 From 0f508c58211051c873aae5a2d1c65a0c595e732a Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Mon, 17 Nov 2014 18:36:32 +0000 Subject: Docs for advanced default argument usage. Closes #1945 --- docs/api-guide/validators.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'docs') diff --git a/docs/api-guide/validators.md b/docs/api-guide/validators.md index a50636bc..ac2f3248 100644 --- a/docs/api-guide/validators.md +++ b/docs/api-guide/validators.md @@ -156,6 +156,38 @@ If you want the date field to be entirely hidden from the user, then use `Hidden --- +# Advanced 'default' argument usage + +Validators that are applied across multiple fields in the serializer can sometimes require a field input that should not be provided by the API client, but that *is* available as input to the validator. + +Two patterns that you may want to use for this sort of validation include: + +* Using `HiddenField`. This field will be present in `validated_data` but *will not* be used in the serializer output representation. +* Using a standard field with `read_only=True`, but that also includes a `default=…` argument. This field *will* be used in the serializer output representation, but cannot be set directly by the user. + +REST framework includes a couple of defaults that may be useful in this context. + +#### CurrentUserDefault + +A default class that can be used to represent the current user. In order to use this, the 'request' must have been provided as part of the context dictionary when instantiating the serializer. + + owner = serializers.HiddenField( + default=CurrentUserDefault() + ) + +#### CreateOnlyDefault + +A default class that can be used to *only set a default argument during create operations*. During updates the field is omitted. + +It takes a single argument, which is the default value or callable that should be used during create operations. + + created_at = serializers.DateTimeField( + read_only=True, + default=CreateOnlyDefault(timezone.now) + ) + +--- + # Writing custom validators You can use any of Django's existing validators, or write your own custom validators. -- cgit v1.2.3 From 5e74f02d61e05d38bf4e22c6227144def2a96128 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 18 Nov 2014 14:49:00 +0000 Subject: Note removal of 'save_object' and fail loudly if it exists --- docs/topics/3.0-announcement.md | 2 ++ 1 file changed, 2 insertions(+) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 885fc183..90cbda4d 100644 --- a/docs/topics/3.0-announcement.md +++ b/docs/topics/3.0-announcement.md @@ -84,6 +84,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. -- cgit v1.2.3 From f269826a7d8a980536ffbce369be369c4df58d23 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 18 Nov 2014 16:26:45 +0000 Subject: Note on 3.0 beta --- docs/topics/3.0-announcement.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 90cbda4d..06fdc9fd 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!** -- cgit v1.2.3 From 51b7033e4aeeefe19012a77b09a0b23d4a52a5bc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Wed, 19 Nov 2014 14:03:19 +0000 Subject: Further notes in 3.0 announcement --- docs/topics/3.0-announcement.md | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 90cbda4d..3c73881f 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. -- cgit v1.2.3 From 928cbc640ef1db2c3fb96d352f69b8ffb66313e6 Mon Sep 17 00:00:00 2001 From: Raony Guimarães Date: Wed, 19 Nov 2014 13:53:36 -0200 Subject: small type --- docs/tutorial/quickstart.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md index c2dc4bea..1c398c1f 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 .. -- cgit v1.2.3 From 04d2635b24f0699ed8ed24436a58c203ad6a9a11 Mon Sep 17 00:00:00 2001 From: Gil Gonçalves Date: Thu, 20 Nov 2014 11:33:42 +0000 Subject: Removed unused import from code snippet in tutorial --- docs/tutorial/5-relationships-and-hyperlinked-apis.md | 1 - 1 file changed, 1 deletion(-) (limited to 'docs') diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md index 36473ce9..50552616 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 -- cgit v1.2.3 From 6a2023a362bcc10978ed725fa8816ddf08ea7c73 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 13:52:00 +0000 Subject: Note on 3.1 and 3.2 releases. --- docs/topics/3.0-announcement.md | 3 +++ 1 file changed, 3 insertions(+) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 958be2d6..03e3e070 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. -- cgit v1.2.3 From 071c064d251bb2f268762fb34cf3f248b5ce4557 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Thu, 20 Nov 2014 14:22:53 +0000 Subject: Fill in TODOs for 3.0 beta release notes --- docs/topics/3.0-announcement.md | 47 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 03e3e070..7bbfba5b 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 -- cgit v1.2.3 From 8e940a22fb292ed6b20b9e4b5da607ffca1dba2f Mon Sep 17 00:00:00 2001 From: Matthew Dapena-Tretter Date: Fri, 21 Nov 2014 15:58:31 -0500 Subject: 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(-) (limited to 'docs') diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 48af30a5..96d715ea 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: -- cgit v1.2.3 From c94d1e6d3ea76eb8bdd28717364e42d14e6722d7 Mon Sep 17 00:00:00 2001 From: Tony Nguyen Date: Sun, 23 Nov 2014 22:40:40 +0700 Subject: Fix typo "serailizers" to "serializers" --- docs/topics/3.0-announcement.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 7bbfba5b..694ad8a5 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 -- cgit v1.2.3 From 790a651893c4bc6e68bc4d724d5df4ea3f883ced Mon Sep 17 00:00:00 2001 From: Gil Gonçalves Date: Mon, 24 Nov 2014 08:51:08 +0000 Subject: Fixed database update instructions --- docs/tutorial/4-authentication-and-permissions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md index adab1b55..4e4edeea 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. -- cgit v1.2.3 From 918b9cc6a605ee4701d77f73e93a62f04c2345b0 Mon Sep 17 00:00:00 2001 From: Gil Gonçalves Date: Mon, 24 Nov 2014 08:54:07 +0000 Subject: Added missing import in tutorial snippet --- docs/tutorial/6-viewsets-and-routers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/tutorial/6-viewsets-and-routers.md b/docs/tutorial/6-viewsets-and-routers.md index cf37a260..3fad509a 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({ -- cgit v1.2.3 From 83e556ba1e129fac28a74b0a2784e4728f491a41 Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 25 Nov 2014 12:37:20 +0100 Subject: Missing quotes on validate_score example --- docs/topics/3.0-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 694ad8a5..6a662326 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 -- cgit v1.2.3 From 2e726e22a394347b7337eb38a2a3a1b0ccde88bc Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 11:42:43 +0000 Subject: 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 ++-- 7 files changed, 34 insertions(+), 31 deletions(-) (limited to 'docs') diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index e61dcfa9..8a99abb9 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 72a4af64..a50b5240 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 87425ed1..d659e17a 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. -## .QUERY_PARAMS +## .DATA and .FILES -`request.QUERY_PARAMS` is a more correctly named synonym for `request.GET`. +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 -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 96d715ea..ba470f8b 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 9030e3ee..f60d4a47 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 136b0135..f377c712 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 382f078a..0a9ea3f1 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) -- cgit v1.2.3 From a3c0c8fb90f084d494cdf46551c8a6228ca5ffd0 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 12:04:35 +0000 Subject: Add missing period. --- docs/api-guide/generic-views.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/api-guide/generic-views.md b/docs/api-guide/generic-views.md index 49a5e58f..16ae8c55 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 -- cgit v1.2.3 From c4dff54ecc487b192d17763e48adc0bfcaa620d3 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 12:04:46 +0000 Subject: Docs on ValidationError --- docs/api-guide/exceptions.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'docs') diff --git a/docs/api-guide/exceptions.md b/docs/api-guide/exceptions.md index 8a99abb9..33eb74c8 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 -- cgit v1.2.3 From 83281254d1a4685535ab877fb9f9affeb93578bd Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 12:06:27 +0000 Subject: Hypenate 'un-coerced' --- docs/topics/3.0-announcement.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md index 694ad8a5..24b0923f 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. -- cgit v1.2.3 From 3b40f8c1c110ef44d112a705652b9ef57a36d9c9 Mon Sep 17 00:00:00 2001 From: Tom Christie Date: Tue, 25 Nov 2014 12:15:48 +0000 Subject: Drop 0.x release notes --- docs/topics/release-notes.md | 122 ++----------------------------------------- 1 file changed, 4 insertions(+), 118 deletions(-) (limited to 'docs') diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 88780c3f..efc49ba1 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 -- cgit v1.2.3 From 3a648b9f0b0aa9a955065b96ea1a03376816ec00 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Thu, 9 Oct 2014 10:14:04 +0100 Subject: Migrate documentation to MkDocs proper --- docs/404.html | 201 ------------------------------------------ docs/template.html | 239 ------------------------------------------------- docs/theme/404.html | 201 ++++++++++++++++++++++++++++++++++++++++++ docs/theme/base.html | 244 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 445 insertions(+), 440 deletions(-) delete mode 100644 docs/404.html delete mode 100644 docs/template.html create mode 100644 docs/theme/404.html create mode 100644 docs/theme/base.html (limited to 'docs') diff --git a/docs/404.html b/docs/404.html deleted file mode 100644 index 864247e7..00000000 --- a/docs/404.html +++ /dev/null @@ -1,201 +0,0 @@ - - - - - Django REST framework - 404 - Page not found - - - - - - - - - - - - - - - - - - - -
    - - - -
    -
    - - - - -
    -
    -

    404

    -

    Page not found

    -

    Try the homepage, or search the documentation.

    -
    -
    -
    -
    - -
    -
    - - - - - - - - - - diff --git a/docs/template.html b/docs/template.html deleted file mode 100644 index f36cffc6..00000000 --- a/docs/template.html +++ /dev/null @@ -1,239 +0,0 @@ - - - - - {{ title }} - - - - - - - - - - - - - - - - - - - - -
    - - - -
    -
    - - - - -
    - -
    - -
    - - -
    -
    - -
    - {{ content }} -
    -
    -
    -
    - -
    -
    - - - - - - - - - - - diff --git a/docs/theme/404.html b/docs/theme/404.html new file mode 100644 index 00000000..864247e7 --- /dev/null +++ b/docs/theme/404.html @@ -0,0 +1,201 @@ + + + + + Django REST framework - 404 - Page not found + + + + + + + + + + + + + + + + + + + +
    + + + +
    +
    + + + + +
    +
    +

    404

    +

    Page not found

    +

    Try the homepage, or search the documentation.

    +
    +
    +
    +
    + +
    +
    + + + + + + + + + + diff --git a/docs/theme/base.html b/docs/theme/base.html new file mode 100644 index 00000000..45e19cf3 --- /dev/null +++ b/docs/theme/base.html @@ -0,0 +1,244 @@ + + + + + {{ title }} + + + + + + + + + + + + + + + + + + + + +
    + + + +
    +
    + + + + +
    + +
    + +
    + + +
    +
    + +
    + {{ content }} +
    +
    +
    +
    + +
    +
    + + + + + + + + + + + -- cgit v1.2.3 From 261c4ce9aff6725a4e069cf45d0f57ec97a27bdb Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Wed, 22 Oct 2014 21:13:40 +0100 Subject: Remove redundant requirements file --- docs/requirements.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 docs/requirements.txt (limited to 'docs') diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index a91fb978..00000000 --- a/docs/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -markdown>=2.1.0 -- cgit v1.2.3 From 84d9c4b46eacda54b31a3779c623554758f4dccc Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Wed, 22 Oct 2014 21:36:11 +0100 Subject: Change site_dir to html to match mkdocs.py --- docs/topics/contributing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'docs') diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md index 52f6e287..5809af65 100644 --- a/docs/topics/contributing.md +++ b/docs/topics/contributing.md @@ -135,15 +135,15 @@ There are many great Markdown editors that make working with the documentation r ## Building the documentation -To build the documentation, simply run the `mkdocs.py` script. +To build the documentation, simply install MkDocs with `pip install mkdocs` and then run the following command. - ./mkdocs.py + mkdocs build This will build the html output into the `html` directory. -You can build the documentation and open a preview in a browser window by using the `-p` flag. +You can build the documentation and open a preview in a browser window by using the `serve` command. - ./mkdocs.py -p + mkdocs serve ## Language style -- cgit v1.2.3 From 2ddef2d3263b2187fd502e42eaaecc3a4f0ad2b2 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Thu, 23 Oct 2014 14:10:23 +0100 Subject: Drop simply from the mkdocs install lines It is still simple tho', honest ;) --- docs/topics/contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/topics/contributing.md b/docs/topics/contributing.md index 5809af65..c7991a0f 100644 --- a/docs/topics/contributing.md +++ b/docs/topics/contributing.md @@ -135,7 +135,7 @@ There are many great Markdown editors that make working with the documentation r ## Building the documentation -To build the documentation, simply install MkDocs with `pip install mkdocs` and then run the following command. +To build the documentation, install MkDocs with `pip install mkdocs` and then run the following command. mkdocs build -- cgit v1.2.3 From d6b203f013890cc25f5454696805bc85c1ca2482 Mon Sep 17 00:00:00 2001 From: José Padilla Date: Tue, 28 Oct 2014 17:40:29 -0400 Subject: Use page_title instead of title in docs theme --- docs/theme/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/theme/base.html b/docs/theme/base.html index 45e19cf3..943a60e4 100644 --- a/docs/theme/base.html +++ b/docs/theme/base.html @@ -2,7 +2,7 @@ - {{ title }} + {{ page_title }} -- cgit v1.2.3 From 18712a5cbcbb25d935b93baac589cacfe1cbb754 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Wed, 29 Oct 2014 20:54:20 +0000 Subject: Fix the Navigation style --- docs/theme/base.html | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'docs') diff --git a/docs/theme/base.html b/docs/theme/base.html index 943a60e4..e5ad4377 100644 --- a/docs/theme/base.html +++ b/docs/theme/base.html @@ -188,15 +188,12 @@ a.fusion-poweredby {
    +
    -- cgit v1.2.3 From 49a493f61f4d5b4447d139d189d78995e65231b2 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Wed, 29 Oct 2014 20:54:31 +0000 Subject: Bring back the promo section --- docs/theme/base.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'docs') diff --git a/docs/theme/base.html b/docs/theme/base.html index e5ad4377..7ca7de14 100644 --- a/docs/theme/base.html +++ b/docs/theme/base.html @@ -191,7 +191,8 @@ a.fusion-poweredby {
  • {{ toc_item.title }}
  • {% endfor %}
    - {{ ad_block }} +
    +
    -- cgit v1.2.3 From 3c49b9fe4648d6fb291f0d34bf2e9ef4b72d45be Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Wed, 29 Oct 2014 21:03:00 +0000 Subject: Add next and previous page. --- docs/theme/base.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'docs') diff --git a/docs/theme/base.html b/docs/theme/base.html index 7ca7de14..0f876cc1 100644 --- a/docs/theme/base.html +++ b/docs/theme/base.html @@ -57,8 +57,8 @@ a.fusion-poweredby {