Build Crud Rest Token
Build Crud Rest Token
to/nobleobioma/build-a-crud-django-rest-api-46kc
https://github.com/nobioma1/ah_bk_django_rest_api
In this article, we'll be adding CRUD(Create, Read, Update, Delete) functionality to an already
existing Django REST API with user authentication. This is a continuation of a previous article
where we added authentication functionalities like register, login, logout to a simple Bookstore
Django REST API with just one endpoint that sends a response {"message": "Welcome to the
BookStore!"} and a user must be authenticated to access our endpoint.
We'll start by creating some models. Django models are basically python objects that are utilized in
accessing and managing data. Models define the structure of stored data like field types, creating
relationships between data, applying certain constraints to data fields and a lot more. For more
information on the Django Model, check the documentation v3.
For our bookstore_app, we'll create two models Author and Book.
# ./bookstore_app/api/models.py
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=200)
description = models.CharField(max_length=300)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
added_by = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
created_date = models.DateTimeField(default=timezone.now)
def __str__(self):
return self.title
1. In the Book model, a book must have an author. So we created a field author which is a
ForeignKey referencing the Author model.
2. We want to keep track of the user that added the entry for either Book or Author, so we
create a field added_by which is a ForeignKey referencing the AUTH_USER_MODEL.
Now we have our models created, we'll have to run migrations. But before that let's
makemigrations after which we'll then run the created migrations.
Time for a test-drive 🚀. I'll be testing my newly created models in the Django shell by adding some
entries to Author. On the terminal, let's start the shell by running python manage.py shell.
name: which is a character field with a max length of 200, so it can take strings.
added_by: which is referencing the User model. So to make a new entry, we need to pass an
instance of a user.
created_date: which defaults to the current entry time.
So, in the shell we have to import the User and Author models to make adding an entry to Author
possible.
First, we make an instance of User, then we call the save method on the model to save to the Db.
Adding an Author:
To add an author, we make an instance of Author, passing the instance of the user we already
created to added_by
We have successfully added two new authors. To get all entries on the Authors table:
>>> Author.objects.all()
<QuerySet [<Author: Author Mie>, <Author: Author Mello>]>
We can also with our models through the Django admin interface provided by Django which is
accessible at https://localhost:8000/admin. But before we do that we'll first have to:
# bookstore_app/api/admin.py
To create a superuser
A "superuser" account has full access to the server and all needed permissions.
We have successfully created a superuser. Now, run the server and login to the admin page on the
browser using the superuser credentials that you created. After a successful login, your admin
interface will look like the image below. You can now add more Authors and Books even set
permissions, disable certain users and lots more if need be. Of course, you are the superuser!!!
https://localhost:8000/admin
So far, we have been able to persist our data and read from the DB on the shell. It's time to create
some views to handle POST, GET, PUT, DELETE requests on the server. But before we start
adding new views in the api/views.py file, let's create serializers for our models.
Serializers allow complex data such as querysets and model instances to be converted to native
Python datatypes that can then be easily rendered into JSON.
To begin creating our serializers, let's create a serializers.py file in our api app folder and then
create our AuthorSerializer and BookSerializer, selecting the fields that we care about in the
different models that we will pass to the response.
# bookstore_app/api/serializers.py
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = Author
fields = ['id', 'name', 'added_by', 'created_by']
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ['id', 'title', 'description', 'created_date', 'author',
'added_by']
We have our serializer ready, let's open the api/views.py file. The current content of the file
should be from the previous post, Adding Authentication to a REST Framework Django API.
# ./bookstore_app/api/views.py
@api_view(["GET"])
@csrf_exempt
@permission_classes([IsAuthenticated])
def welcome(request):
content = {"message": "Welcome to the BookStore!"}
return JsonResponse(content)
# ./bookstore_app/api/views.py
...
from .serializers import BookSerializer
from .models import Book
from rest_framework import status
@api_view(["GET"])
@csrf_exempt
@permission_classes([IsAuthenticated])
def get_books(request):
user = request.user.id
books = Book.objects.filter(added_by=user)
serializer = BookSerializer(books, many=True)
return JsonResponse({'books': serializer.data}, safe=False,
status=status.HTTP_200_OK)
# ./bookstore_app/api/views.py
...
from .models import Book, Author
import json
from django.core.exceptions import ObjectDoesNotExist
@api_view(["POST"])
@csrf_exempt
@permission_classes([IsAuthenticated])
def add_book(request):
payload = json.loads(request.body)
user = request.user
try:
author = Author.objects.get(id=payload["author"])
book = Book.objects.create(
title=payload["title"],
description=payload["description"],
added_by=user,
author=author
)
serializer = BookSerializer(book)
return JsonResponse({'books': serializer.data}, safe=False,
status=status.HTTP_201_CREATED)
except ObjectDoesNotExist as e:
return JsonResponse({'error': str(e)}, safe=False,
status=status.HTTP_404_NOT_FOUND)
except Exception:
return JsonResponse({'error': 'Something terrible went wrong'},
safe=False, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
User can update a book entry by id
# ./bookstore_app/api/views.py
...
@api_view(["PUT"])
@csrf_exempt
@permission_classes([IsAuthenticated])
def update_book(request, book_id):
user = request.user.id
payload = json.loads(request.body)
try:
book_item = Book.objects.filter(added_by=user, id=book_id)
# returns 1 or 0
book_item.update(**payload)
book = Book.objects.get(id=book_id)
serializer = BookSerializer(book)
return JsonResponse({'book': serializer.data}, safe=False,
status=status.HTTP_200_OK)
except ObjectDoesNotExist as e:
return JsonResponse({'error': str(e)}, safe=False,
status=status.HTTP_404_NOT_FOUND)
except Exception:
return JsonResponse({'error': 'Something terrible went wrong'},
safe=False, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
# ./bookstore_app/api/views.py
...
@api_view(["DELETE"])
@csrf_exempt
@permission_classes([IsAuthenticated])
def delete_book(request, book_id):
user = request.user.id
try:
book = Book.objects.get(added_by=user, id=book_id)
book.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
except ObjectDoesNotExist as e:
return JsonResponse({'error': str(e)}, safe=False,
status=status.HTTP_404_NOT_FOUND)
except Exception:
return JsonResponse({'error': 'Something went wrong'}, safe=False,
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Having completed the views and its functionalites, we'll now add them to the api/urls.py file.
# ./bookstore_app/api/urls.py
urlpatterns = [
...
path('getbooks', views.get_books),
path('addbook', views.add_book),
path('updatebook/<int:book_id>', views.update_book),
path('deletebook/<int:book_id>', views.delete_book)
]
Now, let's get our environment and Django server started. To access the manage.py file, you have
to be in the django project bookstore_app directory.
$ cd bookstore_app
$ pipenv shell
$ python manage.py runserver
You can use Postman to test with the same JSON properties, but I'll be using curl.
> Request
> Response:
{"key":"1565c60a136420bc733b10c4a165e07698014acb"}
You also get an authentication token after a successful login localhost:8000/login/ passing
fields username and password. To test the rest of the endpoints, we need to prove to the server that
we are valid authenticated users. So to do this we'll set the token we got after registration to the
Authorization property in the Headers dict prefixing the actual token with Token.
> Request
$ curl -X POST -H "Authorization: Token
1565c60a136420bc733b10c4a165e07698014acb" -d '{"title":"CRUD Django",
"description":"Walkthrough for CRUD in DJANGO", "author": 1}'
localhost:8000/api/addbook
> Response
{"book": {
"id": 1,
"title": "CRUD Django",
"description": "Walkthrough for CRUD in DJANGO",
"author": 1,
"added_by": 2,
"created_date": "2020-02-29T21:07:27.968463Z"
}
}
Get all books
To get all books, we'll make a GET request to localhost:8000/api/getbooks. This will give us a list of
all book that has been added by the currently logged in user.
> Request
$ curl -X GET -H "Authorization: Token 9992e37dcee4368da3f720b510d1bc9ed0f64fca"
-d '' localhost:8000/api/getbooks
> Response
{"books": [
{
"id": 1,
"title": "CRUD Django",
"description": "Walkthrough for CRUD in DJANGO",
"author": 1,
"added_by": 2,
"created_date": "2020-02-29T21:07:27.968463Z"
}
]
}
Update a book-entry by id
To update a book, we make a PUT request passing the id of the book we want to update as a
parameter on the URL to localhost:8000/api/updatebook/<id> passing fields the fields you
want to alter.
> Request
$ curl -X PUT -H "Authorization: Token 9992e37dcee4368da3f720b510d1bc9ed0f64fca"
-d '{"title":"CRUD Django Updated V2", "description":"Walkthrough for CRUD in
DJANGO", "author": 1}' localhost:8000/api/updatebook/1
> Response
{"book": {
"id": 1,
"title": "CRUD Django Updated V2",
"description": "Walkthrough for CRUD in DJANGO",
"author": 1,
"added_by": 2,
"created_date": "2020-02-29T21:07:27.968463Z"
}
}
Delete a book-entry by id
To delete a book, we make a DELETE request passing the id of the book we want to delete as a
parameter on the URL to localhost:8000/api/deletebook/<id>
> Request
$ curl -X DELETE -H "Authorization: Token
9992e37dcee4368da3f720b510d1bc9ed0f64fca" -d '' localhost:8000/api/deletebook/1
Hurray🎉🎉, we have a fully functional CRUD Django REST API. If you are testing using postman
you might run into an error response { "detail": "CSRF Failed: CSRF token missing or
incorrect." }, clearing Cookies on Postman will fix the issue.
All our code for the last two(2) posts and this post: