I thought I’d show you a straight forward way to build a Django API using Django and Django Rest Framework (DRF). Ive decided to start building a project idea that will help with revision, a system or question cards and answers and score. i am going to keep it simple at first and at a later date make it more ‘realistic’ for use. So this is just an example and some knowledge is required (for instance I am not going to go into installing Python on your machine etc.)
Set up a Django project and create a new app for the API. You can do this using the command django-admin startproject <project_name> and python manage.py startapp <app_name> in your terminal.
Define the models for Question cards, and answer scores in your app’s models.py file. For example, your models could look like this:
from django.db import models from django.contrib.auth.models import User class QuestionCard(models.Model): question = models.CharField(max_length=800) answer = models.CharField(max_length=800) class AnswerScore(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) question_card = models.ForeignKey(QuestionCard, on_delete=models.CASCADE) score = models.IntegerField()
In this example, we’re using the built-in User model provided by Django’s contrib.auth library to store information about users. The User model has fields for common user attributes like username, password, email, and first_name/last_name, but you can customize these fields or add additional ones as needed.
The QuestionCard model represents a question card that can be answered by a user. It has two fields: question and answer.
The AnswerScore model represents a user’s answer to a question card and their score for that answer. It has three fields: user (a foreign key to the User model), question_card (a foreign key to the QuestionCard model), and score.
You can use these models to create tables in your database to store user information, question cards, and answer scores, and then use Django’s ORM to interact with these tables in your views and serializers.
from rest_framework import serializers from django.contrib.auth.models import User from .models import QuestionCard, AnswerScore class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ('id', 'username', 'email', 'first_name', 'last_name', 'password') class QuestionCardSerializer(serializers.ModelSerializer): class Meta: model = QuestionCard fields = ('id', 'question', 'answer') class AnswerScoreSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) question_card = QuestionCardSerializer(read_only=True) class Meta: model = AnswerScore fields = ('id', 'user', 'question_card', 'score')
In this example, the UserSerializer is used to convert the User model into a format that can be easily rendered into JSON or other content types. The UserSerializer includes fields for the user’s id, username, email, first_name, last_name, and password. The password field is write-only, which means it can only be used for creating or updating a user, but not for retrieving user data.
The QuestionCardSerializer is used to convert the QuestionCard model into a format that can be easily rendered into JSON or other content types. The QuestionCardSerializer includes fields for the id, question, and answer of the question card.
The AnswerScoreSerializer is used to convert the AnswerScore model into a format that can be easily rendered into JSON or other content types. The AnswerScoreSerializer includes fields for the id, user, question_card, and score. The user and question_card fields are nested serializers that use the UserSerializer and QuestionCardSerializer, respectively, to convert the foreign key relationships into JSON representations of the related models. The read_only=True option on the nested serializers means that the related models will be read-only when serializing data (i.e., you can’t create or update related models through the serializer).
Then we move on to the views.py
from rest_framework import generics, permissions from django.contrib.auth.models import User from .models import QuestionCard, AnswerScore from .serializers import UserSerializer, QuestionCardSerializer, AnswerScoreSerializer class UserList(generics.ListCreateAPIView): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [permissions.IsAdminUser] class UserDetail(generics.RetrieveUpdateDestroyAPIView): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [permissions.IsAdminUser] class QuestionCardList(generics.ListCreateAPIView): queryset = QuestionCard.objects.all() serializer_class = QuestionCardSerializer class QuestionCardDetail(generics.RetrieveUpdateDestroyAPIView): queryset = QuestionCard.objects.all() serializer_class = QuestionCardSerializer class AnswerScoreList(generics.ListCreateAPIView): queryset = AnswerScore.objects.all() serializer_class = AnswerScoreSerializer class AnswerScoreDetail(generics.RetrieveUpdateDestroyAPIView): queryset = AnswerScore.objects.all() serializer_class = AnswerScoreSerializer class UserSelfUpdate(generics.UpdateAPIView): serializer_class = UserSerializer def get_object(self): return self.request.user class UserSelfDetail(generics.RetrieveAPIView): queryset = User.objects.all() serializer_class = UserSerializer def get_object(self): return self.request.user
In this example, we have UserList and UserDetail views that handle listing and detail views of all users respectively. These views are restricted to admin users only.
We also have UserSelfUpdate and UserSelfDetail views that allow a user to update their own user data and view their own user details, respectively. These views are only accessible by authenticated users.
The QuestionCardList and QuestionCardDetail views handle listing and detail views for question cards, respectively.
The AnswerScoreList and AnswerScoreDetail views handle listing and detail views for answer scores, respectively.
All of the views except for UserList and UserDetail inherit from Django REST framework’s built-in generics views, which provide default implementations of the most common RESTful CRUD operations.
You’ll notice that the UserSelfUpdate view overrides the get_object() method to return the currently authenticated user, so that they can only update their own user data.
The UserSelfDetail view also overrides the get_object() method to return the currently authenticated user. This is because the URL for this view includes the user’s own ID (e.g., /users/me/), so we need to manually retrieve the user object instead of relying on the default object lookup by ID.
Then we move onto the urls.py
from django.urls import path from rest_framework.authtoken.views import obtain_auth_token from .views import UserList, UserDetail, QuestionCardList, QuestionCardDetail, AnswerScoreList, AnswerScoreDetail, UserSelfUpdate, UserSelfDetail urlpatterns = [ path('api-token-auth/', obtain_auth_token, name='api_token_auth'), path('users/', UserList.as_view(), name='user-list'), path('users/<int:pk>/', UserDetail.as_view(), name='user-detail'), path('users/me/', UserSelfDetail.as_view(), name='user-self-detail'), path('users/me/update/', UserSelfUpdate.as_view(), name='user-self-update'), path('question-cards/', QuestionCardList.as_view(), name='question-card-list'), path('question-cards/<int:pk>/', QuestionCardDetail.as_view(), name='question-card-detail'), path('answer-scores/', AnswerScoreList.as_view(), name='answer-score-list'), path('answer-scores/<int:pk>/', AnswerScoreDetail.as_view(), name='answer-score-detail'), ]
In this example, we’re defining paths for all of the endpoints we’ve created so far. We’re also including a path for Django REST framework’s built-in obtain_auth_token view, which is used for obtaining an authentication token for a user based on their username and password.
The UserList and UserDetail paths include an integer parameter pk that represents the ID of the user being requested.
The UserSelfDetail and UserSelfUpdate paths don’t include a pk parameter, since they operate on the currently authenticated user.
All of the other paths include an integer parameter pk that represents the ID of the object being requested or modified (i.e., a question card or answer score).
You can customize the URLs and URL patterns as needed to fit your specific API requirements.
Here’s an example settings.py file that includes the settings for the rest_framework app and the app we created for the API:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', '<app_name>', # replace with your app name 'rest_framework', ] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } } REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ] }
In this example, we’re adding our app to the INSTALLED_APPS list and configuring the database settings for SQLite.
We’re also configuring the rest_framework app to use token authentication and require authentication for all requests by default.
To start the server, you can run the following command in your terminal:
python manage.py runserver
This will start the development server on http://localhost:8000/ by default. You can then access the API endpoints you’ve defined by visiting the corresponding URLs in your web browser or using a tool like curl or Postman to send requests programmatically.
Upon getting your API working to your satisfaction then we’ll build the react frontend for it.
Happy Coding!
Quick Links
Legal Stuff