首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >全栈“食”代:用 Django + Nuxt 实现美食分享网站(一)

全栈“食”代:用 Django + Nuxt 实现美食分享网站(一)

作者头像
一只图雀
发布2020-04-07 15:05:02
1.5K0
发布2020-04-07 15:05:02
举报
文章被收录于专栏:图雀社区图雀社区图雀社区

Django 作为 Python 社区最受欢迎的 Web 框架之一,凭借其高度抽象的组件和强大方便的脚手架,将快速且流畅的开发体验演绎到了极致。而 Nuxt 作为从 Vue.js 进化而来的前端框架,能够轻松胜任复杂的 SPA(单页应用)开发。两者相遇,能够擦出怎样的火花?这篇教程将用 Django + Nuxt 实现带有完整的增删改查(CRUD)功能的全栈应用。最后郑重警告:不要在深夜阅读此教程!!!

本文所涉及的源代码都放在了 Github[1] 上,如果您觉得我们写得还不错,希望您能给❤️这篇文章点个在看+Github仓库加星❤️哦~ 本文代码改编自 Scotch[2]

项目初始化

在这一系列教程中,我们将会实现一个全栈美食分享网站,后端用 Django 实现,前端则是 Nuxt 框架,下面是最终完成后的项目效果:

预备知识

本教程假定你已经知道了

  • 基本的 Python 3 语言知识,包括使用 pip 安装包
  • Django 框架的基础概念(MTV 架构),可参考这篇教程[3]进行学习
  • Vue 的基础概念,以及用 npm 工具链的使用,可参考这篇教程[4]
  • 前后端分离的基本概念,包括前端如何通过发起 HTTP(S) 请求从后端获取数据

学习目标

学完这篇教程后,你将:

  • 了解用 pipenv 工具管理 Python 依赖
  • 学会用 Django REST Framework 快速开发 REST API
  • 学会用 Nuxt 框架快速开发 SPA(单页应用),能够从后端获取数据并渲染

用 pipenv 初始化 Python 环境

首先创建项目目录,并进入:

$ mkdir recipes_app && cd recipes_app

在这个项目中,我们用 pipenv[5] 来管理 Python 项目的环境依赖。Pipenv 是 Python 社区偶像级大师 Kenneth Reitz 牵头开发的开发流程优化工具,立志集所有项目管理工具(Node 的 npm、Ruby 的 bundler、PHP 的 composer 等等)的优势为一体。我们通过下面的命令安装 pipenv,并创建项目的依赖环境:

$ pip install pipenv
$ pipenv shell

如果看到命令提示符前面出现 (recipes_app-nV3wuGJ1) 的提示(后面那串随机字符串可能不一样),就表明我们已经成功地创建了项目独有的虚拟环境!我们接着安装 Django “三件套”:

  • Django: Django 框架本身,提供了丰富且强大的服务器开发组件;
  • DRF (Django Rest Framework):Django 框架的超级搭档,大大方便了 REST API 的开发;
  • Django CORS Headers:用于实现跨域资源请求(CORS)的 Django 中间件(如果你不了解 CORS,可以参考阮一峰的日志[6])。

安装命令如下:

(recipes_app-nV3wuGJ1) $ pipenv install django django-rest-framework django-cors-headers

这时 pipenv 便产生了 Pipfile 文件,它的作用就类似 Node 项目中的 package.json 文件:

[[source]]
url = "https://mirrors.aliyun.com/pypi/simple/"
verify_ssl = true
name = "pypi"

[packages]
django = "*"
django-rest-framework = "*"
django-cors-headers = "*"

[dev-packages]

[requires]
python_version = "3.6"

然后用 Django 脚手架创建服务器项目 api 的基本结构,并进入到 api创建一个子应用 core

(recipes_app-nV3wuGJ1) $ django-admin startproject api
(recipes_app-nV3wuGJ1) $ cd api
(recipes_app-nV3wuGJ1) $ python manage.py startapp core

接着进行数据库迁移,并创建用于登录后台管理的超级用户:

(recipes_app-nV3wuGJ1) $ python manage.py migrate
(recipes_app-nV3wuGJ1) $ python manage.py createsuperuser

按照问题输入信息即可。要记住用户名和密码哦!然后运行开发服务器:

(recipes_app-nV3wuGJ1) $ python manage.py runserver

