Comprehensive Practice
This article applies the DRF knowledge covered in previous chapters through a Province Information Management System. It walks through the complete implementation pipeline — model design, serializers, viewsets, routing, and CORS configuration — and closes with hands-on exercises.
Project Goals
Build a REST API for province data that supports:
- List retrieval, single-record retrieval, create, update, and delete (standard CRUD)
- Ordering by GDP and name-based search
- Paginated responses
- Cross-origin access from a Vue + axios front end
Sample data structure:
| id | Province | Area (10,000 km²) | Population (100M) | GDP (Trillion CNY) |
|---|---|---|---|---|
| 1 | Guangdong | 17.98 | 1.12 | 9.73 |
| 2 | Jiangsu | 10.26 | 0.80 | 9.26 |
| 3 | Shandong | 15.70 | 1.00 | 7.65 |
Back-End Implementation
Model Definition
# provinces/models.py
from django.db import models
class Province(models.Model):
name = models.CharField(max_length=50, verbose_name="Province name", unique=True)
area = models.FloatField(verbose_name="Area (10,000 km²)")
population = models.FloatField(verbose_name="Population (100 million)")
gdp = models.FloatField(verbose_name="GDP (trillion CNY)")
class Meta:
db_table = "tb_province"
verbose_name = "Province"
ordering = ["-gdp"] # Default: order by GDP descendingSerializer
# provinces/serializers.py
from rest_framework import serializers
from .models import Province
class ProvinceSerializer(serializers.ModelSerializer):
class Meta:
model = Province
fields = "__all__"
extra_kwargs = {
"id": {"read_only": True},
}ViewSet
# provinces/views.py
from rest_framework.viewsets import ModelViewSet
from rest_framework.filters import SearchFilter, OrderingFilter
from rest_framework.pagination import PageNumberPagination
from .models import Province
from .serializers import ProvinceSerializer
class StandardPagination(PageNumberPagination):
page_size = 10
page_size_query_param = "size"
max_page_size = 100
class ProvinceViewSet(ModelViewSet):
queryset = Province.objects.all()
serializer_class = ProvinceSerializer
pagination_class = StandardPagination
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ["name"]
ordering_fields = ["id", "gdp", "population", "area"]
ordering = ["-gdp"] # Default: GDP descendingURL Configuration
# provinces/urls.py
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register("provinces", views.ProvinceViewSet)
urlpatterns = router.urls# Project-level urls.py
from django.urls import path, include
urlpatterns = [
path("api/", include("provinces.urls")),
]CORS Configuration
In a decoupled front-end/back-end architecture, browsers block cross-origin requests. DRF recommends django-cors-headers to resolve this:
pip install django-cors-headers# settings.py
INSTALLED_APPS = [
...
'corsheaders',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # Must come before CommonMiddleware
'django.middleware.common.CommonMiddleware',
...
]
# Allow specific origins (replace with real domain names in production)
CORS_ALLOWED_ORIGINS = [
"http://localhost:5173", # Vite dev server
"http://localhost:3000", # Create React App dev server
]
# Or open all origins during development (disable in production)
# CORS_ALLOW_ALL_ORIGINS = TrueYou can also add CORS headers manually without a third-party package:
class CORSMixin:
"""Mix into a view class to add CORS headers to all responses."""
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(request, response, *args, **kwargs)
response["Access-Control-Allow-Origin"] = "*"
response["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS"
response["Access-Control-Allow-Headers"] = "Content-Type, Authorization"
return responseFront-End Example (Vue + axios)
Install dependencies:
npm create vite@latest frontend -- --template vue
cd frontend
npm install axiosWrap the API calls:
// src/api/provinces.js
import axios from 'axios'
const api = axios.create({
baseURL: 'http://127.0.0.1:8000/api/',
timeout: 5000,
})
export const getProvinces = (params) => api.get('provinces/', { params })
export const createProvince = (data) => api.post('provinces/', data)
export const updateProvince = (id, data) => api.put(`provinces/${id}/`, data)
export const deleteProvince = (id) => api.delete(`provinces/${id}/`)Use in a component:
// src/components/ProvinceTable.vue
<script setup>
import { ref, onMounted } from 'vue'
import { getProvinces, deleteProvince } from '../api/provinces'
const provinces = ref([])
const total = ref(0)
const page = ref(1)
async function load() {
const res = await getProvinces({ page: page.value, size: 10 })
provinces.value = res.data.results
total.value = res.data.count
}
async function remove(id) {
await deleteProvince(id)
load()
}
onMounted(load)
</script>Practice Exercises
Exercise 1: Basic CRUD
Following the back-end example in this article, complete the following tasks:
- Create a Django project and add the province application.
- Insert the sample records into the database (using
python manage.py shellor fixtures). - Start the DRF server and test each operation through the Browsable API:
- GET
/api/provinces/— retrieve the list - POST
/api/provinces/— create a record (e.g., “Beijing”) - GET
/api/provinces/1/— retrieve a single record - PATCH
/api/provinces/1/— update the GDP - DELETE
/api/provinces/1/— delete the record
- GET
Exercise 2: Vue Front End and CORS
Building on the back end from this article:
- Create a Vue project, call the provinces list endpoint via axios, and display the data in a table.
- Add “Create”, “Edit”, and “Delete” buttons to the table to implement full front-end CRUD interactions.
- Configure CORS so that the front end (
localhost:5173) can successfully access the back end (localhost:8000).
Tip: After a create, edit, or delete operation, remember to refresh the table by re-calling the list endpoint.
Exercise 3: GDP Sorting and Search
- Add a click event on the “GDP” column header to toggle between ascending and descending order (append the
orderingparameter to the request URL). - Add a search box that supports fuzzy name search (append the
searchparameter to the request URL). - How should the parameters be combined when both search and ordering are active simultaneously?
Exercise 4: Authentication to Protect Write Operations
Require anonymous users to be read-only (GET), with authenticated users allowed to create, update, and delete.
- Configure
rest_framework.authtokenand generate a token for a test user. - Add
IsAuthenticatedOrReadOnlypermission toProvinceViewSet. - Test with Postman: Can an unauthenticated GET succeed? Does an unauthenticated POST return 401? Does a POST with a valid token succeed?