Creating and retrieving related resources
Now, we will use the HTTPie command or its curl equivalents to compose and send HTTP requests to the API. We will use JSON for the requests that require additional data. Remember that you can perform the same tasks with your favorite GUI-based tool or with the browsable API.
First, we will compose and send an HTTP request to create a new game category. Remember that we used the browsable API to create a game category named '3D RPG'
.
http POST :8000/game-categories/ name='2D mobile arcade'
The following is the equivalent curl
command:
curl -iX POST -H "Content-Type: application/json" -d '{"name":"2D mobile arcade"}' :8000/game-categories/
The preceding command will compose and send a POST
HTTP request with the specified JSON key-value pair. The request specifies /game-categories/
, and therefore, it will match '^game-categories/$'
and run the post
method for the views.GameCategoryList
class-based view. Remember that the method is defined in the ListCreateAPIView
superclass and it ends up calling the create method defined in mixins.CreateModelMixin
. If the new GameCategory
instance was successfully persisted in the database, the call to the method will return an HTTP 201 Created
status code and the recently persisted GameCategory
serialized to JSON in the response body. The following line shows a sample response for the HTTP request with the new GameCategory
object in the JSON response. The response doesn't include the header. Note that the response includes both the primary key, pk
, and the url, url
, for the created category. The games
array is empty because there aren't games related to the new category yet:
{ "games": [], "name": "2D mobile arcade", "pk": 4, "url": "http://localhost:8000/game-categories/4/" }
Now, we will compose and send HTTP requests to create two games that belong to the first category we recently created: 3D RPG
. We will specify the game_category
value with the name of the desired game category
. However, the database table that persists the Game
model will save the value of the primary key of the related GameCategory
whose name value matches the one we provide:
http POST :8000/games/ name='PvZ Garden Warfare 4' game_category='3D RPG' played=false release_date='2016-06-21T03:02:00.776594Z' http POST :8000/games/ name='Superman vs Aquaman' game_category='3D RPG' played=false release_date='2016-06-21T03:02:00.776594Z'
The following are the equivalent curl
commands:
curl -iX POST -H "Content-Type: application/json" -d '{"name":"PvZ Garden Warfare 4", "game_category":"3D RPG", "played": "false", "release_date": "2016-06-21T03:02:00.776594Z"}' :8000/games/ curl -iX POST -H "Content-Type: application/json" -d '{"name":" Superman vs Aquaman", "game_category":"3D RPG", "played": "false", "release_date": "2016-06-21T03:02:00.776594Z"}' :8000/games/
The previous commands will compose and send two POST
HTTP requests with the specified JSON key-value pairs. The request specifies /games/
, and therefore, it will match '^games/$'
and run the post
method for the views.GameList
class-based view. The following lines show sample responses for the two HTTP requests with the new Game
objects in the JSON responses. The responses don't include the headers. Note that the response includes only the url, url
, for the created games and doesn't include the primary key. The value for game_category
is the name
for the related GameCategory
:
{ "game_category": "3D RPG", "name": "PvZ Garden Warfare 4", "played": false, "release_date": "2016-06-21T03:02:00.776594Z", "url": "http://localhost:8000/games/2/" } { "game_category": "3D RPG", "name": "Superman vs Aquaman", "played": false, "release_date": "2016-06-21T03:02:00.776594Z", "url": "http://localhost:8000/games/3/" }
We can run the previously explained commands to check the contents of the tables that Django created in the PostgreSQL database. We will notice that the game_category_id
column for the games_game
table saves the value of the primary key of the related row in the games_game_category
table. The GameSerializer
class uses the SlugRelatedField
to display the name value for the related GameCategory
. The following screenshot shows the contents of the games_game_category
and the games_game
table in a PostgreSQL database after running the HTTP requests:
Now, we will compose and send an HTTP request to retrieve the game category that is contains two games, that is the game category resource whose id or primary key is equal to 3
. Don't forget to replace 3
with the primary key value of the game whose name is equal to '3D RPG'
in your configuration:
http :8000/game-categories/3/
The following is the equivalent curl command:
curl -iX GET :8000/game-categories/3/
The previous commands will compose and send the following HTTP request: GET http://localhost:8000/game-categories/3/
. The request has a number after /game-categories/
, and therefore, it will match '^game-categories/(?P<pk>[0-9]+)/$'
and run the get
method for the views.GameCategoryDetail
class based view. Remember that the method is defined in the RetrieveUpdateDestroyAPIView
superclass and it ends up calling the retrieve
method defined in mixins.RetrieveModelMixin
. The following lines show a sample response for the HTTP request, with the GameCategory
object and the hyperlinks of the related games in the JSON response:
HTTP/1.0 200 OK Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS Content-Type: application/json Date: Tue, 21 Jun 2016 23:32:04 GMT Server: WSGIServer/0.2 CPython/3.5.1 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "games": [ "http://localhost:8000/games/2/", "http://localhost:8000/games/3/" ], "name": "3D RPG", "pk": 3, "url": "http://localhost:8000/game-categories/3/" }
The GameCategorySerializer
class defined the games
attribute as a HyperlinkedRelatedField
, and therefore, the serializer renders the URL for each related Game
instance in the value for the games
array. If we view the results in a web browser through the browsable API, we will be able to click or tap on the hyperlink to see the details for each game.
Now, we will compose and send a POST
HTTP request to create a game related to a game category name that doesn't exist: 'Virtual reality'
:
http POST :8000/games/ name='Captain America vs Thor' game_category='Virtual reality' played=false release_date='2016-06-21T03:02:00.776594Z'
The following is the equivalent curl command:
curl -iX POST -H "Content-Type: application/json" -d '{"name":"'Captain America vs Thor", "game_category":"Virtual reality", "played": "false", "release_date": "2016-06-21T03:02:00.776594Z"}' :8000/games/
Django won't be able to retrieve a GameCategory
instance whose name
is equal to the specified value, and therefore, we will receive a 400 Bad Request
status code in the response header and a message related to the value specified in for game_category
in the JSON body. The following lines show a sample response:
HTTP/1.0 400 Bad Request Allow: GET, POST, HEAD, OPTIONS Content-Type: application/json Date: Tue, 21 Jun 2016 23:51:19 GMT Server: WSGIServer/0.2 CPython/3.5.1 Vary: Accept, Cookie X-Frame-Options: SAMEORIGIN { "game_category": [ "Object with name=Virtual reality does not exist." ] }
Now, we will compose and send HTTP requests to create two players:
http POST :8000/players/ name='Brandon' gender='M' http POST :8000/players/ name='Kevin' gender='M'
The following are the equivalent curl
commands:
curl -iX POST -H "Content-Type: application/json" -d '{"name":"Brandon", "gender":"M"}' :8000/players/ curl -iX POST -H "Content-Type: application/json" -d '{"name":" Kevin", "gender":"M"}' :8000/players/
The previous commands will compose and send two POST
HTTP requests with the specified JSON key-value pairs. The request specifies /players/
, and therefore, it will match '^players/$'
and run the post
method for the views.PlayerList
class based view. The following lines show sample responses for the two HTTP requests with the new Player
objects in the JSON responses. The responses don't include the headers. Notice that the response includes only the url, url
, for the created players and doesn't include the primary key. The value for gender_description
is the choice description for the gender
char. The scores
array is empty because there aren't scores related to each new player yet:
{ "gender": "M", "name": "Brandon", "scores": [], "url": "http://localhost:8000/players/2/" } { "gender": "M", "name": "Kevin", "scores": [], "url": "http://localhost:8000/players/3/" }
Now, we will compose and send HTTP requests to create four scores:
http POST :8000/player-scores/ score=35000 score_date='2016-06-21T03:02:00.776594Z' player='Brandon' game='PvZ Garden Warfare 4' http POST :8000/player-scores/ score=85125 score_date='2016-06-22T01:02:00.776594Z' player='Brandon' game='PvZ Garden Warfare 4' http POST :8000/player-scores/ score=123200 score_date='2016-06-22T03:02:00.776594Z' player='Kevin' game='Superman vs Aquaman' http POST :8000/player-scores/ score=11200 score_date='2016-06-22T05:02:00.776594Z' player='Kevin' game='PvZ Garden Warfare 4'
The following are the equivalent curl commands:
curl -iX POST -H "Content-Type: application/json" -d '{"score":"35000", "score_date":"2016-06-21T03:02:00.776594Z", "player":"Brandon", "game":"PvZ Garden Warfare 4"}' :8000/player-scores/ curl -iX POST -H "Content-Type: application/json" -d '{"score":"85125", "score_date":"2016-06-22T01:02:00.776594Z", "player":"Brandon", "game":"PvZ Garden Warfare 4"}' :8000/player-scores/ curl -iX POST -H "Content-Type: application/json" -d '{"score":"123200", "score_date":"2016-06-22T03:02:00.776594Z", "player":"Kevin", "game":"'Superman vs Aquaman"}' :8000/player-scores/ curl -iX POST -H "Content-Type: application/json" -d '{"score":"11200", "score_date":"2016-06-22T05:02:00.776594Z", "player":"Kevin", "game":"PvZ Garden Warfare 4"}' :8000/player-scores/
The previous commands will compose and send four POST
HTTP requests with the specified JSON key-value pairs. The request specifies /player-scores/
, and therefore, it will match '^player-scores/$'
and run the post
method for the views.PlayerScoreList
class based view. The following lines show sample responses for the four HTTP requests with the new Player
objects in the JSON responses. The responses don't include the headers.
Django REST Framework uses the PlayerScoreSerializer
class to generate the JSON response. Thus, the value for game
is the name for the related Game
instance and the value for player
is the name for the related Player
instance. The PlayerScoreSerializer
class used SlugRelatedField
for both fields:
{ "game": "PvZ Garden Warfare 4", "pk": 3, "player": "Brandon", "score": 35000, "score_date": "2016-06-21T03:02:00.776594Z", "url": "http://localhost:8000/player-scores/3/" } { "game": "PvZ Garden Warfare 4", "pk": 4, "player": "Brandon", "score": 85125, "score_date": "2016-06-22T01:02:00.776594Z", "url": "http://localhost:8000/player-scores/4/" } { "game": "Superman vs Aquaman", "pk": 5, "player": "Kevin", "score": 123200, "score_date": "2016-06-22T03:02:00.776594Z", "url": "http://localhost:8000/player-scores/5/" } { "game": "PvZ Garden Warfare 4", "pk": 6, "player": "Kevin", "score": 11200, "score_date": "2016-06-22T05:02:00.776594Z", "url": "http://localhost:8000/player-scores/6/" }
We can run the previously explained commands to check the contents of the tables that Django created in the PostgreSQL database. We will notice that the game_id
column for the games_playerscore
table saves the value of the primary key of the related row in the games_game
table. In addition, the player_id
column for the games_playerscore
table saves the value of the primary key of the related row in the games_player
table. The following screenshot shows the contents for the games_game_category
, games_game
, games_player
and games_playerscore
tables in a PostgreSQL database after running the HTTP requests:
Now, we will compose and send an HTTP request to retrieve a specific player that contains two scores, which is the player resource whose id or primary key is equal to 3
. Don't forget to replace 3
with the primary key value of the player whose name is equal to 'Kevin'
in your configuration:
http :8000/players/3/
The following is the equivalent curl command:
curl -iX GET :8000/players/3/
The previous commands will compose and send the following HTTP request: GET http://localhost:8000/players/3/
. The request has a number after /players/
, and therefore, it will match '^players/(?P<pk>[0-9]+)/$'
and run the get
method for the views.PlayerDetail
class based view. Remember that the method is defined in the RetrieveUpdateDestroyAPIView
superclass and it ends up calling the retrieve
method defined in mixins.RetrieveModelMixin
. The following lines show a sample response for the HTTP request, with the Player
object, the related PlayerScore
objects and the Game
object related to each PlayerScore
object in the JSON response:
HTTP 200 OK Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS Content-Type: application/json Vary: Accept { "url": "http://localhost:8000/players/3/", "name": "Kevin", "gender": "M", "gender_description": "Male", "scores": [ { "url": "http://localhost:8000/player-scores/5/", "pk": 5, "score": 123200, "score_date": "2016-06-22T03:02:00.776594Z", "game": { "url": "http://localhost:8000/games/3/", "game_category": "3D RPG", "name": "Superman vs Aquaman", "release_date": "2016-06-21T03:02:00.776594Z", "played": false } }, { "url": "http://localhost:8000/player-scores/6/", "pk": 6, "score": 11200, "score_date": "2016-06-22T05:02:00.776594Z", "game": { "url": "http://localhost:8000/games/2/", "game_category": "3D RPG", "name": "PvZ Garden Warfare 4", "release_date": "2016-06-21T03:02:00.776594Z", "played": false } } ] }
The PlayerSerializer
class defined the scores
attribute as a ScoreSerializer
with many
equal to True
, and therefore, this serializer renders each score related to the player. The ScoreSerializer
class defined the game
attribute as a GameSerializer
, and therefore, this serializer renders each game related to the score. If we view the results in a web browser through the browsable API, we will be able to click or tap on the hyperlink of each of the related resources. However, in this case, we also see all their details without having to follow the hyperlink.