访问 http://localhost:8000/admin[7],可以看到后台管理的登录页面。输入刚才创建的超级用户的用户名和密码,就进入了后台管理系统,如下所示:

熟悉的界面,但是——没什么东西,而且全是英文!别担心,后面我们会一个个搞定。

用 Django 实现 REST API

接下来我们将实现本项目所需要用的所有 API。对,你没有听错,我们会在这一步实现所有后端接口,大概只 10 分钟左右可以敲完!这就是 Django 的宣言:

The web framework for perfectionists with deadlines.

“为赶时间的完美主义者而生!”

全局配置

首先,在全局配置文件 settings.py 中做如下改动:

  1. INSTALLED_APPS 中添加 rest_frameworkcorsheaderscore,前两个分别是 Django Rest Framework 和 Django CORS Headers 的应用,最后一个是我们网站的应用;
  2. MIDDLEWARE 中添加 corsheaders.middleware.CorsMiddleware,注册跨域请求中间件(注意一定要放在最前面!);
  3. 设置 CORS_ORIGIN_WHITELIST,添加跨域请求白名单,这里我们先写上 http://localhost:3000,后面开发前端时将用到;
  4. 设置 LANGUAGE_CODEzh-hans,可以将后台管理设置为中文,非常方便;
  5. 设置 MEDIA_URLMEDIA_ROOT,用于在开发中提供图片资源文件的访问。

具体代码如下:

# ...

INSTALLED_APPS = [
    # 默认的 App ...

    'rest_framework',
    'corsheaders',
    'core',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    # 默认的中间件 ...
]

CORS_ORIGIN_WHITELIST = (
    'http://localhost:3000',
)

# ...

LANGUAGE_CODE = 'zh-hans'

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

实现 core 应用

接下来就是实现 core 这个 Django 应用。实现一个 Django 应用大致都是按照这样的流程:

  1. 定义数据模型(models.py),用于实现和数据库之间的绑定;
  2. 定义后台管理配置(admin.py),用于在后台管理系统中进行操作;
  3. 定义序列化器(serializers.py),仅当实现 REST API 时需要,用于提供数据模型的 JSON 序列化(或其他数据交换格式);
  4. 定义视图(views.py),用于实现具体的业务逻辑;
  5. 定义路由(urls.py),用于定义路由规则,将其映射到相应的视图;
  6. 将应用路由接入全局路由文件(api/urls.py)中。

我们从第一步开始,完成菜谱 Recipe 数据模型如下:

from django.db import models


class Recipe(models.Model):
    DIFFICULTY_LEVELS = (
        ('Easy', '容易'),
        ('Medium', '中等'),
        ('Hard', '困难'),
    )
    name = models.CharField(max_length=120, verbose_name='名称')
    ingredients = models.CharField(max_length=400, verbose_name='食材')
    picture = models.FileField(verbose_name='图片')
    difficulty = models.CharField(choices=DIFFICULTY_LEVELS, max_length=10,
                                  verbose_name='制作难度')
    prep_time = models.PositiveIntegerField(verbose_name='准备时间')
    prep_guide = models.TextField(verbose_name='制作指南')

    class Meta:
        verbose_name = '食谱'
        verbose_name_plural = '食谱'

    def __str__(self):
        return '{} 的食谱'.format(self.name)

其中,class Meta 定义了 Recipe 的元数据;__str__ 方法定义了一个菜谱对象转换为字符串时应该怎样显示。这些设置的作用在打开后台管理系统之后就会很清晰了。想要了解更多关于 Django 数据模型的知识,请参考相关中文文档[8]

第二步,为 core 子应用配置相应的后台管理功能。非常简单,只需注册定义好的 Recipe 模型:

from django.contrib import admin
from .models import Recipe

# Register your models here.
admin.site.register(Recipe)

第三步,定义序列化器 serializers.py(脚手架并不会自动创建,需要手动创建)。序列化器是 Django Rest Framework 提供的功能,能够非常方便地将 Django 数据模型序列化成相应的 JSON 数据格式。在这里,我们定义一个 RecipeSerializer,并在 class Meta 中指定对应的数据模型为刚才创建的 Recipe,并选择相应的字段展示:

from rest_framework import serializers
from .models import Recipe


class RecipeSerializer(serializers.ModelSerializer):

    class Meta:
        model = Recipe
        fields = (
            'id', 'name', 'ingredients', 'picture',
            'difficulty', 'prep_time', 'prep_guide'
        )

