DRF + VUE 开发流程

转载
手把手Django+Vue前后端分离开发入门(附demo)
Vue-cli3和Django本地结合教程

结合两者的内容,内容更完整些。

环境:
WebStorm 2020.1
PyCharm 2020.1
Python 3.8
django 2.x
django-cors-headers-3.3.0
@vue/cli 4.3.1

前言

众所周知,Django对于网站快速开发非常友好,这得益于框架为我们做了很多事情,让我们只需要做一些简单的配置和逻辑即可把网站的功能开发出来。但是,在使用Django的过程中,有一个地方一直是比较难受的,那就是使用Django自带的模版,这种通常需要自己利用HTML+CSS+Jquery的方式总感觉是上一个时代的做法,前后端分离无论对于开发效率、多端支持等等都是很有好处的。所以,本文希望通过一个简单的demo,讲一讲基于Django+Vue的前后端分离开发,将Django作为一个纯后端的服务,为前端Vue页面提供数据支持。

本文采用的django版本号2.2.5,Vue版本2.9.6。

如果文章太长,可先收藏关注一波~

项目准备

  1. 对于django和Vue的安装这里就略过了~
  2. 创建前后端项目:创建一个文件夹,然后命令行创建项目即可,如下图~
image

3. 测试:

命令行进入后端文件夹book_demo,输入下面命令,浏览器登陆127.0.0.1:8000看见欢迎页即成功。

python manage.py runserver

再进入前端文件夹appfront,输入下面命令,浏览器登陆127.0.0.1:8080看见欢迎页即成功。

npm run dev

上面两个命令也是对应前后端项目的启动命令,后面就直接将过程说成启动前/后端项目。

后端实现

为了方便后端的实现,作为django做后端api服务的一种常用插件,django-rest-framework(DRF)提供了许多好用的特性,所以本文demo中也应用一下,命令行输入命令安装:

pip install django-rest-framework

进入book_demo目录,创建一个新的名为books的应用,并在新应用中添加urls.py文件,方便后面的路由配置,命令行输入:

python manage.py startapp books 
cd books
touch urls.py

现在的目录结构如下:

image

下面开始做一些简单的配置:

将DRF和books配置到django项目中,打开项目中的settings.py文件,添加:

# book_demo/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # demo add
    'rest_framework',
    'books',
]

对整个项目的路由进行配置,让访问api/路径时候转到books应用中的urls.py文件配置进行处理。

# book_demo/settings.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('books.urls')), # demo add
] 

下面在books应用中写简单的逻辑,demo只最简单涉及对书本的增删改查。因为这一部分不是本文重点,所以这里只介绍写代码流程和贴代码,对代码不做详细解释:

在models.py文件中写简单数据类Books:

# books/models.py
from django.db import models

class Books(models.Model):
    name = models.CharField(max_length=30)
    author = models.CharField(max_length=30, blank=True, null=True)

在books文件夹中创建serializer.py文件,并写对应序列化器BooksSerializer:

# books/serializer.py
from rest_framework import serializers

from books.models import Books

class BooksSerializer(serializers.ModelSerializer):
    class Meta:
        model = Books
        fields = '__all__'

在views.py文件中写对应的视图集BooksViewSet来处理请求:

# books/views.py
from rest_framework import viewsets

from books.models import Books
from books.serializer import BooksSerializer

class BooksViewSet(viewsets.ModelViewSet):
    queryset = Books.objects.all()
    serializer_class = BooksSerializer

在urls.py文件中写对应的路由映射:

# books/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter

from books import views

router = DefaultRouter()
router.register('books', views.BooksViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

对于books应用中的内容,如果对DRF和Django流程熟悉的同学肯定知道都干了些什么,篇幅关系这里只能简单解释,DRF通过视图集ViewSet的方式让我们对某一个数据类Model可以进行增删改查,而且不同的操作对应于不同的请求方式,比如查看所有books用get方法,添加一本book用post方法等,让整个后端服务是restful的。如果实在看不懂代码含义,只需知道这样做之后就可以通过不同的网络请求对后端数据库的Books数据进行操作即可,后续可以结合Django和DRF官方文档对代码进行学习,或关注本人未来分享的内容。

到这里,可以运行一下后端项目看看效果,命令行运行:

python manage.py makemigrations
python manage.py migrate
python manage.py runserver

得益于DRF提供的api可视化界面,浏览器访问127.0.0.1:8000/api/books/,如果出现了以下界面并添加数据正常,则说明后端的基本逻辑已经ok了~下图为添加了一条数据之后的效果。

image

前端实现

接下来的工作以appfront项目目录为根目录进行,开始写一点前端的展示和表单,希望达到两个目标,一是能从后端请求到所有的books列表,二是能往后端添加一条book数据。说白了就是希望把上面的页面用Vue简单实现一下,然后能达到相同的功能。对于Vue项目中各个文件的功能这里也不多解释,可以参考其文档系统学习。这里只需要知道欢迎页面中的样式是写在App.vue和components/HelloWorld.vue中即可。

这里直接用HelloWorld.vue进行修改,只求功能不追求页面了~

// appfront/src/components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <!-- show books list -->
    <ul>
      <li v-for="(book, index) in books" :key="index" style="display:block">
        {{index}}-{{book.name}}-{{book.author}}
      </li>
    </ul>
    <!-- form to add a book -->
    <form action="">
      输入书名:<input type="text" placeholder="book name" v-model="inputBook.name"><br>
      输入作者:<input type="text" placeholder="book author" v-model="inputBook.author"><br>
    </form>
    <button type="submit" @click="bookSubmit()">submit</button>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App',
      // books list data
      books: [
        {name: 'test', author: 't'},
        {name: 'test2', author: 't2'}
      ],
      // book data in the form
      inputBook: {
        "name": "",
        "author": "",
      }
    }
  },
  methods: {
    loadBooks () {...}, // load books list when visit the page
    bookSubmit () {...} // add a book to backend when click the button
  },
  created: function () {
    this.loadBooks()
  }
}
</script>
...

