APIS with Django REST Framework
CC Attribution - ShareAlike 4.0 International:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Zakharenko, Nina
Django REST Framework can make creating a RESTFUL api quick and easy. Join me as I go over: - What makes an API Restful - How Serializers can make representing your existing models in JSON a breeze - What the built in Views provide for you, and how to provide authentication for your API - How to route to your new Endpoints - Last but not least, how to Unit Test them
 Welcome to DjangoCon and good morning. So this talk is a novice level talk and my purpose is for novices to get familiar with the terminology of APIs and for me to give them the confidence to explore Django REST framework on their own. So what is REST? What is RESTful? It kind of gets talked about a lot. Really it's just an architecture and for the purpose of Web API is it has a few properties that define a RESTful API. It has to be stateless. So the client doesn't have to maintain any sort of knowledge about the server. Each request is made as if it were fresh. It supports common HTTP methods such as actually we'll see some on the next slide. And it returns internet media types like JSON or XML. So HTTP verbs, these are the five of them. We have a post get, put, patch and delete. It's like crime, sort of, not quite. So we use them to perform operations on different endpoints. And our endpoints are going to look kind of like this. So that first endpoint, if we call a get on it, it will return a collection of all users. The second endpoint, we want to pass it an ID and it will return, if we call a get on it, it will return a single user at that ID. So some of the other HTTP verbs, how would they look like if we called them on these endpoints? If we called a post on our first endpoint, it would create a new user. If we called a delete on an endpoint with just an ID, it would delete that single user. If we called delete on our users collection, it would delete all the users. So not very good. And here are some common HTTP response codes that you'll probably see when you're using your API. So 200 is okay. 201, if you call a post on an endpoint, and 201 is return, that means that resource was created. 401 is not authorized. So the endpoint requires some sort of authentication like a token and you did not provide it. For a four common one, the server did not find the resource that you requested. And 500 is a server error. So I am potency. It's a hard to pronounce word. What it means is that the operation produces the same result no matter how many times it is repeated. So put and delete an important log get is a safe method. It might not return the same result every time, but by calling it, you're not modifying any resource. You're knowing that the server isn't, you know, kind of doing anything funky to mess with that object that you're trying to get. So here is where DRF comes in. DRF is Django REST framework and it makes API creation really, really easy. So serializers can kind of be thought of as Django forms. They're a guide to representing your data over the wire. You're telling DRF how you want that data to look. So here we have a model of a tweet. It's very simple. We have a foreign key to user. We have some text. The maximum length is 140 characters. And we have a timestamp. So to represent that data, we're going to want to make a serializer for this model. So we're going to tell our serializer that the user comes from the source field user. And in the meta is where we define which model we're trying to serialize and what fields on that model we're trying to represent. Non-model serializers are also available. You kind of have a bunch of random data and you want to create an endpoint out of it. You can create a serializer but that nice fields that won't be available to you, you'll actually have to define every serializer one by one. So when we pass our data through our serializer, this is what our result will look like. If we configure DRF to send back JSON responses, which are the default, can also pass back XML. And that's just a simple setting. You don't really have to change any of your code. So DRF supports really great validation. And here's what a field validator looks like. So an interesting thing to know is that kind of by creating a method called validate underscore text, it automatically knows that this validator is for that text attribute. There's no configuration here. It's just kind of a naming convention. So here we're getting our source attribute out of the list of values and we're checking to see if the length of it is less than five. If it is, we're going to send back a validation error that says the text is too short. We're going to get back an error response in our API. Other kinds of validators that are available is valid. If you add that method to your serializer, you can actually validate a combination of attributes. For example, if you have a start date and an end date, you would, and you want to make sure that your end date isn't before your start date, you would put that validation logic in your is valid method. And if you want to make sure that your serializer is valid, you can call that method on it pretty similar to a Django form. So permissions. Let's say we only want to let authenticated users see our list of tweets. So is authenticated is actually a built-in permission in Django REST framework and does what it says. It only allows authenticated users to hit that endpoint. Another built-in one is admin user and it checks to see if the user is actually a staff member or not. Creating your own permissions are actually very easy. Let's say we only want the author of the tweet to be able to edit it. So we create our own permission. We base it off base permission and we create a method called has object permission. So we, before our endpoint is hit, that object is passed into this permission method. And we check if our method is in the safe methods such as get. Go ahead, let them see it. If they're trying to call something destructive like a delete, make sure that the object user is the same as the request user. So views are a really interesting topic in DRF. The easiest one to kind of get a grip on is called a view set. Here's an example of a model view set. So we really have to define a few simple variables to be able to use this endpoint. We have a query set. So that's how do we get a collection of the items that we're trying to view. We define our serializer and we define a list of permission classes. So you can provide a set as long as you like for each of your end points. We can also define a handy pre-save method so that gets called before the object is saved to the database and we want the user of our tweet to be saved as the request user. So the model view set is kind of the whole shebang in one. Behind the scenes it supports all of the operations on an endpoint. So you can get one tweet by an ID. You can get the collection of tweets. You can delete. You can modify. And that's kind of all given to you by using this class. It's really handy. So model view sets are awesome when you're working with models but sometimes you have to break things down a little bit more. So general rest framework provides us generic views. API view is the base class. Kind of takes care of all the routing. So if you're calling get it will call the get method. And mixins provide common operations. So and there's some generic views that are provided for us that kind of have a common combination of mixins. For example, if you're only supporting an endpoint that has a list of objects then you would call the, I'm sorry, you would use the list create API view and it would support get and post for you. And you would kind of have to fill in those details. Request and Django rest framework actually look pretty similar to many of the methods that we're used to seeing. So if you guys have used Django forms, you're used to seeing a request.post. And that's the data that comes in when a user submits your form. Request.data is kind of similar. It provides all the fields that were submitted to the API along with their values. Handles the data types we specified. I pretty much exclusively use JSON but some of you might like XML. And responses are also kind of similar but not really. So Django rest framework responses are actually completely un-rendered. You're getting a bunch of data back and it's up to you to figure out how you want to consume it. So, you know, maybe you're using jQuery on your front end to call this endpoint. Maybe you're using Angular or another JavaScript framework. But what's going on behind the scenes when you call or when you want to response back, if you're using a serializer, is your query set, those tweets are being passed into the serializer. The serializer does its work and spits out that JSON representation. And then you call response with that serializer data. By calling response with no status code, you're actually just sending back a 200. If you wanted a processor request with your serializer, you would pass that request data into your serializer and then call serializer.isValid before actually performing the save. Otherwise terrible things happen. So, let's talk a little bit about routing. Viewset routing is absolutely the simplest. So, we have a, what's called the default router and we register our view sets with it. The nice thing about using a default router is we can pass all sorts of configurations to it. For example, if you don't care about appending, trailing, slashes, that's a property that you can pass into default router on instantiation. So, when you register your view sets, all you need to do is include the router URLs in your URL patterns. And what that gives you is the whole shebang. You have tweets multiple, end point, you have a single tweet endpoint, and then you support all the operations on those end points as well to get the post, delete, kind of with just this. So, without view sets, you're going to be setting up your routing in a default way. So, you would create a name for your view and then register each URL pattern. The Bratheable API is really one of the strongest features of the Jinger REST framework. So, it gives you API end point that you can kind of hit and instead of getting just a vanilla JSON response, you'll get this kind of interactive page and you can see all the methods that are available on your end point via that get. You can hit it, you can provide data to it. I think we actually have time for a little bit of a demo. So, let's make sure my server is running. Oops, I was messing with my code so now. Sorry about that guys. Kind of what happens when you do live demos when you're talking, right? Something is always broken. So, here's an example of the raw data that you can pass in. You'll make sure, you have to make sure that your, the data that you're passing in is valid JSON and then it matches the fields in your serializer or Jingo REST framework provides those handy dandy HTML form. So, it provides the correct input box for the data you're trying to pass in. Unit testing. Everyone loves the testing goat. It's important. So, in our past few slides we defined this field validator. That test to make sure that the tweet that someone is trying to submit is more than five characters or it's invalid. So, here we're creating a test for that validator. Let's walk through it one by one and see what's going on. So, we define our API client which again has many useful methods available for us for unit testing and here we're logging in. We're authenticating our request. So, if we didn't pass that in and we have an authentication permission then we would get a 401 back, not authorized. We're getting the URL for our endpoint. We're making up some test data. So, this will print out four X's and we're posting to that URL to create a new tweet. And we assume an error message. So, we want to assert that the status code is 400. That means bad request. Your data isn't valid. We want to ensure that the error message is the same one that we provided in that validation exception. So, I've been using it pretty extensively for the past few months and definitely have some feedback. So, the best place to use it is new projects. You don't really have to code for the framework or around the framework. You can use some of the really powerful tools that are available such as those view sets. You have clean models, not a ton of interdependent validation going on. In that case, it's really just excellent. It saves you so much time. So, when not to use it, legacy Django is out there. If you have a ton of complex models, interdependent validations, if you're trying to save non-model fields in your endpoints, things really kind of get tricky. You don't get to use that nice view set. You kind of have to break out and use those generic view sets and customize your code to deal with some of these complexities. So, doesn't mean you shouldn't go for it, but really there might be hurdles. On the bright side, though, Django S Framework 3 is on the horizon. Apparently, they're going to be fixing some of these issues that I've had and redoing the serializers completely. So, don't discount it, keep an eye out for it, but just prepare for some issues. So, next steps for you guys, pip install Django S Framework. The documentation is really good. The tutorial is very thorough. Hopefully, I've familiarized you with some of the terminology that you'll be seeing. And that is it for me. Thanks, guys. Thanks so much, Nina. I think we do actually have time for perhaps one or maybe two unbelievably brief questions so if somebody has a snappy one. Thanks, great talk, Nina. I just wanted to quickly ask you, is there any particular pattern you think that doesn't work with models with APIs in general, like sort of a past commercial relationship or foreign keys? What would your advice to new programmers on how to handle that more complex model structure? I've been able to do it before a few hiccups. It definitely still helps to have those built-in things like those generic API views so you don't have to write a bunch of code to be like when this request is get, do this. And the API browser has been such a powerful and helpful tool that it's still worthwhile kind of exploring it even for more complex data models. Thank you. Sure. I was wondering, you were talking about like having clean models and if you didn't really it was kind of a tough fit or if you had old code it was a tough fit. I was wondering if you thought that in some ways, because it's something that I've looked at and I actually have two of those issues a little bit with older code base, but I was wondering if you thought it was able to, it was practical to break out certain components like the serializers if you thought that was practical or if it's actually kind of pretty tangled up together or if you had any, if that question sort of made sense. If it's possible to sort of use some components even if you can't use the whole thing because of this. I actually tackled that problem with a very complex model both ways with the serializer and without a serializer. Using the serializer required a hack kind of out of the scope of this talk, but I'm happy to talk to you about it afterwards and it really did provide some nice things. For example, field level validation, like if I said my serializer only accepts 140 characters instead of the model, it would kind of throw back that correct error. Whereas if I didn't use the serializer, I'd have to do all that kind of plumbing myself. Alright Nina, thank you again. Thank you.
 Welcome to DjangoCon and good morning. So this talk is a novice level talk and my purpose  is for novices to get familiar with the terminology of APIs and for me to give them the confidence  to explore Django REST framework on their own. So what is REST? What is RESTful? It  kind of gets talked about a lot. Really it's just an architecture and for the purpose of  Web API is it has a few properties that define a RESTful API. It has to be stateless. So  the client doesn't have to maintain any sort of knowledge about the server. Each request  is made as if it were fresh. It supports common HTTP methods such as actually we'll see some  on the next slide. And it returns internet media types like JSON or XML. So HTTP verbs,  these are the five of them. We have a post get, put, patch and delete. It's like crime,  sort of, not quite. So we use them to perform operations on different endpoints. And our  endpoints are going to look kind of like this. So that first endpoint, if we call a get on  it, it will return a collection of all users. The second endpoint, we want to pass it an  ID and it will return, if we call a get on it, it will return a single user at that ID.  So some of the other HTTP verbs, how would they look like if we called them on these  endpoints? If we called a post on our first endpoint, it would create a new user. If we  called a delete on an endpoint with just an ID, it would delete that single user. If we  called delete on our users collection, it would delete all the users. So not very good.  And here are some common HTTP response codes that you'll probably see when you're using  your API. So 200 is okay. 201, if you call a post on an endpoint, and 201 is return,  that means that resource was created. 401 is not authorized. So the endpoint requires  some sort of authentication like a token and you did not provide it. For a four common  one, the server did not find the resource that you requested. And 500 is a server error.  So I am potency. It's a hard to pronounce word. What it means is that the operation  produces the same result no matter how many times it is repeated. So put and delete an  important log get is a safe method. It might not return the same result every time, but  by calling it, you're not modifying any resource. You're knowing that the server isn't, you  know, kind of doing anything funky to mess with that object that you're trying to get.  So here is where DRF comes in. DRF is Django REST framework and it makes API creation  really, really easy. So serializers can kind of be thought of as Django forms. They're  a guide to representing your data over the wire. You're telling DRF how you want that  data to look. So here we have a model of a tweet. It's very simple. We have a foreign  key to user. We have some text. The maximum length is 140 characters. And we have a timestamp.  So to represent that data, we're going to want to make a serializer for this model.  So we're going to tell our serializer that the user comes from the source field user.  And in the meta is where we define which model we're trying to serialize and what fields  on that model we're trying to represent. Non-model serializers are also available. You kind of  have a bunch of random data and you want to create an endpoint out of it. You can create  a serializer but that nice fields that won't be available to you, you'll actually have  to define every serializer one by one. So when we pass our data through our serializer,  this is what our result will look like. If we configure DRF to send back JSON responses,  which are the default, can also pass back XML. And that's just a simple setting. You don't  really have to change any of your code. So DRF supports really great validation. And here's  what a field validator looks like. So an interesting thing to know is that kind of by creating a  method called validate underscore text, it automatically knows that this validator is  for that text attribute. There's no configuration here. It's just kind of a naming convention.  So here we're getting our source attribute out of the list of values and we're checking  to see if the length of it is less than five. If it is, we're going to send back a validation  error that says the text is too short. We're going to get back an error response in our  API. Other kinds of validators that are available is valid. If you add that method to your serializer,  you can actually validate a combination of attributes. For example, if you have a start  date and an end date, you would, and you want to make sure that your end date isn't before  your start date, you would put that validation logic in your is valid method. And if you  want to make sure that your serializer is valid, you can call that method on it pretty  similar to a Django form. So permissions. Let's say we only want to let authenticated  users see our list of tweets. So is authenticated is actually a built-in permission in Django  REST framework and does what it says. It only allows authenticated users to hit that endpoint.  Another built-in one is admin user and it checks to see if the user is actually a staff  member or not. Creating your own permissions are actually very easy. Let's say we only  want the author of the tweet to be able to edit it. So we create our own permission.  We base it off base permission and we create a method called has object permission. So  we, before our endpoint is hit, that object is passed into this permission method. And  we check if our method is in the safe methods such as get. Go ahead, let them see it. If  they're trying to call something destructive like a delete, make sure that the object user  is the same as the request user. So views are a really interesting topic in DRF. The  easiest one to kind of get a grip on is called a view set. Here's an example of a model view  set. So we really have to define a few simple variables to be able to use this endpoint.  We have a query set. So that's how do we get a collection of the items that we're trying  to view. We define our serializer and we define a list of permission classes. So you can provide  a set as long as you like for each of your end points. We can also define a handy pre-save  method so that gets called before the object is saved to the database and we want the user  of our tweet to be saved as the request user. So the model view set is kind of the whole  shebang in one. Behind the scenes it supports all of the operations on an endpoint. So you  can get one tweet by an ID. You can get the collection of tweets. You can delete. You  can modify. And that's kind of all given to you by using this class. It's really handy.  So model view sets are awesome when you're working with models but sometimes you have  to break things down a little bit more. So general rest framework provides us generic  views. API view is the base class. Kind of takes care of all the routing. So if you're  calling get it will call the get method. And mixins provide common operations. So and there's  some generic views that are provided for us that kind of have a common combination of  mixins. For example, if you're only supporting an endpoint that has a list of objects then  you would call the, I'm sorry, you would use the list create API view and it would support  get and post for you. And you would kind of have to fill in those details. Request and  Django rest framework actually look pretty similar to many of the methods that we're  used to seeing. So if you guys have used Django forms, you're used to seeing a request.post.  And that's the data that comes in when a user submits your form. Request.data is kind of  similar. It provides all the fields that were submitted to the API along with their values.  Handles the data types we specified. I pretty much exclusively use JSON but some of you  might like XML. And responses are also kind of similar but not really. So Django rest  framework responses are actually completely un-rendered. You're getting a bunch of data  back and it's up to you to figure out how you want to consume it. So, you know, maybe  you're using jQuery on your front end to call this endpoint. Maybe you're using Angular  or another JavaScript framework. But what's going on behind the scenes when you call or  when you want to response back, if you're using a serializer, is your query set, those  tweets are being passed into the serializer. The serializer does its work and spits out  that JSON representation. And then you call response with that serializer data. By calling  response with no status code, you're actually just sending back a 200. If you wanted a processor  request with your serializer, you would pass that request data into your serializer and  then call serializer.isValid before actually performing the save. Otherwise terrible things  happen. So, let's talk a little bit about routing. Viewset routing is absolutely the  simplest. So, we have a, what's called the default router and we register our view sets  with it. The nice thing about using a default router is we can pass all sorts of configurations  to it. For example, if you don't care about appending, trailing, slashes, that's a property  that you can pass into default router on instantiation. So, when you register your view sets, all  you need to do is include the router URLs in your URL patterns. And what that gives you  is the whole shebang. You have tweets multiple, end point, you have a single tweet endpoint,  and then you support all the operations on those end points as well to get the post,  delete, kind of with just this. So, without view sets, you're going to be setting up your  routing in a default way. So, you would create a name for your view and then register each  URL pattern. The Bratheable API is really one of the strongest features of the Jinger  REST framework. So, it gives you API end point that you can kind of hit and instead of getting  just a vanilla JSON response, you'll get this kind of interactive page and you can see all  the methods that are available on your end point via that get. You can hit it, you can  provide data to it. I think we actually have time for a little bit of a demo. So, let's  make sure my server is running. Oops, I was messing with my code so now. Sorry about that  guys. Kind of what happens when you do live demos when you're talking, right? Something  is always broken. So, here's an example of the raw data that you can pass in. You'll  make sure, you have to make sure that your, the data that you're passing in is valid JSON  and then it matches the fields in your serializer or Jingo REST framework provides those handy  dandy HTML form. So, it provides the correct input box for the data you're trying to pass  in. Unit testing. Everyone loves the testing goat. It's important. So, in our past few  slides we defined this field validator. That test to make sure that the tweet that someone  is trying to submit is more than five characters or it's invalid. So, here we're creating a  test for that validator. Let's walk through it one by one and see what's going on. So,  we define our API client which again has many useful methods available for us for unit  testing and here we're logging in. We're authenticating our request. So, if we didn't pass that in  and we have an authentication permission then we would get a 401 back, not authorized. We're  getting the URL for our endpoint. We're making up some test data. So, this will print out  four X's and we're posting to that URL to create a new tweet. And we assume an error  message. So, we want to assert that the status code is 400. That means bad request. Your  data isn't valid. We want to ensure that the error message is the same one that we provided  in that validation exception. So, I've been using it pretty extensively for the past few  months and definitely have some feedback. So, the best place to use it is new projects.  You don't really have to code for the framework or around the framework. You can use some  of the really powerful tools that are available such as those view sets. You have clean models,  not a ton of interdependent validation going on. In that case, it's really just excellent.  It saves you so much time. So, when not to use it, legacy Django is out there. If you  have a ton of complex models, interdependent validations, if you're trying to save non-model  fields in your endpoints, things really kind of get tricky. You don't get to use that nice  view set. You kind of have to break out and use those generic view sets and customize your  code to deal with some of these complexities. So, doesn't mean you shouldn't go for it,  but really there might be hurdles. On the bright side, though, Django S Framework 3  is on the horizon. Apparently, they're going to be fixing some of these issues that I've  had and redoing the serializers completely. So, don't discount it, keep an eye out for  it, but just prepare for some issues. So, next steps for you guys, pip install Django  S Framework. The documentation is really good. The tutorial is very thorough. Hopefully,  I've familiarized you with some of the terminology that you'll be seeing. And that is it for  me. Thanks, guys.  Thanks so much, Nina. I think we do actually have time for perhaps one or maybe two unbelievably  brief questions so if somebody has a snappy one.  Thanks, great talk, Nina. I just wanted to quickly ask you, is there any particular  pattern you think that doesn't work with models with APIs in general, like sort of a past  commercial relationship or foreign keys? What would your advice to new programmers on how  to handle that more complex model structure?  I've been able to do it before a few hiccups. It definitely still helps to have those built-in  things like those generic API views so you don't have to write a bunch of code to be  like when this request is get, do this. And the API browser has been such a powerful and  helpful tool that it's still worthwhile kind of exploring it even for more complex data  models.  Thank you.  Sure.  I was wondering, you were talking about like having clean models and if you didn't really  it was kind of a tough fit or if you had old code it was a tough fit. I was wondering if  you thought that in some ways, because it's something that I've looked at and I actually  have two of those issues a little bit with older code base, but I was wondering if you  thought it was able to, it was practical to break out certain components like the serializers  if you thought that was practical or if it's actually kind of pretty tangled up together  or if you had any, if that question sort of made sense. If it's possible to sort of use  some components even if you can't use the whole thing because of this.  I actually tackled that problem with a very complex model both ways with the serializer  and without a serializer. Using the serializer required a hack kind of out of the scope of  this talk, but I'm happy to talk to you about it afterwards and it really did provide some  nice things. For example, field level validation, like if I said my serializer only accepts  140 characters instead of the model, it would kind of throw back that correct error. Whereas  if I didn't use the serializer, I'd have to do all that kind of plumbing myself.  Alright Nina, thank you again.  Thank you.
DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_0_71.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_72_143.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_144_215.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_216_311.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_312_419.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_420_515.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_516_6119.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_6120_6779.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_6780_9131.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_9132_9311.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_9312_11363.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_11364_11435.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_11436_13211.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_13212_13379.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_13380_16691.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_16692_17843.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_17844_19859.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_19860_19931.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_19932_21767.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_21768_21959.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_21960_22931.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_22932_23255.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_23256_23387.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_23388_23663.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_23664_23711.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_23712_24671.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_24672_24851.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_24852_27263.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_27264_29171.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_29172_29999.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_30000_30527.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_30528_34751.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_34752_34811.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_34812_34895.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_34896_35015.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_35016_35171.png DjangoCon_2014-_Django_REST_API_-_So_easy_you_can_learn_it_in_25_minutes_cqP758k1BaQ_35172_35211.png