第四步,实现视图。这里我们采用开挂模式,直接调用 Django Rest Framework 提供的模型视图集(ModelViewset)直接搞定数据模型的增删改查逻辑:

from rest_framework import viewsets
from .serializers import RecipeSerializer
from .models import Recipe


class RecipeViewSet(viewsets.ModelViewSet):
    serializer_class = RecipeSerializer
    queryset = Recipe.objects.all()

只需指定 serializer_class(序列器类)和 queryset(模型查询集),就自动定义好了模型的添加、删除、查询和修改!虽然视图集非常强大,但是如果要实现更加灵活的业务逻辑,那么还是要为每个接口定义单独的视图类才行。

第五步,实现路由。由于我们上一步使用了视图集,因此只需先调用 DefaultRouter 自动生成相关的路由,然后加入记录路由映射的列表 urlpatterns 中:

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import RecipeViewSet

router = DefaultRouter()
router.register(r'recipes', RecipeViewSet)

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

router 为我们自动生成以下路由:

  • /recipes/:创建食谱(POST 方法)或读取食谱列表(GET方法);
  • /recipes/{id}:获取单个食谱(GET)、更新单个食谱(PUT)或删除食谱(DELETE)。

注意 在 Django 路由定义中不包括 HTTP 方法,具体的 HTTP 方法可以在视图中读取并判断。

最后一步,我们将 core 子应用中的路由接入全局路由:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('core.urls')),
]

没错,关于食谱的增删改查的 API 我们全都实现了!不信?先运行开发服务器:

(recipes_app-nV3wuGJ1) $ python manage.py runserver

由于 Django REST Framework 为我们提供了测试 API 的 Web 界面,因此这里就不用 Postman 等工具进行测试了。用浏览器访问 localhost:8000/api/recipes[9],就进入了如下所示的 API 测试页面:

这个页面的下方还有添加数据(发起 POST 请求)的表单,我们填一些数据,然后点击 POST 按钮:

然后再次访问食谱列表页面,就有我们刚刚添加的食谱了!此外,你还可以尝试访问单个食谱的详情页面(例如 localhost:8000/api/recipes/1[10]),并且可以通过 Web 页面直接修改或删除哦!

用 Nuxt.js 实现网站首页

Django 的 MTV 架构固然优秀,但是随着现在的业务逻辑越来越多地向前端倾斜(也就是现在流行的富前端应用),其中的 T(Template)需要更强大的武器来解决,这里就是我们的第二位主角 Nuxt。

用脚手架初始化 Nuxt 项目

我们将把所有的前端代码放到 client 目录中,不过无需自己创建,我们调用 nuxt 的脚手架来创建前端应用:

$ npx create-nuxt-app client

之后脚手架应用会询问一系列问题,按下面的截图进行选择(当然作者名填自己):

我们对 Nuxt 脚手架生成的目录结构稍作讲解。可以看到 client 目录下有以下子目录:

  • assets:存放图片、CSS、JS 等原始资源文件
  • components:存放 Vue 组件
  • layouts:存放应用布局文件,布局可在多个页面中使用
  • middleware:存放应用的中间件。Nuxt 中的中间件指页面渲染前执行的自定义函数(本教程中不需要)
  • pages:应用的视图和路由。Nuxt 会根据此目录中的 .vue 文件自动创建应用的路由
  • plugins: 存放 JavaScript 插件,用于在应用启动前加载(本教程中不需要)
  • static:存放通常不会改变的静态文件,并且将直接映射到路由(即可通过 /static/picture.png 访问)
  • store:存放 Vuex Store 文件(本教程中不需要)

本项目所用到的图片资源请访问我们的 GitHub 仓库[11],并下载到对应的目录中。

编写前端首页

我们在 client/pages 中创建 index.vue 文件,并在其中实现我们的前端首页:

<template>
  <header>
    <div class="text-box">
      <h1>吃货天堂 ?</h1>
      <p class="mt-3">制作我们喜爱的美食 ❤️ ️</p>
      <nuxt-link class="btn btn-outline btn-large btn-info" to="/recipes">
        查看食谱
        <span class="ml-2">→</span>
      </nuxt-link>
    </div>
  </header>
</template>

<script>
export default {
  head() {
    return {
      title: "首页"
    };
  }
};
</script>

