Hero Image of content
Django和Vue结合的前后端开发记录

这篇文章记录前后端结合的最小实践Demo

Django和Vue结合的前后端开发记录

以音乐列表为例,简单的增删改查

Part of Django

初始化项目

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

对我个人来说,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.

Part of Vue3

初始代码整理

优先删除默认代码,在src下创建api目录,创建request.ts和music.ts,前者用于对axios的封装,后者负责处理后端接口逻辑

这里使用Ant-design-vue实现可复用组件

vite.config.ts

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,
  }
})

在main.ts中进行挂载

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')

request.ts

这里使用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