aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/api-guide/fields.md6
-rw-r--r--docs/api-guide/serializers.md37
-rw-r--r--docs/api-guide/viewsets.md3
-rw-r--r--docs/index.md2
-rw-r--r--docs/topics/3.0-announcement.md12
-rw-r--r--docs/topics/release-notes.md10
-rw-r--r--docs/tutorial/1-serialization.md94
-rw-r--r--docs/tutorial/2-requests-and-responses.md57
-rw-r--r--docs/tutorial/4-authentication-and-permissions.md24
-rw-r--r--docs/tutorial/5-relationships-and-hyperlinked-apis.md2
-rw-r--r--docs/tutorial/quickstart.md32
11 files changed, 212 insertions, 67 deletions
diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md
index aa5cc84e..e4ef1d4a 100644
--- a/docs/api-guide/fields.md
+++ b/docs/api-guide/fields.md
@@ -310,6 +310,9 @@ Used by `ModelSerializer` to automatically generate fields if the corresponding
**Signature:** `ChoiceField(choices)`
- `choices` - A list of valid values, or a list of `(key, display_name)` tuples.
+- `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`.
+
+Both the `allow_blank` and `allow_null` are valid options on `ChoiceField`, although it is highly recommended that you only use one and not both. `allow_blank` should be preferred for textual choices, and `allow_null` should be preferred for numeric or other non-textual choices.
## MultipleChoiceField
@@ -318,6 +321,9 @@ A field that can accept a set of zero, one or many values, chosen from a limited
**Signature:** `MultipleChoiceField(choices)`
- `choices` - A list of valid values, or a list of `(key, display_name)` tuples.
+- `allow_blank` - If set to `True` then the empty string should be considered a valid value. If set to `False` then the empty string is considered invalid and will raise a validation error. Defaults to `False`.
+
+As with `ChoiceField`, both the `allow_blank` and `allow_null` options are valid, although it is highly recommended that you only use one and not both. `allow_blank` should be preferred for textual choices, and `allow_null` should be preferred for numeric or other non-textual choices.
---
diff --git a/docs/api-guide/serializers.md b/docs/api-guide/serializers.md
index 1779c863..5fe6b4c2 100644
--- a/docs/api-guide/serializers.md
+++ b/docs/api-guide/serializers.md
@@ -104,7 +104,7 @@ If your object instances correspond to Django models you'll also want to ensure
instance.created = validated_data.get('created', instance.created)
instance.save()
return instance
-
+
Now when deserializing data, we can call `.save()` to return an object instance, based on the validated data.
comment = serializer.save()
@@ -113,7 +113,7 @@ Calling `.save()` will either create a new instance, or update an existing insta
# .save() will create a new instance.
serializer = CommentSerializer(data=data)
-
+
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
@@ -140,7 +140,7 @@ For example:
class ContactForm(serializers.Serializer):
email = serializers.EmailField()
message = serializers.CharField()
-
+
def save(self):
email = self.validated_data['email']
message = self.validated_data['message']
@@ -230,7 +230,7 @@ Serializer classes can also include reusable validators that are applied to the
name = serializers.CharField()
room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
date = serializers.DateField()
-
+
class Meta:
# Each room only has one event per day.
validators = UniqueTogetherValidator(
@@ -326,9 +326,9 @@ Here's an example for an `update()` method on our previous `UserSerializer` clas
# would need to be handled.
profile = instance.profile
- user.username = validated_data.get('username', instance.username)
- user.email = validated_data.get('email', instance.email)
- user.save()
+ instance.username = validated_data.get('username', instance.username)
+ instance.email = validated_data.get('email', instance.email)
+ instance.save()
profile.is_premium_member = profile_data.get(
'is_premium_member',
@@ -340,7 +340,7 @@ Here's an example for an `update()` method on our previous `UserSerializer` clas
)
profile.save()
- return user
+ return instance
Because the behavior of nested creates and updates can be ambiguous, and may require complex dependancies between related models, REST framework 3 requires you to always write these methods explicitly. The default `ModelSerializer` `.create()` and `.update()` methods do not include support for writable nested representations.
@@ -448,7 +448,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the
id = IntegerField(label='ID', read_only=True)
name = CharField(allow_blank=True, max_length=100, required=False)
owner = PrimaryKeyRelatedField(queryset=User.objects.all())
-
+
## Specifying which fields should be included
If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
@@ -505,6 +505,21 @@ This option should be a list or tuple of field names, and is declared as follows
Model fields which have `editable=False` set, and `AutoField` fields will be set to read-only by default, and do not need to be added to the `read_only_fields` option.
+---
+
+**Note**: There is a special-case where a read-only field is part of a `unique_together` constraint at the model level. In this case the field is required by the serializer class in order to validate the constraint, but should also not be editable by the user.
+
+The right way to deal with this is to specify the field explicitly on the serializer, providing both the `read_only=True` and `default=…` keyword arguments.
+
+One example of this is a read-only relation to the currently authenticated `User` which is `unique_together` with another identifier. In this case you would declare the user field like so:
+
+ user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
+
+Please review the [Validators Documentation](/api-guide/validators/) for details on the [UniqueTogetherValidator](/api-guide/validators/#uniquetogethervalidator) and [CurrentUserDefault](/api-guide/validators/#currentuserdefault) classes.
+
+---
+
+
## Specifying additional keyword arguments for fields.
There is also a shortcut allowing you to specify arbitrary additional keyword arguments on fields, using the `extra_kwargs` option. Similarly to `read_only_fields` this means you do not need to explicitly declare the field on the serializer.
@@ -516,7 +531,7 @@ This option is a dictionary, mapping field names to a dictionary of keyword argu
model = User
fields = ('email', 'username', 'password')
extra_kwargs = {'password': {'write_only': True}}
-
+
def create(self, validated_data):
user = User(
email=validated_data['email'],
@@ -656,7 +671,7 @@ To support multiple updates you'll need to do so explicitly. When writing your m
* How do you determine which instance should be updated for each item in the list of data?
* How should insertions be handled? Are they invalid, or do they create new objects?
* How should removals be handled? Do they imply object deletion, or removing a relationship? Should they be silently ignored, or are they invalid?
-* How should ordering be handled? Does changing the position of two items imply any state change or is it ignored?
+* How should ordering be handled? Does changing the position of two items imply any state change or is it ignored?
Here's an example of how you might choose to implement multiple updates:
diff --git a/docs/api-guide/viewsets.md b/docs/api-guide/viewsets.md
index 28186c64..3e37cef8 100644
--- a/docs/api-guide/viewsets.md
+++ b/docs/api-guide/viewsets.md
@@ -201,6 +201,8 @@ Note that you can use any of the standard attributes or method overrides provide
def get_queryset(self):
return self.request.user.accounts.all()
+Note however that upon removal of the `queryset` property from your `ViewSet`, any associated [router][routers] will be unable to derive the base_name of your Model automatically, and so you you will have to specify the `base_name` kwarg as part of your [router registration][routers].
+
Also note that although this class provides the complete set of create/list/retrieve/update/destroy actions by default, you can restrict the available operations by using the standard permission classes.
## ReadOnlyModelViewSet
@@ -243,3 +245,4 @@ To create a base viewset class that provides `create`, `list` and `retrieve` ope
By creating your own base `ViewSet` classes, you can provide common behavior that can be reused in multiple viewsets across your API.
[cite]: http://guides.rubyonrails.org/routing.html
+[routers]: routers.md
diff --git a/docs/index.md b/docs/index.md
index e0ba2332..52e42fc9 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -4,7 +4,7 @@
<a href="https://twitter.com/share" class="twitter-share-button" data-url="django-rest-framework.org" data-text="Checking out the totally awesome Django REST framework! http://www.django-rest-framework.org" data-count="none"></a>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="http://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
-<img src="https://secure.travis-ci.org/tomchristie/django-rest-framework.png?branch=master" class="travis-build-image">
+<img src="https://secure.travis-ci.org/tomchristie/django-rest-framework.svg?branch=master" class="travis-build-image">
</p>
---
diff --git a/docs/topics/3.0-announcement.md b/docs/topics/3.0-announcement.md
index 8791ad08..8fa86b9a 100644
--- a/docs/topics/3.0-announcement.md
+++ b/docs/topics/3.0-announcement.md
@@ -28,10 +28,18 @@ Notable features of this new release include:
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.
+---
+
+#### REST framework: Under the hood.
+
+This talk from the [Django: Under the Hood](http://www.djangounderthehood.com/) event in Amsterdam, Nov 2014, gives some good background context on the design decisions behind 3.0.
+
+<iframe width="560" height="315" src="//www.youtube.com/embed/3cSsbe-tA0E" frameborder="0" allowfullscreen></iframe>
---
+*Below is an in-depth guide to the API changes and migration notes for 3.0.*
+
## Request objects
#### The `.data` and `.query_params` properties.
@@ -931,6 +939,7 @@ The default JSON renderer will return float objects for un-coerced `Decimal` ins
* 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.
+* Some of the default validation error messages were rewritten and might no longer be pre-translated. You can still [create language files with Django][django-localization] if you wish to localize them.
---
@@ -952,3 +961,4 @@ You can follow development on the GitHub site, where we use [milestones to indic
[kickstarter]: http://kickstarter.com/projects/tomchristie/django-rest-framework-3
[sponsors]: http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors
[mixins.py]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py
+[django-localization]: https://docs.djangoproject.com/en/dev/topics/i18n/translation/#localization-how-to-create-language-files
diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md
index 19dfbb98..550fdf75 100644
--- a/docs/topics/release-notes.md
+++ b/docs/topics/release-notes.md
@@ -38,6 +38,16 @@ You can determine your currently installed version using `pip freeze`:
---
+## 3.0.x series
+
+### 3.0.0
+
+**Date**: 1st December 2014
+
+For full details see the [3.0 release announcement](3.0-announcement.md).
+
+---
+
## 2.4.x series
### 2.4.4
diff --git a/docs/tutorial/1-serialization.md b/docs/tutorial/1-serialization.md
index a3c19858..dea43cc0 100644
--- a/docs/tutorial/1-serialization.md
+++ b/docs/tutorial/1-serialization.md
@@ -16,7 +16,6 @@ The tutorial is fairly in-depth, so you should probably get a cookie and a cup o
Before we do anything else we'll create a new virtual environment, using [virtualenv]. This will make sure our package configuration is kept nicely isolated from any other projects we're working on.
- :::bash
virtualenv env
source env/bin/activate
@@ -75,12 +74,8 @@ For the purposes of this tutorial we're going to start by creating a simple `Sni
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
- language = models.CharField(choices=LANGUAGE_CHOICES,
- default='python',
- max_length=100)
- style = models.CharField(choices=STYLE_CHOICES,
- default='friendly',
- max_length=100)
+ language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
+ style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ('created',)
@@ -101,30 +96,27 @@ 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.IntegerField(read_only=True)
- title = serializers.CharField(required=False,
- max_length=100)
+ title = serializers.CharField(required=False, allow_blank=True, max_length=100)
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')
+ language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
+ style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
- def create(self, validated_attrs):
+ def create(self, validated_data):
"""
Create and return a new `Snippet` instance, given the validated data.
"""
- return Snippet.objects.create(**validated_attrs)
+ return Snippet.objects.create(**validated_data)
- def update(self, instance, validated_attrs):
+ def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
- 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.title = validated_data.get('title', instance.title)
+ instance.code = validated_data.get('code', instance.code)
+ instance.linenos = validated_data.get('linenos', instance.linenos)
+ instance.language = validated_data.get('language', instance.language)
+ instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
@@ -181,7 +173,9 @@ Deserialization is similar. First we parse a stream into Python native datatype
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
- serializer.object
+ serializer.validated_data
+ # OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
+ serializer.save()
# <Snippet: Snippet object>
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.
@@ -210,7 +204,7 @@ One nice property that serializers have is that you can inspect all the fields i
>>> from snippets.serializers import SnippetSerializer
>>> serializer = SnippetSerializer()
- >>> print repr(serializer) # In python 3 use `print(repr(serializer))`
+ >>> print(repr(serializer))
SnippetSerializer():
id = IntegerField(label='ID', read_only=True)
title = CharField(allow_blank=True, max_length=100, required=False)
@@ -301,7 +295,7 @@ 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 django.conf.urls import url
from snippets import views
urlpatterns = [
@@ -332,17 +326,51 @@ Quit out of the shell...
In another terminal window, we can test the server.
-We can get a list of all of the snippets.
-
- 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 test our API using using [curl][curl] or [httpie][httpie]. Httpie is a user friendly http client that's written in Python. Let's install that.
+
+You can install httpie using pip:
+
+ pip install httpie
+
+Finally, we can get a list of all of the snippets:
+
+ http http://127.0.0.1:8000/snippets/
+
+ HTTP/1.1 200 OK
+ ...
+ [
+ {
+ "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/2/
+ http http://127.0.0.1:8000/snippets/2/
- {"id": 2, "title": "", "code": "print \"hello, world\"\n", "linenos": false, "language": "python", "style": "friendly"}
+ HTTP/1.1 200 OK
+ ...
+ {
+ "id": 2,
+ "title": "",
+ "code": "print \"hello, world\"\n",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ }
Similarly, you can have the same json displayed by visiting these URLs in a web browser.
@@ -359,3 +387,5 @@ We'll see how we can start to improve things in [part 2 of the tutorial][tut-2].
[sandbox]: http://restframework.herokuapp.com/
[virtualenv]: http://www.virtualenv.org/en/latest/index.html
[tut-2]: 2-requests-and-responses.md
+[httpie]: https://github.com/jakubroztocil/httpie#installation
+[curl]: http://curl.haxx.se
diff --git a/docs/tutorial/2-requests-and-responses.md b/docs/tutorial/2-requests-and-responses.md
index f377c712..49e96d03 100644
--- a/docs/tutorial/2-requests-and-responses.md
+++ b/docs/tutorial/2-requests-and-responses.md
@@ -127,31 +127,64 @@ Go ahead and test the API from the command line, as we did in [tutorial part 1][
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"}]
+ http http://127.0.0.1:8000/snippets/
+
+ HTTP/1.1 200 OK
+ ...
+ [
+ {
+ "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
+ http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON
+ http http://127.0.0.1:8000/snippets/ 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 # Browsable API suffix
+ http http://127.0.0.1:8000/snippets/.json # JSON suffix
+ http http://127.0.0.1:8000/snippets/.api # Browsable 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"
+ http --form POST http://127.0.0.1:8000/snippets/ code="print 123"
- {"id": 3, "title": "", "code": "print 123", "linenos": false, "language": "python", "style": "friendly"}
+ {
+ "id": 3,
+ "title": "",
+ "code": "print 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"}
+ http --json POST http://127.0.0.1:8000/snippets/ code="print 456"
+
+ {
+ "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].
diff --git a/docs/tutorial/4-authentication-and-permissions.md b/docs/tutorial/4-authentication-and-permissions.md
index 4e4edeea..a6d27bf7 100644
--- a/docs/tutorial/4-authentication-and-permissions.md
+++ b/docs/tutorial/4-authentication-and-permissions.md
@@ -43,7 +43,7 @@ And now we can add a `.save()` method to our model class:
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
+ rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
@@ -59,7 +59,7 @@ Now that we've got some users to work with, we'd better add representations of t
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
- snippets = serializers.PrimaryKeyRelatedField(many=True)
+ snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
@@ -198,15 +198,25 @@ If we're interacting with the API programmatically we need to explicitly provide
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"
+ http POST http://127.0.0.1:8000/snippets/ code="print 123"
- {"detail": "Authentication credentials were not provided."}
+ {
+ "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"}
+ http POST -a tom:password http://127.0.0.1:8000/snippets/ code="print 789"
+
+ {
+ "id": 5,
+ "owner": "tom",
+ "title": "foo",
+ "code": "print 789",
+ "linenos": false,
+ "language": "python",
+ "style": "friendly"
+ }
## Summary
diff --git a/docs/tutorial/5-relationships-and-hyperlinked-apis.md b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
index 50552616..58422929 100644
--- a/docs/tutorial/5-relationships-and-hyperlinked-apis.md
+++ b/docs/tutorial/5-relationships-and-hyperlinked-apis.md
@@ -44,7 +44,7 @@ Instead of using a concrete generic view, we'll use the base class for represent
As usual we need to add the new views that we've created in to our URLconf.
We'll add a url pattern for our new API root in `snippets/urls.py`:
- url(r'^$', 'api_root'),
+ url(r'^$', views.api_root),
And then add a url pattern for the snippet highlights:
diff --git a/docs/tutorial/quickstart.md b/docs/tutorial/quickstart.md
index 3e1ce0a9..c3f95994 100644
--- a/docs/tutorial/quickstart.md
+++ b/docs/tutorial/quickstart.md
@@ -19,10 +19,10 @@ 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 ..
+ cd ..
Now sync your database for the first time:
@@ -159,6 +159,33 @@ We can now access our API, both from the command-line, using tools like `curl`..
]
}
+Or using the [httpie][httpie], command line tool...
+
+ bash: http -a username:password http://127.0.0.1:8000/users/
+
+ HTTP/1.1 200 OK
+ ...
+ {
+ "count": 2,
+ "next": null,
+ "previous": null,
+ "results": [
+ {
+ "email": "admin@example.com",
+ "groups": [],
+ "url": "http://localhost:8000/users/1/",
+ "username": "paul"
+ },
+ {
+ "email": "tom@example.com",
+ "groups": [ ],
+ "url": "http://127.0.0.1:8000/users/2/",
+ "username": "tom"
+ }
+ ]
+ }
+
+
Or directly through the browser...
![Quick start image][image]
@@ -173,3 +200,4 @@ If you want to get a more in depth understanding of how REST framework fits toge
[image]: ../img/quickstart.png
[tutorial]: 1-serialization.md
[guide]: ../#api-guide
+[httpie]: https://github.com/jakubroztocil/httpie#installation