这篇文章记录前后端结合的最小实践Demo
以音乐列表为例,简单的增删改查
django-admin startproject music_project
创建项目
python manage.py startapp music
创建应用
每次新建应用都需要在项目settings中进行注册
# settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"music",
"corsheaders", # 添加 corsheaders
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware", # 添加 CORS 中间件
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
CORS_ALLOW_ALL_ORIGINS = True
在应用models.py中对对象属性进行注册,继承models.Model,编写属性的类型
from django.db import models
# Create your models here.
class Music(models.Model):
title = models.CharField(max_length=250)
artist = models.CharField(max_length=250)
duration = models.FloatField()
last_day_played = models.DateField()
当模型注册完成后,运行
python .\manage.py makemigrations
检测模型有无变化,生成一次迁移
此时会在应用目录中生成migrations文件夹
再运行
python .\manage.py migrate
生成数据表
此时会在项目目录下生成db.sqlite3数据库文件
# 向数据库批量添加数据
from datetime import datetime
import json
from django.conf import settings
from django.core.management.base import BaseCommand
from django.utils.timezone import make_aware
from music.models import Music
class Command(BaseCommand):
help = 'Create tracks from JSON file'
def handle(self, *args, **kwargs):
# set the path to the datafile
datafile = settings.BASE_DIR / 'data' / 'tracks.json'
assert datafile.exists()
# load the datafile
with open(datafile, 'r') as f:
data = json.load(f)
# create tz-aware datetime object from the JSON string.
DATE_FMT = "%Y-%m-%d %H:%M:%S"
for track in data:
track_date = datetime.strptime(track['last_play'], DATE_FMT)
track['last_play'] = make_aware(track_date)
musics = [Music(**track) for track in data]
Music.objects.bulk_create(musics)
运行 python .\manage.py register_tracks
即可完成初始数据插入。
对我个人来说,NinjaAPI与FastAPI更相似,所以会更偏向于使用NinjaAPI代替Django REST Framework (DRF)
在项目文件中创建scheme.py 用于模板提示
# scheme.py
from ninja import Schema
from datetime import datetime
class MusicSchema(Schema):
artist: str
duration: float
last_day_played: datetime
title: str
class NotFoundSchema(Schema):
message: str = 'Not Found'
在项目文件中创建api.py
from typing import List
from ninja import NinjaAPI
from music.models import Music
from music.scheme import MusicSchema, NotFoundSchema
api = NinjaAPI()
# 查所有
@api.get("/musics", response=List[MusicSchema])
def get_musics(request):
musics = Music.objects.all()
return musics
# 查单个
@api.get("/musics/{id}", response={200: List[MusicSchema], 404: NotFoundSchema})
def get_music(request, id: int):
try:
# pk = PrimaryKey 主键
music: Music = Music.objects.get(pk=id)
return [music]
except Music.DoesNotExist as e:
return 404, {"message": "Not Found"}
# 增
@api.post("/add_music", response={201: MusicSchema})
def add_music(request, music: MusicSchema):
try:
# **自动解包没有对多余属性进行过滤验证
Music.objects.create(**music.dict())
return music
except Exception as e:
return 400, {"message": "Bad Request"}
# 改
@api.put("/musics/update/{id}", response={200: MusicSchema, 404: NotFoundSchema})
def update_music(request, id: int, data: MusicSchema):
try:
music: Music = Music.objects.get(pk=id)
# music.artist = music.artist
# music.duration = music.duration
# music.last_day_played = music.last_day_played
# music.title = music.title
for attribute, value in data.dict().items(): # 遍历字典
setattr(music, attribute, value) # 把对象的xx属性赋值为value
music.save()
return 200, music
except Music.DoesNotExist as e:
return 404, {"message": "Not Found"}
@api.delete("/musics/delete/{id}", response={200: None, 404: NotFoundSchema})
def delete_music(request, id: int):
try:
music: Music = Music.objects.get(pk=id)
music.delete()
return 200
except Music.DoesNotExist as e:
return 404, {"message": "Not Found"}
在项目文件夹中的urls.py中,添加路由
from django.contrib import admin
from django.urls import path
from music.api import api
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", api.urls),
]
musics:1 Access to XMLHttpRequest at 'http://127.0.0.1:8000/api/musics' from origin 'http://127.0.0.1:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
优先删除默认代码,在src下创建api目录,创建request.ts和music.ts,前者用于对axios的封装,后者负责处理后端接口逻辑
这里使用Ant-design-vue实现可复用组件
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
server: {
host: '0.0.0.0',
port: 5173,
strictPort: true,
}
})
import './assets/main.css'
import router from './router'
import { createApp } from 'vue'
import App from './App.vue'
import Antd from 'ant-design-vue'
const app = createApp(App)
app.use(Antd) // 全局注册 Ant Design Vue 组件库
app.use(router)
app.mount('#app')
这里使用ant-design-vue
main.ts中进行挂载
import './assets/main.css'
import { createApp } from 'vue'
import App from './App.vue'
import Antd from 'ant-design-vue'
const app = createApp(App)
app.use(Antd) // 全局注册 Ant Design Vue 组件库
app.mount('#app')
import axios from 'axios'
import { message, Modal } from 'ant-design-vue'
// 创建 axios 实例
const service = axios.create({
baseURL: 'http://127.0.0.1:8000/api', // url = base url + request url
timeout: 5000 // 请求超时
})
// 请求拦截器:携带的 token 字段
service.interceptors.request.use(
config => {
config.headers = config.headers || {}
if (localStorage.getItem('token')) {
config.headers.token = localStorage.getItem('token') || ''
}
return config
},
error => {
console.log(error) // for debug
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
if (response.status !== 200) {
message.error(response.statusText || 'Error', 3)
if (response.status === 404 || response.status === 50012 || response.status === 50014) {
// 显示确认框
Modal.confirm({
title: '您已登出,是否重新登录?',
content: '您可以取消,或者重新登录。',
onOk() {
localStorage.dispatch('user/resetToken').then(() => {
location.reload()
})
},
onCancel() {
console.log('取消登录');
}
})
}
return Promise.reject(new Error(response.statusText || 'Request failed'))
} else {
return response
}
},
error => {
console.log('err' + error) // for debug
message.error(error.message, 3)
return Promise.reject(error)
}
)
export default service