社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Django

Django 3 + Vue.js 前后端分离Web开发实战

编程派 • 5 年前 • 1926 次点击  

点击上方“编程派”,选择设为“设为星标

优质文章,第一时间送达!


学习全文大概需要 12分钟,内容实战性较强。


1. 前言


本篇将基于Python 3.7+Django 3.0结合Vue.js前端框架,为大家介绍如何基于这三者的技术栈来实现一个前端后离的Web开发项目。为了简化,方便读者理解,本文将以开发一个单体页面应用作为实战演示。



2. 先搞清楚什么是前后端分离


在正式开始实战示例之前,我们有必要先弄清楚一个概念:什么是前后端分离?


前后端分离目前已成为互联网项目开发的业界标准使用方式,在聊前后端分离之前,相信也有很多读者,对如何区分前端还是后端,还搞不清楚(是不是让我戳中了你的痛处了)。本着“致良知”,先科谱一下知识。


通常情况下,我们说的前端,大多是指浏览器这一端,一般是用Html+CSS+JS来实现的,所以通常会引申为用Html+CSS+JS写的大部分程序都是前端,包括App,小程序,H5等。


PS: 在NodeJS出现之后,用NodeJS写的后端部分,也会被人归类为前端,为了区分之前的前端,就给他们起了一个名字,叫做“大前端”。


久而久之,人们习惯把Html+CSS+JS,运行在浏览器端执行的,称之为前端。

而Java,C,Python,PHP这些运行在服务器端的,统一称之为后端。


但,这种以语言为分界点去区分前后端,真的合理么? 显然不合理!


前后端的定义,不应该是以语言来定义,而是应该以它的运行环境,如果是在服务器端,就应该被称之为后端,代表着你看不见,摸不着。而如果运行在用户端,就应该被称之为前端,代表你是可以看得到的。


在不分前后端的时候,无论是Java还是JS,全都是一个人来写。

为什么现在很多互联网公司在项目开发时,建议要进行前后端分离,或者说前后端分离能带来哪些优势?(好处多多,这里仅提两个点)

  • 第一个,并行开发、独立部署、实现前后端解前后端的进度互不影响,在过去,前后端不分离的情况下,项目代码耦合严重相互影响,且前后端人员工作量分布不均。


  • 第二个,术业有专攻(开发人员分离)以前的JavaWeb项目大多数都是Java程序员又当爹又当妈,又搞前端,又搞后端。前后端分离之后,前端工程师只管前端的事情,后端工程师只管后端的事情。



我们先看看一个 Web 系统,在前后端不分离时架构设计是什么样的。

用户在浏览器上发送请求,服务器端接收到请求,根据 Header 中的 token 进行用户鉴权,从数据库取出数据,处理后将结果数据填入 HTML 模板,返回给浏览器,浏览器将 HTML 展现给用户。



而采用前后端分离之后,分离的是人员职责,人员职责分离了,因此架构也发生变化。



前后端分离后,前端人员和后端人员约定好接口,前端人员不用再关心业务处理是怎么回事,他只需要把界面做好就可以了,后端人员也不用再关系前端界面是什么样的,他只需要做好业务逻辑处理即可。



小结一下,前后端分离是什么?

前后端分离是一种架构模式,或者说是最佳实践,它主张将前端开发人员和后端开发人员的工作进行解耦,尽量减少他她们之间的交流成本,帮助他她们更能专注于自己擅长的工作。



PS: 本篇实战示例,使用Vue.js作为前端框架,代替Django本身自带的模板引擎,Django则作为服务端提供API接口,从而实现前后端分离。



3. 环境准备


本实战示例,基础环境对应安装版本如下:

  • Python 3.7.4

  • Mysql 5.7

  • Pycharm (建议专业版)

  • Node


PS: 其中Python、Mysql、Pycharm、Node安装过程皆较为简单,不是本文介绍重点,读者可直接参考官网安装方法。


4. 新建独立的虚拟开发环境


1、创建一个用于Django项目开发的独立虚拟环境,切换到本地开发目录,输入如下命令:

python3 -m venv venv


2、创建完成后,目录结构如下:

➜  venv tree -L 2.├── bin│   ├── activate│   ├── activate.csh│   ├── activate.fish│   ├── easy_install│   ├── easy_install-3.7│   ├── pip│   ├── pip3│   ├── pip3.7│   ├── python -> python3│   └── python3 -> /usr/local/bin/python3├── include├── lib│   └── python3.7└── pyvenv.cfg
4 directories, 11 files