<style>
header {
  min-height: 100vh;
  background-image: linear-gradient(
      to right,
      rgba(0, 0, 0, 0.9),
      rgba(12, 5, 5, 0.4)
    ),
    url("/images/banner.jpg");
  background-position: center;
  background-size: cover;
  position: relative;
}
.text-box {
  position: absolute;
  top: 50%;
  left: 10%;
  transform: translateY(-50%);
  color: #fff;
}
.text-box h1 {
  font-family: cursive;
  font-size: 5rem;
}
.text-box p {
  font-size: 2rem;
  font-weight: lighter;
}
</style>

模板(Template)+ 脚本(Script)+ 样式(Style),经典的 Vue.js 组件。

我们刚刚创建了 pages 目录下的 index.vue 文件,这意味着当访问根路由 / 时,这个文件将被访问到。通过 npm run dev运行我们的前端页面(记得在 client 子目录下运行!),可以看到:

真是让人食欲大开!

数据展示:实现食谱列表

接下来我们将演示如何展示数据,并实现食谱列表页面。

实现 RecipeCard 组件

首先,实现将会在多个页面中反复使用的食谱卡片组件 RecipeCard 如下:

<template>
  <div class="card recipe-card">
    <img :src="recipe.picture" class="card-img-top" />
    <div class="card-body">
      <h5 class="card-title">{{ recipe.name }}</h5>
      <p class="card-text">
        <strong>成分:</strong>
        {{ recipe.ingredients }}
      </p>
      <div class="action-buttons">
        <nuxt-link :to="`/recipes/${recipe.id}/`" class="btn btn-sm btn-success">查看</nuxt-link>
        <nuxt-link :to="`/recipes/${recipe.id}/edit/`" class="btn btn-sm btn-primary">编辑</nuxt-link>
        <button @click="onDelete(recipe.id)" class="btn btn-sm btn-danger">删除</button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: ["recipe", "onDelete"]
};
</script>

<style>
.card-img-top {
  height: 12rem;
  width: 100%;
}

.recipe-card {
  border: none;
  box-shadow: 0 1rem 1.5rem rgba(0, 0, 0, 0.6);
}
</style>

在这个组件中,我们定义了两个 props,分别是 recipe(代表食谱对象)和 onDelete(删除时的回调函数),并在模板中使用这两个成员。

了解 Nuxt 的路由功能

在实现第二个页面之前,我们有必要先了解一下 Nuxt 的路由功能——通过 pages 目录下的文档结构,就可以自动生成 vue-router 的路由器配置!

例如我们这样安排 pages 下面的目录结构?:

pages
├── README.md
├── index.vue
└── recipes
    ├── _id
    │   ├── edit.vue
    │   └── index.vue
    ├── add.vue
    └── index.vue

_id 目录(或者其他以单下划线开头的目录或 .vue 文件)被称作是动态路由(Dynamic Routing),可以接受参数作为 URL 的一部分。上面的 pages 目录自动生成下面的 router

router: {
  routes: [
    {
      name: 'index',
      path: '/',
      component: 'pages/index.vue'
    },
    {
      name: 'recipes',
      path: '/recipes',
      component: 'pages/recipes/index.vue'
    },
    {
      name: 'recipes-add',
      path: '/recipes/add',
      component: 'pages/recipes/add.vue'
    },
    {
      name: 'recipes-id',
      path: '/recipes/:id?',
      component: 'pages/recipes/_id/index.vue'
    },
    {
      name: 'recipes-id-edit',
      path: '/recipes/:id?/edit',
      component: 'pages/recipes/_id/edit.vue'
    }
  ]
}

提示 如果想要更深入地了解 Nuxt 的路由功能,请参考官方文档[12]

实现食谱列表页面

创建食谱列表页面 pages/recipes/index.vue(先使用假数据填充),代码如下:

<template>
  <main class="container mt-5">
    <div class="row">
      <div class="col-12 text-right mb-4">
        <div class="d-flex justify-content-between">
          <h3>吃货天堂</h3>
          <nuxt-link to="/recipes/add" class="btn btn-info">添加食谱</nuxt-link>
        </div>
      </div>
      <template v-for="recipe in recipes">
        <div :key="recipe.id" class="col-lg-3 col-md-4 col-sm-6 mb-4">
          <recipe-card :onDelete="deleteRecipe" :recipe="recipe"></recipe-card>
        </div>
      </template>
    </div>
  </main>
</template>

<script>
import RecipeCard from "~/components/RecipeCard.vue";

