aboutsummaryrefslogtreecommitdiffstats
path: root/docs/tutorial
diff options
context:
space:
mode:
authorTom Christie2013-03-18 21:03:05 +0000
committerTom Christie2013-03-18 21:03:05 +0000
commit74fb366c595db87bb71baeffcacfb7d2482e3a18 (patch)
tree2e28cb52542742f32cdd3fbeb625f7f59cba0a3f /docs/tutorial
parent4c6396108704d38f534a16577de59178b1d0df3b (diff)
parent034c4ce4081dd6d15ea47fb8318754321a3faf0c (diff)
downloaddjango-rest-framework-74fb366c595db87bb71baeffcacfb7d2482e3a18.tar.bz2
Merge branch 'master' into resources-routers
Diffstat (limited to 'docs/tutorial')
-rw-r--r--docs/tutorial/1-serialization.md63
-rw-r--r--docs/tutorial/2-requests-and-responses.md38
-rw-r--r--docs/tutorial/3-class-based-views.md2
-rw-r--r--docs/tutorial/4-authentication-and-permissions.md48
-rw-r--r--docs/tutorial/5-relationships-and-hyperlinked-apis.md12
5 files changed, 110 insertions, 53 deletions
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index d3ada9e3..205ee7e0 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -4,11 +4,11 @@
This tutorial will cover creating a simple pastebin code highlighting Web API. Along the way it will introduce the various components that make up REST framework, and give you a comprehensive understanding of how everything fits together.
-The tutorial is fairly in-depth, so you should probably get a cookie and a cup of your favorite brew before getting started.<!-- If you just want a quick overview, you should head over to the [quickstart] documentation instead. -->
+The tutorial is fairly in-depth, so you should probably get a cookie and a cup of your favorite brew before getting started. If you just want a quick overview, you should head over to the [quickstart] documentation instead.
---
-**Note**: The code for this tutorial is available in the [tomchristie/rest-framework-tutorial][repo] repository on GitHub. As pieces of code are introduced, they are committed to this repository. The completed implementation is also online as a sandbox version for testing, [available here][sandbox].
+**Note**: The code for this tutorial is available in the [tomchristie/rest-framework-tutorial][repo] repository on GitHub. The completed implementation is also online as a sandbox version for testing, [available here][sandbox].
---
@@ -86,7 +86,7 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
- title = models.CharField(max_length=100, default='')
+ title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES,
@@ -109,7 +109,7 @@ The first thing we need to get started on our Web API is provide a way of serial
from django.forms import widgets
from rest_framework import serializers
- from snippets import models
+ from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
@@ -119,26 +119,30 @@ The first thing we need to get started on our Web API is provide a way of serial
code = serializers.CharField(widget=widgets.Textarea,
max_length=100000)
linenos = serializers.BooleanField(required=False)
- language = serializers.ChoiceField(choices=models.LANGUAGE_CHOICES,
+ language = serializers.ChoiceField(choices=LANGUAGE_CHOICES,
default='python')
- style = serializers.ChoiceField(choices=models.STYLE_CHOICES,
+ style = serializers.ChoiceField(choices=STYLE_CHOICES,
default='friendly')
def restore_object(self, attrs, instance=None):
"""
- Create or update a new snippet instance.
+ Create or update a new snippet instance, given a dictionary
+ of deserialized field values.
+
+ Note that if we don't define this method, then deserializing
+ data will simply return a dictionary of items.
"""
if instance:
# Update existing instance
- instance.title = attrs['title']
- instance.code = attrs['code']
- instance.linenos = attrs['linenos']
- instance.language = attrs['language']
- instance.style = attrs['style']
+ 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 models.Snippet(**attrs)
+ return Snippet(**attrs)
The first part of serializer class defines the fields that get serialized/deserialized. The `restore_object` method defines how fully fledged instances get created when deserializing data.
@@ -150,13 +154,16 @@ Before we go any further we'll familiarize ourselves with using our new Serializ
python manage.py shell
-Okay, once we've got a few imports out of the way, let's create a code snippet to work with.
+Okay, once we've got a few imports out of the way, let's create a couple of code snippets to work with.
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
+ snippet = Snippet(code='foo = "bar"\n')
+ snippet.save()
+
snippet = Snippet(code='print "hello, world"\n')
snippet.save()
@@ -164,13 +171,13 @@ We've now got a few snippet instances to play with. Let's take a look at serial
serializer = SnippetSerializer(snippet)
serializer.data
- # {'pk': 1, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
+ # {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
At this point we've translated the model instance into python native datatypes. To finalize the serialization process we render the data into `json`.
content = JSONRenderer().render(serializer.data)
content
- # '{"pk": 1, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
+ # '{"pk": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
Deserialization is similar. First we parse a stream into python native datatypes...
@@ -189,6 +196,12 @@ Deserialization is similar. First we parse a stream into python native datatype
Notice how similar the API is to working with forms. The similarity should become even more apparent when we start writing views that use our serializer.
+We can also serialize querysets instead of model instances. To do so we simply add a `many=True` flag to the serializer arguments.
+
+ serializer = SnippetSerializer(Snippet.objects.all(), many=True)
+ serializer.data
+ # [{'pk': 1, 'title': u'', 'code': u'foo = "bar"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}, {'pk': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}]
+
## Using ModelSerializers
Our `SnippetSerializer` class is replicating a lot of information that's also contained in the `Snippet` model. It would be nice if we could keep out code a bit more concise.
@@ -237,7 +250,7 @@ The root of our API is going to be a view that supports listing all the existing
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
- serializer = SnippetSerializer(snippets)
+ serializer = SnippetSerializer(snippets, many=True)
return JSONResponse(serializer.data)
elif request.method == 'POST':
@@ -295,11 +308,11 @@ It's worth noting that there are a couple of edge cases we're not dealing with p
Now we can start up a sample server that serves our snippets.
-Quit out of the shell
+Quit out of the shell...
quit()
-and start up Django's development server
+...and start up Django's development server.
python manage.py runserver
@@ -312,19 +325,19 @@ and start up Django's development server
In another terminal window, we can test the server.
-We can get a list of all of the snippets (we only have one at the moment)
+We can get a list of all of the snippets.
curl http://127.0.0.1:8000/snippets/
- [{"id": 1, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}]
+ [{"id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "friendly"}, {"id": 2, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}]
-or we can get a particular snippet by referencing its id
+Or we can get a particular snippet by referencing its id.
- curl http://127.0.0.1:8000/snippets/1/
+ curl http://127.0.0.1:8000/snippets/2/
- {"id": 1, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}
+ {"id": 2, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}
-Similarly, you can have the same json displayed by referencing these URLs from your favorite web browser.
+Similarly, you can have the same json displayed by visiting these URLs in a web browser.
## Where are we now
diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md
index 340ea28e..63cee3a6 100644
--- a/docs/tutorial/2-requests-and-responses.md
+++ b/docs/tutorial/2-requests-and-responses.md
@@ -51,7 +51,7 @@ We don't need our `JSONResponse` class anymore, so go ahead and delete that. On
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
- serializer = SnippetSerializer(snippets)
+ serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
@@ -75,11 +75,11 @@ Here is the view for an individual snippet.
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
-
+
if request.method == 'GET':
serializer = SnippetSerializer(snippet)
return Response(serializer.data)
-
+
elif request.method == 'PUT':
serializer = SnippetSerializer(snippet, data=request.DATA)
if serializer.is_valid():
@@ -126,13 +126,41 @@ We don't necessarily need to add these extra url patterns in, but it gives us a
Go ahead and test the API from the command line, as we did in [tutorial part 1][tut-1]. Everything is working pretty similarly, although we've got some nicer error handling if we send invalid requests.
-**TODO: Describe using accept headers, content-type headers, and format suffixed URLs**
+We can get a list of all of the snippets, as before.
+
+ curl http://127.0.0.1:8000/snippets/
+
+ [{"id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "friendly"}, {"id": 2, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}]
+
+We can control the format of the response that we get back, either by using the `Accept` header:
+
+ curl http://127.0.0.1:8000/snippets/ -H 'Accept: application/json' # Request JSON
+ curl http://127.0.0.1:8000/snippets/ -H 'Accept: text/html' # Request HTML
+
+Or by appending a format suffix:
+
+ curl http://127.0.0.1:8000/snippets/.json # JSON suffix
+ curl http://127.0.0.1:8000/snippets/.api # Browseable API suffix
+
+Similarly, we can control the format of the request that we send, using the `Content-Type` header.
+
+ # POST using form data
+ curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123"
+
+ {"id": 3, "title": "", "code": "123", "linenos": false, "language": "python", "style": "friendly"}
+
+ # POST using JSON
+ curl -X POST http://127.0.0.1:8000/snippets/ -d '{"code": "print 456"}' -H "Content-Type: application/json"
+
+ {"id": 4, "title": "", "code": "print 456", "linenos": true, "language": "python", "style": "friendly"}
Now go and open the API in a web browser, by visiting [http://127.0.0.1:8000/snippets/][devserver].
### Browsability
-Because the API chooses a return format based on what the client asks for, it will, by default, return an HTML-formatted representation of the resource when that resource is requested by a browser. This allows for the API to be easily browsable and usable by humans.
+Because the API chooses the content type of the response based on the client request, it will, by default, return an HTML-formatted representation of the resource when that resource is requested by a web browser. This allows for the API to return a fully web-browsable HTML representation.
+
+Having a web-browseable API is a huge usability win, and makes developing and using your API much easier. It also dramatically lowers the barrier-to-entry for other developers wanting to inspect and work with your API.
See the [browsable api][browseable-api] topic for more information about the browsable API feature and how to customize it.
diff --git a/docs/tutorial/3-class-based-views.md b/docs/tutorial/3-class-based-views.md
index 290ea5e9..e05017c5 100644
--- a/docs/tutorial/3-class-based-views.md
+++ b/docs/tutorial/3-class-based-views.md
@@ -20,7 +20,7 @@ We'll start by rewriting the root view as a class based view. All this involves
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
- serializer = SnippetSerializer(snippets)
+ serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md
index f6daebb7..3ee755a2 100644
--- a/docs/tutorial/4-authentication-and-permissions.md
+++ b/docs/tutorial/4-authentication-and-permissions.md
@@ -22,7 +22,7 @@ We'd also need to make sure that when the model is saved, that we populate the h
We'll need some extra imports:
from pygments.lexers import get_lexer_by_name
- from pygments.formatters import HtmlFormatter
+ from pygments.formatters.html import HtmlFormatter
from pygments import highlight
And now we can add a `.save()` method to our model class:
@@ -54,8 +54,10 @@ You might also want to create a few different users, to use for testing the API.
Now that we've got some users to work with, we'd better add representations of those users to our API. Creating a new serializer is easy:
+ from django.contrib.auth.models import User
+
class UserSerializer(serializers.ModelSerializer):
- snippets = serializers.ManyPrimaryKeyRelatedField()
+ snippets = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = User
@@ -70,14 +72,14 @@ We'll also add a couple of views. We'd like to just use read-only views for the
serializer_class = UserSerializer
- class UserInstance(generics.RetrieveAPIView):
+ class UserDetail(generics.RetrieveAPIView):
model = User
serializer_class = UserSerializer
Finally we need to add those views into the API, by referencing them from the URL conf.
url(r'^users/$', views.UserList.as_view()),
- url(r'^users/(?P<pk>[0-9]+)/$', views.UserInstance.as_view()),
+ url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
## Associating Snippets with Users
@@ -102,8 +104,6 @@ This field is doing something quite interesting. The `source` argument controls
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.
-**TODO: Explain the SessionAuthentication and BasicAuthentication classes, and demonstrate using HTTP basic authentication with curl requests**
-
## Adding required permissions to views
Now that code snippets are associated with users, we want to make sure that only authenticated users are able to create, update and delete code snippets.
@@ -118,8 +118,6 @@ Then, add the following property to **both** the `SnippetList` and `SnippetDetai
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
-**TODO: Now that the permissions are restricted, demonstrate using HTTP basic authentication with curl requests**
-
## Adding login to the Browseable API
If you open a browser and navigate to the browseable API at the moment, you'll find that you're no longer able to create new code snippets. In order to do so we'd need to be able to login as a user.
@@ -159,12 +157,9 @@ In the snippets app, create a new file, `permissions.py`
Custom permission to only allow owners of an object to edit it.
"""
- def has_permission(self, request, view, obj=None):
- # Skip the check unless this is an object-level test
- if obj is None:
- return True
-
- # Read permissions are allowed to any request
+ def has_object_permission(self, request, view, obj):
+ # Read permissions are allowed to any request,
+ # so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
@@ -182,10 +177,31 @@ Make sure to also import the `IsOwnerOrReadOnly` class.
Now, if you open a browser again, you find that the 'DELETE' and 'PUT' actions only appear on a snippet instance endpoint if you're logged in as the same user that created the code snippet.
+## Authenticating with the API
+
+Because we now have a set of permissions on the API, we need to authenticate our requests to it if we want to edit any snippets. We havn't set up any [authentication classes][authentication], so the defaults are currently applied, which are `SessionAuthentication` and `BasicAuthentication`.
+
+When we interact with the API through the web browser, we can login, and the browser session will then provide the required authentication for the requests.
+
+If we're interacting with the API programmatically we need to explicitly provide the authentication credentials on each request.
+
+If we try to create a snippet without authenticating, we'll get an error:
+
+ curl -i -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123"
+
+ {"detail": "Authentication credentials were not provided."}
+
+We can make a successful request by including the username and password of one of the users we created earlier.
+
+ curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 789" -u tom:password
+
+ {"id": 5, "owner": "tom", "title": "foo", "code": "print 789", "linenos": false, "language": "python", "style": "friendly"}
+
## Summary
We've now got a fairly fine-grained set of permissions on our Web API, and end points for users of the system and for the code snippets that they have created.
-In [part 5][tut-5] of the tutorial we'll look at how we can tie everything together by creating an HTML endpoint for our hightlighted snippets, and improve the cohesion of our API by using hyperlinking for the relationships within the system.
+In [part 5][tut-5] of the tutorial we'll look at how we can tie everything together by creating an HTML endpoint for our highlighted snippets, and improve the cohesion of our API by using hyperlinking for the relationships within the system.
-[tut-5]: 5-relationships-and-hyperlinked-apis.md \ No newline at end of file
+[authentication]: ../api-guide/authentication.md
+[tut-5]: 5-relationships-and-hyperlinked-apis.md
diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
index 27898f7b..a702a09d 100644
--- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md
+++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
@@ -70,8 +70,8 @@ The `HyperlinkedModelSerializer` has the following differences from `ModelSerial
* It does not include the `pk` field by default.
* It includes a `url` field, using `HyperlinkedIdentityField`.
-* Relationships use `HyperlinkedRelatedField` and `ManyHyperlinkedRelatedField`,
- instead of `PrimaryKeyRelatedField` and `ManyPrimaryKeyRelatedField`.
+* Relationships use `HyperlinkedRelatedField`,
+ instead of `PrimaryKeyRelatedField`.
We can easily re-write our existing serializers to use hyperlinking.
@@ -86,7 +86,7 @@ We can easily re-write our existing serializers to use hyperlinking.
class UserSerializer(serializers.HyperlinkedModelSerializer):
- snippets = serializers.ManyHyperlinkedRelatedField(view_name='snippet-detail')
+ snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail')
class Meta:
model = User
@@ -123,7 +123,7 @@ After adding all those names into our URLconf, our final `'urls.py'` file should
views.UserList.as_view(),
name='user-list'),
url(r'^users/(?P<pk>[0-9]+)/$',
- views.UserInstance.as_view(),
+ views.UserDetail.as_view(),
name='user-detail')
))
@@ -165,7 +165,7 @@ We've reached the end of our tutorial. If you want to get more involved in the
* Contribute on [GitHub][github] by reviewing and submitting issues, and making pull requests.
* Join the [REST framework discussion group][group], and help build the community.
-* [Follow the author on Twitter][twitter] and say hi.
+* Follow [the author][twitter] on Twitter and say hi.
**Now go build awesome things.**
@@ -173,4 +173,4 @@ We've reached the end of our tutorial. If you want to get more involved in the
[sandbox]: http://restframework.herokuapp.com/
[github]: https://github.com/tomchristie/django-rest-framework
[group]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
-[twitter]: https://twitter.com/_tomchristie \ No newline at end of file
+[twitter]: https://twitter.com/_tomchristie