Skip to content

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:

idProvinceArea (10,000 km²)Population (100M)GDP (Trillion CNY)
1Guangdong17.981.129.73
2Jiangsu10.260.809.26
3Shandong15.701.007.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 descending

Serializer

# 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 descending

URL 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 = True

You 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 response

Front-End Example (Vue + axios)

Install dependencies:

npm create vite@latest frontend -- --template vue
cd frontend
npm install axios

Wrap 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:

  1. Create a Django project and add the province application.
  2. Insert the sample records into the database (using python manage.py shell or fixtures).
  3. 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
Exercise 2: Vue Front End and CORS

Building on the back end from this article:

  1. Create a Vue project, call the provinces list endpoint via axios, and display the data in a table.
  2. Add “Create”, “Edit”, and “Delete” buttons to the table to implement full front-end CRUD interactions.
  3. 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
  1. Add a click event on the “GDP” column header to toggle between ascending and descending order (append the ordering parameter to the request URL).
  2. Add a search box that supports fuzzy name search (append the search parameter to the request URL).
  3. 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.

  1. Configure rest_framework.authtoken and generate a token for a test user.
  2. Add IsAuthenticatedOrReadOnly permission to ProvinceViewSet.
  3. Test with Postman: Can an unauthenticated GET succeed? Does an unauthenticated POST return 401? Does a POST with a valid token succeed?
Last updated on