启动前端项目,浏览器访问127.0.0.1:8080,可以看到刚写的页面已经更新上去了,丑是丑了点,意思到了就行~如下图:

image

前后端联调

敲黑板,重点来了!!上面的页面中数据其实是写死在前端页面的模拟数据,这一节希望通过从后端拿数据并展示在前端页面的方式来完成前后端联调。前后端联调,涉及最多的就是跨域的问题,为了保证安全,通常需要遵循同源策略,即“协议、域名、端口”三者都相同,具体可以看看相关的博客,这里只需知道上述三者相同才算同源即可。

后端部分,对于django的跨域问题,网上比较常用的做法就是利用django-cors-headers模块来解决,这里也不能免俗,操作如下。

先在命令行中进行对应模块的安装:

pip install django-cors-headers

然后在项目中添加该模块:

# books_demo/settings.py
INSTALLED_APPS = [
    ...
    # demo
    'corsheaders',
    ...
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware', # 需注意与其他中间件顺序,这里放在最前面即可
    ...
]

# 支持跨域配置开始
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True 

后端部分告于段落,接下来需要补充一下前端的逻辑,Vue框架现在一般都用axios模块进行网络请求,这里沿用这种方式,下面是在前端项目中操作:

首先命令行安装axios模块,如果没有安装cnpm就还是用npm安装:

cnpm install axios

为了方便管理api请求的各种逻辑,在前端项目的src目录下创建api目录,然后创建api.js和index.js文件。index.js文件是对axios做配置:

// appfront/src/api/index.js
import Vue from 'vue'
import Axios from 'axios'

const axiosInstance = Axios.create({
    withCredentials: true
})

// 通过拦截器处理csrf问题,这里的正则和匹配下标可能需要根据实际情况小改动
axiosInstance.interceptors.request.use((config) => {
    config.headers['X-Requested-With'] = 'XMLHttpRequest'
    const regex = /.*csrftoken=([^;.]*).*$/
    config.headers['X-CSRFToken'] = document.cookie.match(regex) === null ? null : document.cookie.match(regex)[1]
    return config
})

axiosInstance.interceptors.response.use(
    response => {
        return response
    },
    error => {
        return Promise.reject(error)
    }
)

Vue.prototype.axios = axiosInstance

export default axiosInstance

api.js文件是对后端进行请求,可以看到,获取books列表和添加一本book各对应于一个请求:

// appfront/src/api/api.js
import axiosInstance from './index'

const axios = axiosInstance

export const getBooks = () => {return axios.get(`http://localhost:8000/api/books/`)}

export const postBook = (bookName, bookAuthor) => {return axios.post(`http://localhost:8000/api/books/`, {'name': bookName, 'author': bookAuthor})}

然后更新HelloWorld.vue中的处理逻辑:

// appfront/src/components/HelloWorld.vue
<script>
import {getBooks, postBooks} from '../api/api.js'
export default {
  ...
  methods: {
    loadBooks () {
      getBooks().then(response => {
        this.books = response.data
      })
    }, // load books list when visit the page
    bookSubmit () {
      postBooks(this.inputBook.name, this.inputBook.author).then(response => {
        console.log(response)
        this.loadBooks()
      })
    } // add a book to backend when click the button
  },
  ...
}
</script>

至此,一个极其简陋的查询和添加书籍的功能算是完成了~如下图:

image
image

可以看到,列表里面的数据是从后端读取到的,同时前端的提交数据库也能有对应的操作,所以前后端至此是打通了。

打包
现阶段是前后端分开开发,但是当最后要用的时候,还需要把代码合在一起。

首先对前端项目进行打包,这里用Vue的自动打包:

django会认static文件夹,所以没有的话需要在vue.config.js里面配置。
因为vuecli3没有vue.config.js文件,所有需要手动创建

/* django会认static文件夹,所以没有的话需要在vue.config.js里面配置。
因为vuecli3没有vue.config.js文件,所有需要手动创建
*/
module.exports = {
    // 输出目录
    assetsDir: 'static',
};

然后使用命令打包

yarn build

可以看到前端项目中多出了一个dist文件夹,这个就是前端文件的打包结果。需要把dist文件夹复制到books_demo项目文件夹中。


打包成功

然后对settings.py文件进行相应的修改,其实就是帮django指定模版文件和静态文件的搜索地址:

# books_demo/books_demo/settings.py
...
# 基本路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 前端路径
FRONTEND_ROOT = 'frontend/dist'
# 静态资源地址
STATIC_URL = '/static/'
# 模块资源引用地址
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, FRONTEND_ROOT),
    os.path.join(BASE_DIR, FRONTEND_ROOT + '/static/'),
)

# 项目ulrs地址,每个人的项目地址前缀是不一样的
ROOT_URLCONF = 'everything.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'dist')],  # 路径指向前端打包文件dist
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

最后在根urls.py文件中配置一下入口html文件的对应路由:

# books_demo/books_demo/urls.py
...
from django.views.generic.base import TemplateView

urlpatterns = [
    ...
    path('', TemplateView.as_view(template_name='index.html'))
]

重新启动项目,这次用浏览器访问127.0.0.1:8000,即django服务的对应端口即可。

可以看到,项目的交互是正常的,符合我们的预期。

总结

本文以一个非常简单的demo为例,介绍了利用django+drf+vue的前后端分离开发模式,基本可以算是手把手入门。有了这个小demo之后,不管是前端页面还是后端功能,都可以做相应的扩展,从而开发出更加复杂使用的网站。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352