CRUD Operations
Create, Read, Update, and Delete are the operations you need on all data. As REST is a way of reading and manipulating data on a server, it has HTTP operations for each of these actions. In this lesson, you’ll walk through the complete life cycle of a database object through the Ninja REST API.
00:00
In the previous lesson, I showed you how Ninja serializes data using the Schema
and ModelSchema
classes. In this lesson, I’m going put it all together and show you a complete data object life cycle.
00:14 CRUD just might be my favorite computer acronym, maybe a close second behind GIMP. CRUD stands for Create, Read, Update, and Delete. Those are the operations you typically need to do with data.
00:29 In REST, that translates to HTTP operations: POST for Create, GET for Read, PUT or PATCH for Update, and DELETE for, well, deleting. One out of four ain’t bad. Ninja provides a decorator for each one of these operations.
00:48
You’ve seen some of it so far, but I’m going to combine it together in this lesson. Do you feel a chill in the air? Add a new Django app, this time called stark
.
00:58
Don’t forget to add it to INSTALLED_APPS
, register your Ninja routes, and because I’m going to add a model here as well, you’re going to need to run migrations.
01:08
The data I’ll be operating on is the WeddingGift
ORM class. I’m keeping it to a single field for demo purposes. You already know how to serialize these.
01:18
Anything more complicated is just a process of adding fields to Schema
or letting ModelSchema
’s magic do that for you.
01:27
Inside of the stark
app, I’ve created schemas.py
and inside of it, I’ve defined two ModelSchema
classes—one for creating gifts, the other for including them in a response.
01:38
Like I showed in the previous lesson, the ModelSchema
is based on your ORM, so I import the WeddingGift
class from stark
models.
01:47
Then I use the inner-class declaration to connect this ModelSchema
to the WeddingGift
class. In the previous examples, I listed the fields using the model_fields
attribute.
01:58
There’s another attribute you can use instead. That’s model_exclude
. In this case, all the fields will be used except for the ID. Of course, that’s just the description field in this case, but you get the idea. For a larger class, this can save you some typing.
02:15
This is a similar declaration for the GiftOut
serializer, and this time, I’m using model_fields
, but instead of using a list, I’m using the special "__all__"
string.
02:26 This says to use all of the fields. The documentation warns against using this. You want to be careful with it. If you’re explicit about your field listing and the ORM class changes, you won’t accidentally expose anything.
02:40
If you use "__all__"
—or even model_exclude
has the same problem—you are implicitly getting any changes to the ORM without thinking about it.
02:54
With the serializers in place, let’s look at the four CRUD operations. The first is CREATE, using a .post()
. In the decorator, in addition to specifying the path, I’m also specifying the response.
03:08
I’m using a GiftOut
serializer. I’ve also added the url_name
attribute, which is equivalent to the name
attribute in a path.
03:18 This registers the call for reverse lookup. This can be a bit finicky, but I’ll come back to that in a second. The view takes its standard request, and because it’s a POST, you need to indicate what is in the request’s body.
03:33
I’m using GiftIn
as my payload. Inside the view, you use the ORM’s .create()
method, passing in all the fields in the payload.
03:43
The .dict()
method on GiftIn
returns a dictionary with all the fields sent in. The double-star operator (**
) here turns the dictionary into keyword arguments for the create call.
03:55
With the gift
ORM created, I return that in the view, the decorator takes care of translating this newly created object using the GiftOut
serializer, and the result to the user is JSON.
04:09 Okay, that’s the C in CRUD. Now on to the R. There are actually two Rs—one for listing objects, the other for getting a specific object. Let’s start with the list.
04:20
You’ve seen this before. The .get()
decorator takes the path, declares that it will respond with a list of GiftOut
serializers, and I’ve added the url_name
here as well. Because the response is set to a list of GiftOut
serializers, all you need to do is return the .all()
call of the WeddingGift
ORM. Let me just scroll down a bit.
04:45
Time for our second R. The declaration is similar. It’s still a .get()
decorator. This time, the path indicates that it takes an integer named gift_id
.
04:56
The response is a single GiftOut
serializer, and once again, I’ve named the call using url_name
. You may remember that in the targaryen
app, I did a GET on the ORM.
05:08
That’s bad practice. By using the .get_object_or_404()
shortcut, Ninja will handle both the retrieving of the object and the proper re-raising of the exception if no such object exists. In a later lesson, I’ll show you how to customize Ninja’s exception management. On to the U in CRUD.
05:29
The .put()
decorator is used to update an existing object. This means the path needs an ID, similar to the previous .get()
. This also responds with a GiftOut
serializer. Note the lack of a url_name
argument.
05:43
The path here is identical to the .get()
path. It is the operation that is different. If you named this one, only one lookup would be kept because Django does the lookup based on the path. So, if called this one update_gift
, it would override the .get()
, and you’d end up with update_gift
as the name for both. It’s best to just name the first usage something generic and not name the others.
06:10
Remember, reverse lookup is based on the path, not on the operation. The view declaration includes both the gift_id
and a payload, sort of a mix between a GET and a POST. Here, you’re updating a specific object, so you need the ID of the object in order to fetch it and the fields that you’re going to change. Like with the GET, I look the object up using .get_object_or_404()
.
06:37
Then, to replace the fields, I look through all of the fields sent in and call .set_attr
on each of them. This replaces their current contents with whatever was sent in in the payload. With the fields written to, the object needs to be saved and then just returned. Finally, the DELETE operation is used for the D in CRUD.
07:01
This time, you need a path with an ID, but you don’t declare a response in the decorator. Like the GET, you use the shortcut to fetch the object from the database, then call the ORM’s .delete()
method.
07:15 Sometimes you’ll find APIs returning the object that was deleted. This feels fishy to me, as you’re responding with the thing you just removed. I usually just respond with some sort of success indicator.
07:29 Okay, let’s try these out.
07:48
Wow, that was a lot. The -X
flag tells curl
to use a specific HTTP operation, in this case the POST. The -H
flag adds an HTTP header.
08:00
In this case, I’m setting the Content-Type
to json
because that’s what I’m sending. The Ninja POST
decorator expects a single data item in the request body, and that’s supposed to contain a JSON dictionary with fields.
08:16
curl
’s -d
sets this, "I'm giving the lucky couple a knife."
The response is a newly created WeddingGift
object, serialized as JSON, {"id": 1, "description": "knife"}
.
08:42 Same idea, but now they’ve got two presents. Let’s look at what has been created by calling the list version of the read.
09:02
No need to use the -X
this time, because it’s a GET. And you can see a list comes back with the "knife"
and the "stain remover"
. Let’s update gift number two.
09:32
There were a lot of stains at the Red Wedding. "stain remover"
really deserves to be plural. Let’s fetch this item that I’ve just updated.
09:51
And you can see the result. "stain removers"
is now plural.
10:04
And finally—well, there’s an appropriate word for the Red Wedding, finally. And finally, I’ve deleted the "stain remover"
from the database.
10:13 It probably got all used up. To demonstrate that it worked, I’ll list the gifts one last time.
10:26 And you’re left with just the knife. I-I-I-I want the knife. Now there’s a deep-cut reference that probably only my wife will get. I will give kudos to anybody who in the comments correctly identifies what I’m talking about.
10:45
Let me just quickly show you those url_name
arguments in action. I’ve run manage shell
and imported the reverse
function.
10:59
By default, the NinjaAPI
object creates a namespace called api-1.0.0
. You can change this if you like. I’ll show you how later. Our first CRUD operation was named create_gift
, so to get it, you reverse with the namespace and the name, giving this resulting path: /api/stark/gift
.
11:25 And there’s the list reference …
11:33
and the GET, PUT, and DELETE, which use different operations but the same path, can be found under the name "gift"
. This is actually one of my complaints about Django.
11:44
There should either be a flag for reverse
or a different method for doing lookups without the arguments. Frequently, when doing single-page applications, you want pass in the URLs and then append the IDs afterwards as the user does something in the interface.
11:58 I’ve got lots of code and templates that use the URL tag with a fake argument and then JavaScript to strip the argument, just to append a real one later. One of these years, I’ll submit a PR to Django core. But laziness.
12:15
One of the things that comes with Ninja is a web interface for interacting with your API. It gets created automatically at the /docs
endpoint of your path.
12:25 Let’s take a look at it.
12:35
Here are all the views that have been created so far. If I click this down arrow on the lannister
item, you get a bit of info and a Try it out button.
12:46 There’s not much to see with this call, so it’s kind of empty. Well, let’s push the button. This is a two-step process. You’ll see why in a moment. So now I’m going to push Execute.
13:01
It’s run the call. It even shows you what curl
command you could use to get the results on the command line. Let’s do something a little more complicated and create a wedding gift …
13:14 Conveniently color-coded to help you highlight the different kinds of operations.
13:23 When I clicked Try it out, it gave me an editable field pre-populated with a request body. I edit the string, then click Execute. And the third gift was created. Very handy tool, this, especially when you’re debugging your views.
13:48 Next up, I’ll show you a collection of odds and ends, how to write a single view for multiple operations, how to create multiple Ninjas, and using API versioning.
sgarcia on June 27, 2023
Hi! I’ve had problems executing curl insert and update commands in windows. It looks like it was because of the single quotes (found it thanks to this question: stackoverflow.com/questions/54272167/curl-3-unmatched-brace-in-url-position-1)
So this…:
curl -s -X POST http://127.0.0.1:8000/api/stark/gift -H "Content-Type: application/json" -d '{ "description":"stain remover" }'
…gives me this error:
{“detail”: “Cannot parse request body (Expecting value: line 1 column 1 (char 0))”}
But this works:
curl -s -X POST http://127.0.0.1:8000/api/stark/gift -H "Content-Type: application/json" -d "{ \"description\":\"stain remover\" }"
Hope it can helps someone :)
Become a Member to join the conversation.
Kojo Idrissa on April 17, 2023
“I, I, I, I want the knife”, followed by “Pleeeease…” is said by Eddy Murphy in The Golden Child when he’s trying to get the sacred dagger. 👴🏾