3、进入到bin目录,输入命令source activate 命令,激活虚拟环境。



4、虚拟环境激活后,如上图所示。接下来,在虚拟环境安装Django库。


安装Django (最新版本为3.0)

(venv) ➜  pip install Django


Django 项目源码:

https://github.com/django/django



Django3.0 版本特性可查阅官网:

https://docs.djangoproject.com/en/3.0/releases/3.0/


5、安装完成后,可检查一下版本信息:

(venv) ➜  pythonPython 3.7.4 (default, Jul  9 2019, 18:15:00)[Clang 10.0.0 (clang-1000.11.45.5)] on darwinType "help", "copyright", "credits" or "license" for more information.>>> import django>>> print(django.get_version())3.0

可以发现,在虚拟环境中已经成功安装好了Django 3.0。



5. 创建Django后端项目


1、创建Django项目,采用Pycharm或者命令行创建皆可。此处,以命令行方式作为演示,项目名为django_vue。

(venv) ➜ django-admin startproject django_vue

2. Django项目创建完成后,目录结构如下所示。

├── django_vue│   ├── django_vue│   │   ├── __init__.py│   │   ├── asgi.py│   │   ├── settings.py│   │   ├── urls.py│   │   └── wsgi.py│   └── manage.py


3、执行同步数据库文件(Django默认数据库为db.sqlite3),执行同步过程如下:

(venv) ➜  python manage.py migrateOperations to perform:  Apply all migrations: admin, auth, contenttypes, sessionsRunning migrations:  Applying contenttypes.0001_initial... OK  Applying auth.0001_initial... OK  Applying admin.0001_initial... OK  Applying admin.0002_logentry_remove_auto_add... OK


    
  Applying admin.0003_logentry_add_action_flag_choices... OK  Applying contenttypes.0002_remove_content_type_name... OK  Applying auth.0002_alter_permission_name_max_length... OK  Applying auth.0003_alter_user_email_max_length... OK  Applying auth.0004_alter_user_username_opts... OK  Applying auth.0005_alter_user_last_login_null... OK  Applying auth.0006_require_contenttypes_0002... OK  Applying auth.0007_alter_validators_add_error_messages... OK  Applying auth.0008_alter_user_username_max_length... OK  Applying auth.0009_alter_user_last_name_max_length... OK  Applying auth.0010_alter_group_name_max_length... OK  Applying auth.0011_update_proxy_permissions... OK  Applying sessions.0001_initial... OK


4、启动Django Server ,验证默认配置是否正常。

(venv) ➜   python manage.py runserver 0.0.0.0:8000Watching for file changes with StatReloaderPerforming system checks...
System check identified no issues (0 silenced).December 15, 2019 - 08:36:28Django version 3.0, using settings 'django_vue.settings'Starting development server at http://0.0.0.0:8000/Quit the server with CONTROL-C.


5、打开浏览器,访问http://localhost:8000,一切正常的话,可见到如下界面。



6. 将Django数据库更换为Mysql


1、假设在前面,我们已经安装配置好了Mysql,输入如下命令进入到Mysql。

mysql -u root -p


2、创建数据库,数据库取名为django_vue_db,并设置字符集为utf-8。

mysql> CREATE DATABASE django_vue_db CHARACTER SET utf8;Query OK, 1 row affected (0.01 sec)


3、安装myslqclient库

(venv) ➜  pip install mysqlclient


4、配置settings.py文件,配置Mysql数据库引擎。

```python
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql',        'NAME''django_vue_db',        'USER''root',        'PASSWORD''xxxxxxx', 'HOST': '127.0.0.1', }}```


5、执行同步操作,将数据迁移到Mysql。

python manage.py migrate


6、验证是否切库成功,进入到Mysql客户端,查看django初化表是否有生成。

mysql> use django_vue_db;Database changedmysql> show tables;+----------------------------+| Tables_in_django_vue_db    |+----------------------------+| auth_group                 || auth_group_permissions     || auth_permission            || auth_user                  || auth_user_groups           || auth_user_user_permissions |


    
| django_admin_log           || django_content_type        || django_migrations          || django_session             |+----------------------------+10 rows in set (0.00 sec)


7、运行Django Server,重新访问http://localhost:8000。

python manage.py runserver 0.0.0.0:8000

如果能正常访问,过程没有报错,说明切换数据库已经成功了。



7. 创建Django实战项目App


1、创建Django App,进入django_vue项目主目录,输入如下命令:

(venv) ➜  python manage.py startapp api_test


2、App创建完成后,目录结构如下所示:

├── api_test│   ├── __init__.py│   ├── admin.py│   ├── apps.py│   ├── migrations│   │   └── __init__.py│   ├── models.py│   ├── tests.py│   └── views.py


并把api_test加入到settings文件中的installed_apps列表里:

```pythonINSTALLED_APPS = [    'django.contrib.admin',    'django.contrib.auth',    'django.contrib.contenttypes',    'django.contrib.sessions',    'django.contrib.messages',    'django.contrib.staticfiles',    'api_test',]```


3、 在api_test目录下的models.py里我们简单写一个model如下:

```python# -*- coding: utf-8 -*-from __future__ import unicode_literalsfrom django.db import modelsclass Book(models.Model):    book_name = models.CharField(max_length=128)    add_time = models.DateTimeField(auto_now_add=True)
def __unicode__(self): return self.book_name```


只有两个字段,书名book_name和添加时间add_time。如果没有指定主键的话Django会自动新增一个自增id作为主键。


4、在api_test目录下的views里我们新增两个接口,一个是show_books返回所有的书籍列表(通过JsonResponse返回能被前端识别的json格式数据),二是add_book接受一个get请求,往数据库里添加一条book数据。

```pythonfrom django.shortcuts import renderfrom django.views.decorators.http import require_http_methodsfrom django.core import serializersfrom django.http import JsonResponseimport json
from .models import Book
@require_http_methods(["GET"]) def add_book(request): response = {} try: book = Book(book_name=request.GET.get('book_name')) book.save() response['msg'] = 'success' response['error_num'] = 0 except Exception as e: response['msg'] = str(e) response['error_num'] = 1 return JsonResponse(response)
@require_http_methods(["GET"])def show_books(request): response = {} try: books = Book.objects.filter() response['list'] = json.loads(serializers.serialize("json", books)) response['msg'] = 'success' response['error_num'] = 0 except Exception as e: response['msg'] = str(e) response['error_num'] = 1 return JsonResponse(response)```


可以看出,在ORM的帮忙下,我们的接口实际上不需要自己去组织SQL代码。


5、在api_test目录下,新增一个urls.py文件,把我们新增的两个接口添加到路由里:

from django.conf.urls import url, includefrom .views import *
urlpatterns = [ url(r"add_book$", add_book, ), url(r"show_books$", show_books, ),]```


6、我们还要把api_test下的urls添加到项目django_vue下的urls中,才能完成路由:

```pythonfrom django.contrib import adminfrom django.urls import pathfrom django.conf.urls import url, includefrom django.contrib import adminfrom django.views.generic import TemplateViewimport api_test.urls
urlpatterns = [ url(r"^admin/", admin.site.urls), url(r'^api/', include(api_test.urls)),]```


7、在项目的根目录,输入命令:

python manage.py makemigrations api_testpython manage.py migrate


8、查询数据库,看到book表已经自动创建了:

mysql> show tables;+----------------------------+| Tables_in_django_vue_db    |+----------------------------+| api_test_book              || auth_group                 || auth_group_permissions     || auth_permission            || auth_user                  || auth_user_groups           || auth_user_user_permissions || django_admin_log           || django_content_type        || django_migrations          || django_session             |+----------------------------+11 rows in set (0.


    
00 sec)mysql> desc api_test_book;+-----------+--------------+------+-----+---------+----------------+| Field     | Type         | Null | Key | Default | Extra          |+-----------+--------------+------+-----+---------+----------------+| id        | int(11)      | NO   | PRI | NULL    | auto_increment || book_name | varchar(128) | NO   |     | NULL    |                || add_time  | datetime(6)  | NO   |     | NULL    |                |+-----------+--------------+------+-----+---------+----------------+3 rows in set (0.01 sec)mysql>```

Django生成的表名将以app名加上model中的类名组合而成。


9、在项目的根目录,输入命令:

python manage.py runserver 0.0.0.0:800

启动服务,通过httpie测试一下我们刚才写的两个接口。


10、通过调用接口向Django App中添加两条书名记录。