const sampleData = [
  {
    id: 1,
    name: "通心粉",
    picture: "/images/food-1.jpeg",
    ingredients: "牛肉, 猪肉, 羊肉",
    difficulty: "easy",
    prep_time: 15,
    prep_guide:
      "Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis, porro. Dignissimos ducimus ratione totam fugit officiis blanditiis exercitationem, nisi vero architecto quibusdam impedit, earum "
  },
  {
    id: 2,
    name: "羊肉串",
    picture: "/images/food-2.jpeg",
    ingredients: "牛肉, 猪肉, 羊肉",
    difficulty: "easy",
    prep_time: 15,
    prep_guide:
      "Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis, porro. Dignissimos ducimus ratione totam fugit officiis blanditiis exercitationem, nisi vero architecto quibusdam impedit, earum "
  },
  {
    id: 3,
    name: "炒饭",
    picture: "/images/banner.jpg",
    ingredients: "牛肉, 猪肉, 羊肉",
    difficulty: "easy",
    prep_time: 15,
    prep_guide:
      "Lorem ipsum dolor sit amet consectetur adipisicing elit. Omnis, porro. Dignissimos ducimus ratione totam fugit officiis blanditiis exercitationem, nisi vero architecto quibusdam impedit, earum "
  }
];

export default {
  head() {
    return {
      title: "食谱列表"
    };
  },
  components: {
    RecipeCard
  },
  asyncData(context) {
    let data = sampleData;
    return {
      recipes: data
    };
  },
  data() {
    return {
      recipes: []
    };
  },
  methods: {
    deleteRecipe(recipe_id) {
      console.log(deleted`${recipe.id}`);
    }
  }
};
</script>

<style scoped>
</style>

打开前端网站,可以看到我们刚才实现的食谱列表页面:

到这儿,我们分别实现了这个全栈食谱网站的前端和后端应用,这篇教程的第一部分也就结束了。在接下来的教程中,我们将实现前后端之间的通信,并进一步实现食谱的详情及添加页面,不见不散!

想要学习更多精彩的实战技术教程?来图雀社区[13]逛逛吧。

本文所涉及的源代码都放在了 Github[14] 上,如果您觉得我们写得还不错,希望您能给❤️这篇文章点个在看+Github仓库加星❤️哦~ 本文代码改编自 Scotch[15]

参考资料

[1]

Github: https://github.com/tuture-dev/recipes_app

[2]

Scotch: https://scotch.io/tutorials/building-a-universal-application-with-nuxtjs-and-django

[3]

教程: https://juejin.im/post/5dff47ec6fb9a0164c7bb171

[4]

这篇教程: https://juejin.im/post/5df39f94518825122030859c

[5]

pipenv: https://pipenv.kennethreitz.org/

[6]

日志: http://www.ruanyifeng.com/blog/2016/04/cors.html

[7]

http://localhost:8000/admin: http://localhost:8000/admin

[8]

中文文档: https://docs.djangoproject.com/zh-hans/2.2/topics/db/models/

[9]

localhost:8000/api/recipes: localhost:8000/api/recipes

[10]

localhost:8000/api/recipes/1: localhost:8000/api/recipes/1

[11]

GitHub 仓库: https://github.com/tuture-dev/recipes_app/tree/master/client/static/images

[12]

官方文档: https://nuxtjs.org/guide/routing

[13]

图雀社区: https://tuture.co/?utm_source=juejin_zhuanlan

[14]

Github: https://github.com/tuture-dev/recipes_app

[15]

Scotch: https://scotch.io/tutorials/building-a-universal-application-with-nuxtjs-and-django

● 一杯茶的时间,上手Django框架开发
● 从零到部署:用Vue和Express实现迷你全栈电商应用(五)
● 用Vue+ElementUI搭建后台管理极简模板

·END·

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 图雀社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 项目初始化
    • 预备知识
      • 学习目标
        • 用 pipenv 初始化 Python 环境
        • 用 Django 实现 REST API
          • 全局配置
            • 实现 core 应用
            • 用 Nuxt.js 实现网站首页
              • 用脚手架初始化 Nuxt 项目
                • 编写前端首页
                • 数据展示:实现食谱列表
                  • 实现 RecipeCard 组件
                    • 了解 Nuxt 的路由功能
                      • 实现食谱列表页面
                        • 参考资料
                        相关产品与服务
                        消息队列 TDMQ
                        消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档