```shell➜  http http://127.0.0.1:8000/api/add_book\?book_name\=mikezhou_talkHTTP/1.1 200 OKContent-Length: 34Content-Type: application/jsonDate: Sun, 15 Dec 2019 09:11:12 GMTServer: WSGIServer/0.2 CPython/3.7.4X-Content-Type-Options: nosniffX-Frame-Options: DENY{    "error_num": 0,    "msg": "success"}

➜ http http://127.0.0.1:8000/api/add_book\?book_name\=测试开发技术HTTP/1.1 200 OKContent-Length: 34Content-Type: application/jsonDate: Sun, 15 Dec 2019 09:11:44 GMTServer: WSGIServer/0.2 CPython/3.7.4X-Content-Type-Options: nosniffX-Frame-Options: DENY{ "error_num": 0, "msg": "success"}```


11、通过调用接口,显示Django App中所有书名列表:

```shell➜  http http://127.0.0.1:8000/api/show_booksHTTP/1.1 200 OKContent-Length: 305Content-Type: application/jsonDate: Sun, 15 Dec 2019 09:13:48 GMTServer: WSGIServer/0.2 CPython/3.7.4X-Content-Type-Options: nosniffX-Frame-Options: DENY{    "error_num": 0,    "list": [        {            "fields": {                "add_time": "2019-12-15T09:11:12.673Z",                "book_name": "mikezhou_talk"            },            "model": "api_test.book",            "pk": 1        },        {            "fields": {                "add_time": "2019-12-15T09:11:44.305Z",                "book_name": "测试开发技术"          },            "model": "api_test.book",            "pk": 2        }    ],


    
    "msg": "success"}```



8. 新建Vue.js前端项目


1、有关Vue的模块(包括vue)可以使用node自带的npm包管理器安装。推荐使用淘宝的 cnpm 命令行工具代替默认的 npm。

npm install -g cnpm --registry=https://registry.npm.taobao.org


2、先用cnpm安装vue-cli脚手架工具(vue-cli是官方脚手架工具,能迅速帮你搭建起vue项目的框架):

cnpm install -g vue-cli


3、安装好后,在django_vue项目根目录下,新建一个前端工程目录:

vue-init webpack frontend

在创建项目的过程中会弹出一些与项目相关的选项需要回答,按照真实情况进行输入即可。


4、安装 vue 依赖模块

cd frontendcnpm installcnpm install  vue-resourcecnpm install element-ui

5、现在我们可以看到整个文件目录结构是这样的:

本文为了读者方便查看,是直接将vue前端工程放在django项目目录下,实际多人协作开发过程中,完全是可以放在不同代码仓库下面的。


6、在frontend目录src下包含入口文件main.js,入口组件App.vue等。后缀为vue的文件是Vue.js框架定义的单文件组件,其中标签中的内容可以理解为是类html的页面结构内容。


7、在src/component文件夹下新建一个名为Home.vue的组件,通过调用之前在Django上写好的api,实现添加书籍和展示书籍信息的功能。在样式组件上我们使用了饿了么团队推出的element-ui,这是一套专门匹配Vue.js框架的功能样式组件。由于组件的编码涉及到了很多js、html、css的知识,并不是本文的重点,因此在此只贴出部分代码:

Home.vue文件代码:

```vue
<template><div class="home"><el-row display="margin-top:10px"><el-input v-model="input" placeholder="请输入书名" style="display:inline-table; width: 30%; float:left">el-input><el-button type="primary" @click="addBook()" style="float:left; margin: 2px;">新增el-button>el-row><el-row><el-table :data="bookList" style="width: 100%" border><el-table-column prop="id" label="编号" min-width="100"><template slot-scope="scope"> {{ scope.row.pk }} template >el-table-column><el-table-column prop="book_name" label="书名" min-width="100"><template slot-scope="scope"> {{ scope.row.fields.book_name }} template>el-table-column><el-table-column prop="add_time" label="添加时间" min-width="100"><template slot-scope="scope"> {{ scope.row.fields.add_time }} template>el-table-column>el-table> el-row> div>template>
<script>export default { name: 'home', data () { return { input: '', bookList: [] } }, mounted: function () { this.showBooks() }, methods: { addBook () { this.$http.get('http://127.0.0.1:8000/api/add_book?book_name=' + this.input) .then((response) => { var res = JSON.parse(response.bodyText) if (res.error_num === 0) { this.showBooks() } else { this.$message.error('新增书籍失败,请重试') console.log(res['msg']) } }) }, showBooks () { this.$http.get('http://127.0.0.1:8000/api/show_books') .then((response) => { var res = JSON.parse(response.bodyText) console.log(res) if (res.error_num === 0) { this.bookList = res['list'] } else { this.$message.error('查询书籍失败') console.log(res['msg']) } }) } }}script>
<style scoped> h1, h2 { font-weight: normal; }
ul { list-style-type: none; padding: 0;}
li { display: inline-block; margin: 0 10px;}
a { color: #42b983;}style>```


8、在src/router目录的index.js中,我们把新建的Home组件,配置到vue-router路由中:

```jsimport Vue from 'vue'import Router from 'vue-router'// import HelloWorld from '@/components/HelloWorld'import Home from '@/components/Home'Vue.use(Router)export default new Router({  routes: [    {      path: '/',      name: 'Home',      component: Home    }  ]})```

9、在src/main.js文件中,导入element-ui、vue-resource库。

```jsimport Vue from 'vue'import App from './App'import router from './router'import ElementUI from 'element-ui'import VueResource from 'vue-resource'import 'element-ui/lib/theme-chalk/index.css'Vue.use(ElementUI)Vue.use(VueResource)Vue.config.productionTip = false
/* eslint-disable no-new */new Vue({ el: '#app', router, components: { App }, template: ''})```


10、如果出现跨域问题,需要在Django层注入header,用Django的第三方包django-cors-headers来解决跨域问题:

pip install django-cors-headers


settings.py 修改:

```pythonMIDDLEWARE = [    'django.middleware.security.SecurityMiddleware',    'django.contrib.sessions.middleware.SessionMiddleware',    'corsheaders.middleware.CorsMiddleware',    'django.middleware.common.CommonMiddleware',    'django.middleware.csrf.CsrfViewMiddleware',    'django.contrib.auth.middleware.AuthenticationMiddleware',    'django.contrib.messages.middleware.MessageMiddleware',    'django.middleware.clickjacking.XFrameOptionsMiddleware',]
CORS_ORIGIN_ALLOW_ALL = True```


PS: 注意中间件的添加顺序。


12、在前端工程frontend目录下,输入npm run dev启动node自带的服务器,浏览器会自动打开, 我们能看到页面:



13、尝试新增书籍,如填入:“自动化测试实战宝典”,新增的书籍信息会实时反映到页面的列表中,这得益于Vue.js的数据双向绑定特性。



14、在前端工程frontend目录下,输入npm run build,如果项目没有错误的话,就能够看到所有的组件、css、图片等都被webpack自动打包到dist目录下了:



9. 整合Django和Vue.js前端


目前我们已经分别完成了Django后端和Vue.js前端工程的创建和编写,但实际上它们是运行在各自的服务器上,和我们的要求是不一致的。因此我们须要把Django的TemplateView指向我们刚才生成的前端dist文件即可。


1、 找到project目录的urls.py,使用通用视图创建最简单的模板控制器,访问 『/』时直接返回 index.html:

```pythonurlpatterns = [    url(r"^admin/", admin.site.urls),    url(r'^api/', include(api_test.urls)),    url(r'^$', TemplateView.as_view(template_name="index.html")),]```

2、上一步使用了Django的模板系统,所以需要配置一下模板使Django知道从哪里找到index.html。在project目录的settings.py下:

```pythonTEMPLATES = [    {        'BACKEND': 'django.template.backends.django.DjangoTemplates',        'DIRS':['frontend/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',            ],        },    },]```

3、 我们还需要配置一下静态文件的搜索路径。同样是project目录的settings.py下:

```python# Add for vuejsSTATICFILES_DIRS = [    os.path.join(BASE_DIR, "frontend/dist/static"),]```


4、 配置完成,我们在project目录下输入命令python manage.py runserver,就能够看到我们的前端页面在浏览器上展现:


注意此时服务的端口已经是Django服务的8000而不是node服务的8080了,说明我们已经成功通过Django集成了Vue前端工程。


该实战示例为大家充分展示了现在主流的前后端分离方式,由前端框架,如Vue.js来构建实现前端界面,再通过后端框架,如Django来实现API数据提供,两者通过接口进行通讯、相辅相成、最终实现一个完整Web项目。


声明:前后端分离图片来源于网络,如有侵权,请联系删除。


回复下方「关键词」,获取优质资源


回复关键词「 pybook03」,立即获取主页君与小伙伴一起翻译的《Think Python 2e》电子版

回复关键词「入门资料」,立即获取主页君整理的 10 本 Python 入门书的电子版

回复关键词「m」,立即获取Python精选优质文章合集

回复关键词「book 数字」,将数字替换成 0 及以上数字,有惊喜好礼哦~


推荐阅读



题图:pexels,CC0 授权。

好文和朋友一起看~
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/51749
 
1926 次点击