分类目录归档:网站设计

教你部署开源的超漂亮电子商城 — Saleor 实战教程

Saleor 是一个快速发展的开源电子商务平台,基于 Python 和 Django开发,且持续更新中,一点不用担心版本过旧的问题。

它的特点如下:

1.GraphQL API:基于GraphQL实现的前后端分离,属于最前沿的技术。
2.仪表板:管理员可以完全控制用户、流程和产品。
3.订单:订单、发货和退款的综合系统。
4.购物车:高级付款和税收选项,支持折扣和促销活动
5.支付:灵活的 API 架构允许集成任何支付方式。
6.地理自适应:自动支持多国家的结账体验。
7.支持云部署:支持Docker部署。
8.支持谷歌分析:集成了谷歌分析,可以很方便地分析流量去留。

Saleor 仓库地址:
https://github.com/mirumee/saleor

1.Saleor 部署指南

Saleor支持多种运行方式,你可以采用手动安装并运行的方式,也可以使用Docker进行运行,下面介绍全平台通用且最简单的Docker部署方案。

在按照以下说明操作之前,你需要安装Docker DesktopDocker Compose

Docker 部署 Saleor 非常方便,你只需要克隆存储库并构建镜像然后运行服务即可:

# Python 实用宝典
# 克隆存储库
git clone https://github.com/mirumee/saleor-platform.git --recursive --jobs 3
cd saleor-platform
# 构建Docker镜像
docker-compose build

如果你无法成功克隆 Salor 源代码仓库,请在Python实用宝典公众号回复:Saleor 下载全部源代码。

Saleor 使用共享文件夹来启用实时代码重新加载。如果你使用的是WindowsMacOS,则需要:

1.将克隆的 saleor-platform 目录放置到 Docker 的共享目录配置 (Settings -> Shared Drives or Preferences -> Resources -> File sharing)。

2.确保在 Docker 首选项中你有至少 5 GB 的专用内存(设置 -> 高级首选项->资源 -> 高级

执行数据库migrations及打包前端资源:

docker-compose run --rm api python3 manage.py migrate
docker-compose run --rm api python3 manage.py collectstatic --noinput

(可选)使用示例数据填充数据库:

docker-compose run --rm api python3 manage.py populatedb

最后,为自己创建一个管理员帐户:

docker-compose run --rm api python3 manage.py createsuperuser

运行服务:

使用以下命令运行Saleor:

docker-compose up

2.Saleor 架构介绍

如果你要基于 Saleor 进行开发,那么你必须了解它的架构。

Saleor 由三个重要组件组成:

1.Saleor Core, 它是GraphQL API的后端服务器。基于Django开发,数据库采用了PostgreSQL并在Redis中储存了一些缓存信息。

2.Saleor Dashboard, 这是一个可以用来经营商店的仪表盘,它是一个静态网站,因此没有任何自己的后端代码,它是一个与Saleor Core核心服务器对话的React程序。

3.Saleor Storefront, 这是基于React实现的示例商店,你可以自定义这部分代码满足你自己的需求,也可以使用 Saleor SDK 构建自定义店面。

所有三个组件都使用 GraphQL 通过 HTTPS 进行通信。

3.Saleor 扩展开发教程

虽然你可以直接基于Saleor源代码进行开发,但是官方建议不这么做,原因是一旦你的代码和Saleor官方源代码产生冲突,你就很难跟上官方的更新,最终会导致代码没人维护的尴尬局面。

因此Saleor提供了两种添加功能的方式:

1.插件功能:插件提供了一种在Saleor Core上运行附加代码的能力,而且有访问数据库的能力。

2.APPS:基于GraphQL API和Saleor Core开发APP,还可以使用WebHooks订阅事件。

下面我们介绍如何基于插件进行扩展开发。

如上图所示,Saleor Core 提供了一种回调通知事件给插件,插件基于此事件进行相关操作,并与数据库进行交互。

开发插件,你必须继承 BasePlugin 基类,然后重写部分方法,比如下面这个例子重写了 postprocess_order_creation 方法,增加了订单创建时的一些操作:

# Python实用宝典
# custom/plugin.py

from django.conf import settings
from urllib.parse import urljoin

from ..base_plugin import BasePlugin
from .tasks import api_post_request_task


class CustomPlugin(BasePlugin):
    def postprocess_order_creation(self, order: "Order", previous_value: Any):
        # 订单创建时的操作
        data = ...

        transaction_url = urljoin(settings.CUSTOM_API_URL, "transactions/createoradjust")
        api_post_request_task.delay(transaction_url, data)

加载插件, 需要在 setup.py 进行配置来自动发现已安装的插件。要使插件可被发现,你需要设置 entry_points 的 saleor_plugins 字段, 并使用这个语法定义插件:package_name = package_name.path.to:PluginClass.

示例如下:

# setup.py
from setuptools import setup

setup(
    ...,
    entry_points={
        "saleor.plugins": [
            "my_plugin = my_plugin.plugin:MyPlugin"
        ]
    }
)

如果你的插件是 Django 应用程序,包名(等号前的部分)将被添加到 Django 的 INSTALLED_APPS 中,以便你可以利用 Django 的功能,例如 ORM 集成和数据库迁移。

注意到我们前面订单创建时的操作使用了 .delay 的语法,这是 Celery 的异步任务。因为有些插件的操作就应该异步完成,Saleor 使用 Celery 并将发现 tasks.py 在插件目录中声明的所有异步任务:

# custom_plugin/tasks.py

import json
from celery import shared_task
from typing import Any, Dict

import requests
from requests.auth import HTTPBasicAuth
from django.conf import settings


@shared_task
def api_post_request(
    url: str,
    data: Dict[str, Any],
):
    try:
        username = "username"
        password = "password"
        auth = HTTPBasicAuth(username, password)
        requests.post(url, auth=auth, data=json.dumps(data), timeout=settings.TIMEOUT)
    except requests.exceptions.RequestException:
        return

上面这个 api_post_request 函数就是前面插件用到的异步任务,在插件调用delay方法后,这个任务将被塞到队列中异步执行。

好了,上面就是一个简单的插件开发例子,个人认为Saleor的开发模式还是很不错的。如果大家有需要,可以采用这个项目构建一个属于自己的商城。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

手把手Django+Vue前后端分离入门实战教程

1.前言

众所周知,Django对于网站快速开发非常友好,这得益于框架为我们做了很多事情,让我们只需要做一些简单的配置和逻辑即可把网站的功能开发出来。

但是,在使用Django的过程中,有一个地方一直是比较难受的,那就是使用Django自带的模版,这种通常需要自己利用HTML+CSS+Jquery的方式总感觉是上一个时代的做法,前后端分离无论对于开发效率、多端支持等等都是很有好处的。

所以,本文希望通过一个简单的demo,讲一讲基于Django+Vue的前后端分离开发,将Django作为一个纯后端的服务,为前端Vue页面提供数据支持。

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

如果看不完可以先收藏关注哈~

开始之前,你要确保Python和pip已经成功安装在电脑上,如果没有,可以访问这篇文章:超详细Python安装指南 进行安装。

(可选1) 如果你用Python的目的是数据分析,可以直接安装Anaconda:Python数据分析与挖掘好帮手—Anaconda,它内置了Python和pip.

(可选2) 此外,推荐大家用VSCode编辑器,它有许多的优点:Python 编程的最好搭档—VSCode 详细指南

请选择以下任一种方式输入命令安装依赖
1. Windows 环境 打开 Cmd (开始-运行-CMD)。
2. MacOS 环境 打开 Terminal (command+空格输入Terminal)。
3. 如果你用的是 VSCode编辑器 或 Pycharm,可以直接使用界面下方的Terminal.

pip install django

vue的安装可见:
https://www.runoob.com/vue2/vue-install.html

创建前后端项目:创建一个文件夹,然后命令行创建项目即可,如下图:

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

python manage.py runserver

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

npm run dev

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

2.Django+Vue后端实现

为了方便后端的实现,作为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

现在的目录结构如下:

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

将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了~下图为添加了一条数据之后的效果。

3.Vue前端实现教程

接下来的工作以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,可以看到刚写的页面已经更新上去了,丑是丑了点,意思到了就行~如下图:

4.前后端联调

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

后端部分,对于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, postBook} from '../api/api.js'
export default {
  ...
  methods: {
    loadBooks () {
      getBooks().then(response => {
        this.books = response.data
      })
    }, // load books list when visit the page
    bookSubmit () {
      postBook(this.inputBook.name, this.inputBook.author).then(response => {
        console.log(response)
        this.loadBooks()
      })
    } // add a book to backend when click the button
  },
  ...
}
</script>

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

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

5.打包

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

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

npm run build

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

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

# books_demo/books_demo/settings.py
...
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'dist')], # demo add
        ...
    },
]
...
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'dist/static'),
]
..

最后在根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之后,不管是前端页面还是后端功能,都可以做相应的扩展,从而开发出更加复杂使用的网站。

我们的文章到此就结束啦,如果你喜欢今天的Python 实战教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应红字验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

点击下方阅读原文可获得更好的阅读体验

Python实用宝典 (pythondict.com)
不只是一个宝典
欢迎关注公众号:Python实用宝典

Sanic: 一个比Django、Flask、Tornado都快的Web框架

如果说要用 Python 进行 web 开发,我想你一定会告诉我 使用 Flask 或者 Django 再或者 tornado, 用来用去无非就这三种框架。可能逛 github 多的朋友还会说一个 fastapi。但是,皇上,时代变了,大清… 亡了!!!

1.Sanic 速度为先

当下,python都已经更新到了 Python3.9.3 了,如果你还没有使用过 asyncio、和 Python3.5 新增的 async/await 语法,那说明你可能真的是桃花源人,问今是何世,不知有汉,无论魏晋了。

在当下,基于 async/await 语法的异步 Web 框架也有很多,在 github 上找一找比比皆是是,那究竟应该选哪一款呢?在 github 上有一个专门测试各种语言各种 Web 框架速度的项目,我们来看一看简单的数据:

  Language Framework Speed (64) Speed (256) Speed (512)
58 python (3.9) falcon (2.0) 74 256.04 81 538.76 82 897.69
88 python (3.9) pyramid (2.0) 51 298.30 56 850.32 57 128.55
100 python (3.9) asgineer (0.8) 44 745.54 51 318.30 52 105.43
104 python (3.9) bottle (0.12) 39 690.41 42 590.65 44 329.21
107 python (3.9) emmett (2.2) 35 983.95 41 270.86 42 295.95
115 python (3.9) apidaora (0.28) 34 119.60 38 263.87 38 707.73
116 python (3.9) hug (2.6) 34 040.91 35 909.39 53 828.77
125 python (3.9) blacksheep (1.0) 28 887.22 33 204.44 34 356.25
129 python (3.9) index.py (0.16) 27 273.81 29 282.48 30 258.63
131 python (3.9) starlette (0.14) 26 693.66 31 709.52 31 302.99
134 python (3.9) responder (2.0) 26 092.65 31 092.58 32 175.53
135 python (3.9) clastic (19.9) 25 651.88 28 936.99 28 781.05
136 python (3.9) sanic (21.3) 25 446.08 28 833.98 29 194.50
145 python (3.9) molten (1.0) 18 055.01 21 858.74 21 906.60
146 python (3.9) aiohttp (3.7) 17 837.07 23 359.98 24 128.26
149 python (3.9) fastapi (0.63) 16 701.72 22 402.72 22 042.29
164 python (3.9) flask (1.1) 12 901.82 16 427.23 16 521.94
176 python (3.9) cherrypy (18.6) 9 328.09 9 396.33 8 832.55
177 python (3.9) guillotina (6.2) 9 152.33 8 843.65 8 742.31
181 python (3.9) quart (0.14) 7 571.28 7 501.08 6 888.88
183 python (3.9) tonberry (0.2) 7 363.12 6 948.61 6 344.40
191 python (3.9) django (3.1) 5 918.73 6 572.13 6 150.29
193 python (3.9) tornado (6.1) 5 722.03 5 728.67 5 624.07
210 python (3.9) masonite (3.0) 2 485.30 2 477.63 2 477.29
217 python (3.9) cyclone (1.3) 1 597.57 1 591.42 1 577.40
218 python (3.9) klein (20.6) 1 501.11 1 538.24 1 513.71
220 python (3.9) nameko (2.13) 1 213.25 1 164.90 1 142.60
222 python (3.9) django-ninja (0.11) 1 065.37 1 478.17 1 496.42

这是所有的 Python Web 框架速度测试,有人可能会问为什么不是从 1 开始排序的,因为这个项目的测试还包含 golang、java、php 等众多语言的 Web 框架,共有 226 款。这里我们只用 Python 来做对比。

可以明显的看到,flask、django、tornado 等老牌的 Python Web 框架已经快要垫底了。

wow, 这个速度绝了。可能你们还在怀疑这个速度如何测试的,给你们看一下测试源码:

Flask:

import logging

from flask import Flask
from meinheld import patch

logging.disable()
patch.patch_all()
app = Flask(__name__)
@app.route("/")
def index():
    return ""

@app.route("/user/<int:id>", methods=["GET"])
def user_info(id):
    return str(id)

@app.route("/user", methods=["POST"])
def user():
    return ""


Django:

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt

def index(request):
    return HttpResponse(status=200)

def get_user(request, id):
    return HttpResponse(id)

@csrf_exempt
def create_user(request):
    return HttpResponse(status=200)

Tornado:
# Disable all logging features
import logging
logging.disable()

import tornado.httpserver
import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        pass

class UserHandler(tornado.web.RequestHandler):
    def post(self):
        pass

class UserInfoHandler(tornado.web.RequestHandler):
    def get(self, id):
        self.write(id)

app = tornado.web.Application(
    handlers=[
        (r"/", MainHandler),
        (r"/user", UserHandler),
        (r"/user/(\d+)", UserInfoHandler),
    ]
)

Sanic:
# Disable all logging features
import logging
logging.disable()

import multiprocessing
from sanic import Sanic
from sanic.response import text

app = Sanic("benchmark")

@app.route("/")
async def index(request):
    return text("")

@app.route("/user/<id:int>", methods=["GET"])
async def user_info(request, id):
    return text(str(id))

@app.route("/user", methods=["POST"])
async def user(request):
    return text("")
if __name__ == "__main__":
    workers = multiprocessing.cpu_count()
    app.run(host="0.0.0.0", port=3000, workers=workers, debug=False, access_log=False)
就是简单的不做任何操作,只返回响应,虽然这样测试没有任何实际意义,在正常的生产环境中不可能什么都不做,但是如果所有的框架都如此测试,也算是从一定程度上在同一起跑线上了吧。

OK,就这么多,说到这里你也应该知道我想要说的这个异步框架是谁了,没错,我们今天的主角就是 Sanic

2.为什么要用异步 Web 框架?

这可能是众多小伙伴最先想到的问题了吧?我用 Django、Flask 用的好好的,能够完成正常的任务,为什么还要用异步 Web 框架呢?

说到这里,首先我要反问你你一个问题,你认为在 Web 开发过程中我们最大的敌人是谁?思考 5 秒钟,然后看看我的回答:

在 Web 开发的过程中,我们最大的敌人不是用户,而是阻塞!

是的,而异步可以有效的解决 网络 I/O 阻塞,文件 I/O 阻塞。具体的阻塞相关的文章推荐查看深入理解 Python 异步编程。由于异步可以提升效率,所以对于 Python 来说,异步是最好的提升性能的方式之一。这也是为什么要选择 异步 Web 框架的原因。

3.生态环境

可能有的小伙伴还是会说,你为什么不推荐 falcon 而是推荐 Sanic ?明明它的速度非常快,要比 Sanic 快了那么多,那您看一下下面的代码:

from wsgiref.simple_server import make_server
import falcon


class ThingsResource:
    def on_get(self, req, resp):
        """Handles GET requests"""
        resp.status = falcon.HTTP_200  # This is the default status
        resp.content_type = falcon.MEDIA_TEXT  # Default is JSON, so override
        resp.text = ('\nTwo things awe me most, the starry sky '
                     'above me and the moral law within me.\n'
                     '\n'
                     '    ~ Immanuel Kant\n\n')

app = falcon.App()

things = ThingsResource()

app.add_route('/things', things)

if __name__ == '__main__':
    with make_server(''8000, app) as httpd:
        print('Serving on port 8000...')

        httpd.serve_forever()

一个状态码都要自己定义和填写的框架,我想它的速度快是值得肯定的,但是对于开发者来说,又有多少的实用价值呢?所以我们选择框架并不是要选最快的,而是要又快又好用的。

而大多数框架并不具备这样的生态环境,这应该也是为什么大多数 Python 的 Web 开发者愿意选择 Django 、 Flask 、 tornado 的原因。就是因为它们的生态相对于其他框架要丰富太多太多。

可是,如今不一样了。Sanic 框架, 从 2016 年 5 月开始 发布了第一版异步 Web 框架雏形,至今已经走过了 5 个年头,这 5 年,经过不断地技术积累,Sanic 已经由一个步履蹒跚的小框架变成了一个健步如飞的稳重框架。

在 awesome-sanic 项目中,记录了大量的第三方库,你可以找到任何常用的工具:从 API 到 Authentication,从 Development 到 Frontend,从 Monitoring 到 ORM,从 Caching 到 Queue… 只有你想不到的,没有它没有的第三方拓展。

4.生产环境

以前我在国内的社区中看到过一些小伙伴在问 2020 年了,Sanic 可以用于生产环境了吗?

答案是肯定的,笔者以亲身经历来作证,从19年底,我们就已经将 Sanic 用于生产环境了。彼时的 Sanic 还是 19.9,笔者经历了 Sanic 19.9 — 21.3 所有的 Sanic 的版本,眼看着 Sanic 的生态环境变得越来越棒。

还有一个问题可能你们不知道,Sanic 在创建之初目标就是创建一个可以用于生产环境的 Web 框架。可能有些框架会明确的说明框架中自带的 Run 方法仅用于测试环境,不要使用自带的 Run 方法用于部署环境。但是 Sanic 所创建的不止是一个用于测试环境的应用,更是可以直接用在生产环境中的应用。省去了使用 unicorn 等部署的烦恼!

5.文档完善

想必大多数 Python 的 Web 开发者 学的第一个框架就是 Flask 或者 Django 吧,尤其是 Django 的文档,我想大多数小伙伴看了都会心塞。因为旧的版本有中文,但是新的版本,尤其是新特性,完全没有任何中文文档了!!!!这对于关注 Django 发展但英文又不是强项的同学来说,简直苦不堪言。

但 Sanic 拥有完善的 中文用户指南 和 API 文档,这些都是由贡献者自主发起的,官方承认的文档,由翻译者进行翻译贡献,由 Sanic 官方团队进行发布。或许有的小伙伴会说 Flask 也有完善的中文文档,但是那是在不同的站点上的,Sanic 的所有文档都有 Sanic 官方进行发布支持。且目前 Sanic 还在持续支持 韩语、葡萄牙语等更多的语种。

6.社区指导

和其他框架不同,您或许能够在百度上找到论坛、频道等,但这些都是经过本地汉化的,运营者往往并不是官方,且其中夹杂了很多广告。很显然,如果是官方运营的不可能允许这种情况出现。

Sanic 不同于其他的社区,所有的论坛、频道完全由官方运营,在这里,你可以向核心开发者提问问题,Sanic 的官方发布经理也非常乐意回答各种问题。你也可以和志同道合的使用者分享自己的使用经验。这是一个完全开放的环境….

Sanic 目前常用的有 论坛、Discord、github issues、twitter、Stackoverflow

大家可以在以上的方式中关注 Sanic 的发展以及寻求社区帮助。

你还在等什么?还不赶紧去试一下?最后,以 Sanic 的愿景结尾:Build Faster, Run Faster !

作者:ConnorZhang
链接:https://juejin.cn/post/6944598601674784775

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

3分钟部署Python Web应用,云开发快来了解一下

1. 编写云开发基础应用

创建名为 helloworld 的新目录,并转到此目录中:

mkdir hello-cloud-develop
cd hello-cloud-develop

创建名为 main.py 的文件,并将以下代码粘贴到其中:

import os
from flask import Flask

app = Flask(__name__)


@app.route('/')
def hello_world():
    return 'Hello World!'
  
if __name__ == "__main__":
    app.run(debug=True, host='0.0.0.0', port=8080)

以上代码会创建一个基本的 Web 服务器,并监听 8080 端口。

2. 将应用容器化

在项目根目录下,创建一个名为 Dockerfile 的文件,内容如下:

# 使用官方 Python 轻量级镜像
# https://hub.docker.com/_/python
FROM python:3.8-slim
# 将本地代码拷贝到容器内
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./
# 安装依赖
RUN pip install Flask gunicorn
# 启动 Web 服务
# 这里我们使用了 gunicorn 作为 Server,1 个 worker 和 8 个线程
# 如果您的容器实例拥有多个 CPU 核心,我们推荐您把线程数设置为与 CPU 核心数一致
CMD exec gunicorn --bind :8080 --workers 1 --threads 8 --timeout 0 main:app

添加一个 .dockerignore 文件,以从容器映像中排除文件:

Dockerfile
README.md
*.pyc
*.pyo
*.pyd
__pycache__
.pytest_cache

3.部署到 CloudBase 云托管

你可以选择任何云服务商的云开发服务,这里我选择了腾讯云的CloudBase云托管服务。

在 云托管控制台 的服务列表页面,选择对应的环境,单击【新建服务】。

填写新建服务所需的所有信息默认使用系统推荐的配置即可。

单击【提交】,如果部署成功,便可以看到如下弹框:

单击【新建版本】可立刻开始新建版本并部署,在这里我们选择本地代码,将刚刚写好的文件夹打包为zip文件并上传,端口改为【8080】,流量策略改为【部署完成后自动开启100%流量】,其他配置默认即可:

PS: hello-cloud-develop.zip 可在 Python实用宝典 公众号后台回复:云开发 下载

当然,你也可以选择其他的拉取代码方式,比如代码库或者Docker镜像,按需选择即可。点击开始部署将代码仓库部署为线上服务。

状态变成正常说明部署成功。

4.访问网站

部署成功后就可以访问网站了,进入服务配置,找到公网域名:

这个公网域名就是我们部署网站的地址,访问这个公网域名,出现下面页面效果,说明部署成功:

恭喜!你已经成功通过云开发部署一个网站了,准确来讲是云托管服务。这种部署方式实在非常舒服,无需自建服务器、成本降低、运维省事。

那些简单的个人网站、官方网站或者论坛,我认为都可以用云托管的形式去部署,能够大大节省投入成本,站长们可以尝试一下。

本文全部代码和文件可在 Python实用宝典 公众号后台回复:云开发 下载。

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

5 分钟,带你快速入门 Django 文件上传下载

1. 前言

文件上传、下载作为基础功能,在 Web 项目中非常普遍,Django 项目如何实现文件上传下载?

本篇文章将带大家 5 分钟快速实现文件上传下载功能

2. 实战一下

详细实现步骤如下( 9 步)

2-1  进入虚拟环境,创建一个项目及 App

workon django3

# 创建项目
django-admin startproject file_up_and_down_demo

# 进入项目根目录
cd file_up_and_down_demo/

# 创建一个App
django-admin startapp index

2-2  创建模板目录并配置 settings.py

在 index App 下创建一个 templates 文件夹,然后在项目配置文件 settings.py 中配置 App 及模板目录

# settings.py

# 配置App
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'index',
]

TEMPLATES = [
    {
        'BACKEND''django.template.backends.django.DjangoTemplates',
        'DIRS': [
            # 配置模板目录
            os.path.join(BASE_DIR, 'index/templates')
        ],
        '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',
            ],
        },
    },
]

2-3  创建文件模型,并映射到数据库

以默认的 sqlite 为例,在 index App 下的 models.py 中自定义一个代表文件的模型

该模型包含 3 个字段:

  • 文件名称

  • 文件保存路径

  • 上传时间

# index App models.py

from django.db import models
from django.utils import timezone


# 文件模型
class FileModel(models.Model):
    # 文件名称
    name = models.CharField(max_length=50)

    # 文件保存路径
    path = models.CharField(max_length=100)

    # 上传时间
    upload_time = models.DateTimeField(default=timezone.now)

然后,在项目根目录下执行下面 2 条命令,将模型结构映射到数据库中

# 数据库映射
Python3 manage.py makemigrations

python3 manage.py migrate

2-4  自定义表单控件

在 index App 下创建一个表单文件 forms.py

在内部自定义一个表单类,继承于 forms.Form

# index App forms.py

from django import forms

class FileForm(forms.Form):
    file = forms.FileField(
        # 支持多文件上传
        widget=forms.ClearableFileInput(attrs={'multiple'True}),
        label='请选择文件',
    )

2-5  添加上传、下载路由 URL

为上传、下载功能添加路由 URL

# 项目urls.py
from django.contrib import admin
from django.urls import path, include

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

# index App urls.py
from django.urls import path

from .views import *

urlpatterns = [
    # 上传
    path('', index_view, name='index'),

    # 下载
    path('download/<id>', download_view, name='download')
]

2-6  编写模板文件

在 index App 的模板文件夹创建一个简单的模板文件 upload.html

其中

  • form 代表视图函数传过来的表单实体对象

  • form.as_p 代表以字段格式渲染所有的表单元素

# index App upload.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主页-上传文件</title>
</head>
<body>

<form method="post" enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="确定上传">
</form>

</body>
</html>

2-7  上传视图函数

在 index App 下的 views.py 中编写上传功能的视图函数

需要注意的是,我们需要提前在项目根目录创建一个 upload 文件夹,用于存放上传的文件

# index App views.py

def index_view(request):
    """
    上传文件
    :param request:
    :return:
    """

    if request.method == 'POST':
        form = FileForm(request.POST, request.FILES)
        if form.is_valid():
            # 选择的文件
            files = request.FILES.getlist('file')

            # 遍历写入到数据库中
            for file in files:
                # 写入到数据库中
                file_model = FileModel(name=file.name, path=os.path.join('./upload', file.name))
                file_model.save()

                # 写入到服务器本地
                destination = open(os.path.join("./upload", file.name), 'wb+')
                for chunk in file.chunks():
                    destination.write(chunk)
                destination.close()

            # 提示上传成功
            return HttpResponse('上传成功!')
    else:
        form = FileForm()
        return render(request, 'upload.html', locals())

2-8  下载视图函数

接着,编写下载功能的视图函数

# index App views.py

def download_view(request, id):
    """
    下载文件
    :param request:
    :param id:文件id
    :return:
    """

    file_result = FileModel.objects.filter(id=id)

    # 如果文件存在,就下载文件
    if file_result:

        file = list(file_result)[0]

        # 文件名称及路径
        name = file.name
        path = file.path

        # 读取文件
        file = open(path, 'rb')
        response = FileResponse(file)

        # 使用urlquote对文件名称进行编码
        response['Content-Disposition'] = 'attachment;filename="%s"' % urlquote(name)

        return response
    else:
        return HttpResponse('文件不存在!')

2-9  运行并测试

运行项目,访问下面的地址,并上传一个文件

使用 Pycharm 打开 sqlite 数据库,发现成功插入一条文件记录,并且文件也上传到 upload 文件夹下

接着访问下面的地址实现文件下载功能「 其中,file_id 代表文件的 id 值

http://127.0.0.1:8000/download/file_id

3. 最后

文章通过一个简单的例子实现了文件的上传、下载功能,并同步文件记录到数据库

实际项目中,一般还包含文件列表、文件删除等功能,这些功能只需要结合数据库来增删查改即可实现。

本文转自AirPython.

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

一行代码实战,用Python玩转所有的 emoji

本文简介

还记得刚刚玩儿QQ、微信时,表情轰炸的场景吗?
  • 小时候,快乐是件很简单的事儿!
  • 长大后,简单是件很快乐的事儿!
随着时间的推移,有些表情被淘汰了,有的表情被保留了下来。慢慢地,它似乎可以代替我们说话人的表情和语气了,它好像成为了社交必备。你或许还看过,微信表情可以当作证据提交,这样的新闻。
“表情”很好玩,我们可以随时随地用它表达我们此时此刻的情感;

“表情”很烦人,有些表情确实让人挺尴尬。

就是玩儿,反正玩儿 也不犯罪,大家开心就好。今天黄同学就带大家讲述如何用Python玩转“表情”。

安装emoji库

emoji库,属于第三方库。在使用之前,我们需要提前安装和导入。

① emoji库的安装

pip install emoji -i https://pypi.tuna.tsinghua.edu.cn/simple/
当出现successfully表示安装成功。

②emoji库的导入

import emoji

玩儿起来

emoji中主要就两个函数,供大家玩耍,分别是:
  • emojize():根据 code 生成 emoji 表情;
  • demojize():将 emoji 表情解码为code;
注意哦:默认情况,你只能使用一部分表情。很多表情,需要加参数use_aliases=True后,才可以展示。
昨天没睡好,今天好困呀!
emoji.emojize(“昨天没睡好,今天:zzz:呀”)
结果如下:
笑逐颜开,好彩自然来!
emoji.emojize(“:smile:逐颜开,好彩自然来!”,use_aliases=True)
结果如下:
黄同学是白羊座!
emoji.emojize(“黄同学是:aries:”,use_aliases=True)
结果如下:
小明是AB型血型!
emoji.emojize(“小明是:ab:型血型!”,use_aliases=True)
结果如下:
晚上十一点了!
emoji.emojize(“晚上:clock11:了!”,use_aliases=True)
结果如下:
其实这个库有很多表情包了,部分截图如下:
这里直接给大家上官网吧,大家可以自行下去玩玩儿。https://pypi.org/project/emoji/
本文转自快学Python

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Django 展示可视化图表的几种方式对比

1. 前言

使用 Django 进行 Web 开发时,经常有需要展示图表的需求,以此来丰富网页的数据展示,常见方案包含:Highcharts、Matplotlib、Echarts、Pyecharts,其中后 2 种方案使用频率更高

本篇文章将聊聊 Django 结合 Echarts、Pyecharts 实现图表可视化的具体流程

2. Echarts

Echarts 是百度开源的一个非常优秀的可视化框架,它可以展示非常复杂的图表类型

以展示简单的柱状图为例,讲讲 Django 集成 Echarts 的流程

首先,在某个 App 的 views.py 编写视图函数

当请求方法为 POST 时,定义柱状图中的数据值,然后使用 JsonResponse 返回数据

from django.http import JsonResponse
from django.shortcuts import render


def index_view(request):
    if request.method == "POST":

        # 柱状图的数据
        datas = [52036101020]

        # 返回数据
        return JsonResponse({'bar_datas': datas})
    else:
        return render(request, 'index.html', )

在模板文件中,导入 Echarts 的依赖

PS:可以使用本地 JS 文件或 CDN 加速服务

{#导入js和echarts依赖#}
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.0.2/echarts.common.js"></script>

然后,重写 window.onload 函数,发送一个 Ajax 请求给后端,利用 Echarts 将返回结果展示到图表中去

<script>
    // 柱状图
    function show_bar(data{

        //控件
        var bar_widget = echarts.init(document.getElementById('bar_div'));

        //设置option
        option = {
            title: {
                text'简单的柱状图'
            },
            tooltip: {},
            legend: {
                data: ['销量']
            },
            xAxis: {
                type'category',
                data: ["衬衫""羊毛衫""雪纺衫""裤子""高跟鞋""袜子"]
            },
            yAxis: {
                type'value'
            },
            series: [{
                data: data,
                type'bar'
            }]
        };

        bar_widget.setOption(option)
    }

    //显示即加载调用
    window.onload = function () {
        //发送post请求,地址为index(Jquery)
        $.ajax({
            url"/",
            type"POST",
            data: {},
            successfunction (data{
                // 柱状图
                show_bar(data['bar_datas']);

                //后端返回的结果
                console.log(data)
            }
        })
    }
</script>

最后,编写路由 URL,运行项目

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

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

发现,首页展示了一个简单的柱状图

更多复杂的图表展示可以参考官方

https://echarts.apache.org/examples/zh/index.html

3. Pyecharts

Pyecharts 是一款使用 Python 对 Echarts 进行再次封装后的开源框架

相比 Echarts,Django 集成 Pyecharts 更快捷、方便

Django 集成 Pyecharts 只需要 4 步

3-1  安装依赖

# 安装依赖
pip(3) install pyecharts

3-2  拷贝 pyecharts 的模板文件到项目下

将虚拟环境中 pyecharts 的模板文件拷贝到项目的模板文件夹下

比如本机路径如下:

/Users/xingag/Envs/xh_log/lib/python3.7/site-packages/pyecharts/render/templates/

3-3  编写视图函数,渲染图表

在视图文件中,使用 pyecharts 库内置的类 Bar 创建一个柱状图

# Create your views here.
from django.http import HttpResponse
from jinja2 import Environment, FileSystemLoader
from pyecharts.globals import CurrentConfig

CurrentConfig.GLOBAL_ENV = Environment(loader=FileSystemLoader("./index/templates"))

from pyecharts import options as opts
from pyecharts.charts import Bar


# http://127.0.0.1:8000/demo/
def index(request):
    c = (
        Bar()
            .add_xaxis(["衬衫""羊毛衫""雪纺衫""裤子""高跟鞋""袜子"])
            .add_yaxis("商家A", [52036107590])
            .add_yaxis("商家B", [15251655488])
            .set_global_opts(title_opts=opts.TitleOpts(title="Bar-基本示例", subtitle="我是副标题"))
    )
    return HttpResponse(c.render_embed())

3-4  运行项目

运行项目,生成的柱状图如下:

这只是最简单的使用实例,更多复杂的图表及前后端分离、更新的例子

可以参考官网:

https://pyecharts.org/#/zh-cn/web_django?id=django-%e5%89%8d%e5%90%8e%e7%ab%af%e5%88%86%e7%a6%bb

4. 最后

文中介绍了 Django 快速集成 Echarts 和 Pyecharts 的基本步骤

实际项目中,一些复杂的图表、前后端分离数据更新可以参考官网去拓展

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

不限速真香!Python 转移文件至对象存储(COS)上

对象存储(Cloud Object Storage,COS)是一种存储海量文件的分布式存储服务,具有高扩展性、低成本、可靠安全等优点。

对象存储有什么用呢?比如说Python实用宝典网站的服务器位于腾讯云香港,大陆访问速度会比较慢,这时候我可以将图片等静态资源转移到对象存储上,以提高用户的访问速度。

实际上,这两天我就对Python实用宝典做了这样的调整,搭配上懒加载,效果极佳,用户访问并渲染图片的过程所耗费的时间大大减少,大家可以访问Python实用宝典网体验一下:

https://pythondict.com

那么对于非站长的普通用户有什么用?相比于其他什么百度网盘之类的产品,COS最重要的优势是:

  • 上传下载不限速,数据中心多地任选
  • 能实现文件分享功能,同样不限速
  • 能自动创建文件历史备份,方便回溯办公文件
  • 支持各平台用客户端管理文件,电脑端甚至还可直接将网盘挂载为一个虚拟磁盘
  • 价格按用量计费(存储量、下行流量),免费额度用完后,你可能一个月大约也才需要花费 6 元

像腾讯云这样的大云服务商,每个月都给用户提供了50G的免费容量:

非常好,下面大家可以跟着我一起尝试使用一下,或许你会爱上这个产品。

0.创建存储桶

进入腾讯云的对象存储页面:

https://console.cloud.tencent.com/cos5/bucket#

选择创建存储桶,可以随意命名你的数据桶,地域可以选择离你最近的。不过请注意访问权限这一项,如果你希望你的朋友也能下载数据桶内的文件,一定要选择公有读私有写:

1.安装

接下来说说怎么用Python将静态资源转移到腾讯云COS上。

Coscmd是腾讯云官方基于Python推出的上传工具,使用起来非常方便。

使用 pip 安装 coscmd

pip install coscmd

如果直接安装失败了,可以尝试源码安装

git clone https://github.com/tencentyun/coscmd.git
cd coscmd
python setup.py install

可以使用 coscmd -v 查看版本号

# coscmd -v
coscmd 1.8.6.16

2.配置

安装完成之后我们还需要一些参数配置,配置密钥、存储桶地域等。我们迁移数据时仅仅只需要配置一些简单的参数即可

coscmd config -a SecretID -s SecretKey -b BucketName-APPID -r region

SecretID 和 SecretKey 可以在控制台获取,如果没有的话直接创建一个:

BucketName-APPID 是存储桶名称,region 为存储桶所在地域,例如:

coscmd config -a AChT4ThiXAbpBDEFGhT4ThiXAbp**** -s WE54wreefvds3462refgwewe**** -b examplebucket-1250000000 -r ap-beijing

3.迁移

下面以迁移本地附件到 COS 为例,演示一下 COSCMD 的使用

COSCMD 可以上传文件也可以直接上传文件夹,命令为:

#上传文件
coscmd upload  

#上传文件夹
coscmd upload -r  

这样我们就可以使用如下命令将媒体库的文件上传到 COS 中

# home/ 指将媒体库里的文件存放到COS的home文件夹中
coscmd upload -r 你的媒体库文件夹地址 存放到COS的具体位置如home/

#同步上传,跳过 md5 相同的文件
coscmd upload -rs 你的媒体库文件夹地址 home/

#忽略 .mp3 和 .gif 的后缀文件
coscmd upload -rs 你的媒体库文件夹地址 home/ --ignore *.mp3,*.gif

我们需要将媒体库的图片上传到 COS 中,存放的路径则因使用而异。

如果是网站的话,比如WordPress 的媒体库存储路径建议使用 wp-content/uploads/,如果你是为了个人使用,可以上传到任意位置,建议使用:home/.

上传完成后如上图所示,你可以在文件列表中找到你刚上传的这些媒体文件。

当然,COS也支持手动上传文件:

如果你按照我的步骤创建了一个公有读私有写的数据桶,那么上传的这些资源也支持分享文件下载地址给别人:

上图中显示的对象地址,就可以拿来分享给你任何朋友下载,不限速!不限速!不限速!怎么样,是不是超香?

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

升级 Flask 到 Quart, 3 倍性能提升就这么简单!

简评:将你的 Flask 应用程序升级到 Quart 应用程序,轻松获得 3 倍的性能提升。

自从 Flask 在 8 年前发布以来,Python 发生了很大变化,特别是引入了 asyncio之后。asyncio 允许开发像 uvloop 和 asyncpg 这样的库来大大提高性能,可惜要 Flask 集成 asyncio 或这些库并不是一件简单的事情。不过不用气馁,Quart 框架与 asyncio 可以一起使用 Flask-API。

基于共享的 Flask-API,现有的 Flask 应用程序进行很少的修改就可以变成 Quart 应用程序,然后就可以使用这些新的库来实现 Flask 无法做到的性能优化。

本文详细介绍了典型的生产环境的 CRUD 应用程序从 Flask 到 Quart 的转换,并展示相关的性能改进优势。

0. 我想直接看杰伦

将这个 Flask-pyscopg2 应用程序升级到 Quart-asyncpg 应用程序可以提高 3 倍的性能,而且不需要对代码进行重大的重写或调整。

如上所示,在针对单个资源详情的请求下,Flask每秒请求为330个,而quart能达到1160个。以此类推,Quart 相比于 Flask 平均性能提高3倍。

这个比较,我使用了一个简单的只提供一个 RESTful 接口的应用程序,这是微服务架构中的常见用例。

该应用程序有三个路由。这些路由分别是:

  • 单个电影详情:GET /films/pk/
  • 所有电影:GET /films/
  • 添加新评论:POST /reviews/

源代码可以在以下网址找到:
https://github.com/pgjones/faster_than_flask_article

有两个 commit ,分别是一个 Flask 版本和一个Quart 版本。

1. 从 Flask 到 Quart

从 Flask 改用 Quart 很容易,只需要一点点改变,特别是 from flask 改为 from quart,函数变成异步函数。

def add_review():
  data = request.get_json()
  ...

变成

async def add_review():
  data = await request.get_json()
  ...

2.数据库连接,从 psycopg2 到 asyncpg

从 psycopg2 改用 asyncpg 比较麻烦,因为两者有不同的用法。

为了简化区别,我们在 Flask 应用程序中使用了 PoolWrapper,使得 psycopg2 可以使用与 asyncpg 相同的 API 进行上下文管理,即:

with pool.acquire() as connection:

这将允许通过with更改为async with来使用asyncpg。

当然,除了连接之外,Asyncpg和psycopg2还在游标使用、事务、执行参数和查询格式方面存在差异。这些差异是你在迁移过程中需要注意的。

3.部署

Flask 应用程序往往不能直接在生产环境中直接暴露给用户,这是因为Flask 本身一次只能处理一个请求。因此,常常用WSGI服务器与某种异步 worker 结合使用,例如  带 eventlet 的Gunicorn。

Quart 也可以用 Gunicorn 部署,它允许使用相同的命令来运行 Flask 和 Quart 应用程序:

$ gunicorn --config gunicorn.py 'run:create_app()'
针对 Flask 和 Quart 的性能测试是基于 Gunicorn 进行的。

4.添加测试数据

除了添加一个简单的 review 表之外,Postgresql 示例数据库还要为应用程序提供一些用于 CRUD的数据。

CREATE TABLE review (
  film_id INTEGER REFERENCES film(film_id),
  rating INTEGER
);

5.性能测试

为了测量应用程序的性能,我们使用了wrk。它被配置为使用20个连接,以匹配数据库连接池的大小(确保最高的吞吐量,20是我使用过的典型值)。命令如下:

测试 GET 请求的命令是

$ wrk --connections 20 --duration 5m http://localhost:5000/${PATH}/

测试 POST 请求的命令是

$ wrk --connections 20 --duration 5m --script post.lua http://localhost:5000/${PATH}/

测试使用的 post.lua 文件如下:

wrk.method = "POST"
wrk.body = '{"film_id": 995, "rating": 4}'
wrk.headers["Content-Type"] = "application/json"

6.系统信息与结果

系统信息:

Postgres (9.5.10),wrk (4.0.0),Python (3.6.3),asyncpg (0.13.0),Flask (0.12.2),Gunicorn (19.7.1),psycopg2 (2.7.3.2), Quart (0.3.1)

全部运行在一台 AWS c4.large 机器上。

结果

请注意,Quart 服务器的平均等待时间减少了 2 至 3.5 倍,每秒的请求数量增加了 2 至 3.5 倍。

7.结论

Flask 应用程序升级到 Quart 应用程序是相当简单的,因为大部分 API 是共享的,所以主要工作就是在正确的位置写asyncawait。然而,如果使用 SQLAlchemy(或其他 ORM),则从psycopg2 到 asyncpg 的改变会比较复杂,并且可能会很麻烦。

这个 demo 应用程序的性能显着提高,这个改进主要是由于 Quart 使用了 asyncpg 和 uvloop,据估计,仅 Quart 就能提供 1.5 倍的提升。

总之,从 Flask-psycopg2 应用程序升级到 Quart-asyncpg 应用程序的比较简单,并拥有非常合理的性能改进。这可能会扩展到其他基于 asyncio 的库,意味着将 Flask 应用程序转换到 asyncio 生态系统,Quart 只需要很小的工作量。

原文:3x faster Flask apps
https://hackernoon.com/3x-faster-than-flask-8e89bfbe8e4f

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典

Django RestFramework 请求流程、解析器、序列化器分析

本文重点在于讲解什么是REST规范及Django RestFramework中的APIview请求流程,这个流程包括源代码分析和后续的解析器组件及序列化器组件。

1.什么是REST

编程是数据结构和算法的结合,而在Web类型的App中,我们对于数据的操作请求是通过url来承载的,本文详细介绍了REST规范和CBV请求流程。

编程是数据结构和算法的结合,小程序如简单的计算器,我们输入初始数据,经过计算,得到最终的数据,这个过程中,初始数据和结果数据都是数据,而计算过程是我们所说的广义上的算法。

大程序,如一个智能扫地机器人,我们可以设置打扫的距离,左右摆动的幅度来打扫房间,这里面打扫的举例,摆动幅度,都是数据,而打扫的过程是较为复杂的算法过程,总之,也是算法,即程序的实现方式。

另外,我们还可以设置打扫时间等等初始数据。

总之一句话,编程即数据结构和算法的结合。简单的程序可能不需要跟用户交互数据,但是现代的应用程序几乎都需要跟用户进行交互,不分应用程序类型,不管是CS型还是BS型的程序都是如此,而Python最擅长的Web App即BS型的程序,就是通过url和http来跟用户进行数据交互的,通过url和http请求,用户可以操作服务器端的程序,主要操作分为:增、删、改、查几类

引入

在开始之前,我们回顾一下咱们之前写过的图书管理系统项目,请仔细回想一下,对于该项目的设计,我们大概是以下面这种方式来实现的

传统url设计风格
  • url各式各样,设计混乱

理论上来说,这种方式完全可以实现我们的需求,但是一旦项目丰富起来,随着数据量增加,随着各个业务系统之间的逻辑关系不断的复杂,url会越来越复杂,理论上来说,不管是什么类型、什么名称的url都能指向具体的业务逻辑(视图函数),从而实现业务需求,但是如果没有明确的规范,因每个人的思维方式不一样、命名方式不一样而导致的url非常的乱,不方便项目的后期维护和扩展。

  • 对于请求处理成功或者失败的返回信息没有明确的响应信息规范,返回给客户端的信息往往都是很随意的

以上这些情况的出现,导致了很多的问题,让互联网的世界变得杂乱不堪,日益复杂且臃肿。

因此http协议创始人警告我们这些凡人们正在错误的使用http协议,除了警告,他还发表了一篇博客,大概意思就是教大家如何正确使用http协议,如何正确定义url,这就是REST(Representational State Transfer),不需要管这几个英文单词代表什么意思,只需要记住下面一句话:

  • 用url唯一定位资源,用Http请求方式(GET, POST, DELETE, PUT)描述用户行为

根据这句话,我们重新定义图书管理系统中的url

RESTful Api设计风格

可以看到,url非常简洁优雅,不包含任何操作,不包含任何动词,简简单单,用来描述服务器中的资源而已,服务器根据用户的请求方式对资源进行各种操作。而对数据的操作,最常见的就是CRUD(创建,读取,更新,删除),通过不同的请求方式,就足够描述这些操作方式了。

如果不够用,Http还有其他的请求方式呢!比如:PATCH,OPTIONS,HEAD, TRACE, CONNECT。

REST定义返回结果

每一种请求方式的返回结果不同。

REST定义错误信息
{
    "error": "Invalid API key"
}

通过一个字典,返回错误信息。

这就是REST,上图中的url就是根据REST规范进行设计的RESTful api。

因此REST是一种软件架构设计风格,不是标准,也不是具体的技术实现,只是提供了一组设计原则和约束条件。

它是目前最流行的 API 设计规范,用于 Web 数据接口的设计。2000年,由Roy Fielding在他的博士论文中提出,Roy Fielding是HTTP规范的主要编写者之一。

那么,我们所要讲的Django RestFramework与rest有什么关系呢?

其实,DRF(Django RestFramework)是一套基于Django开发的、帮助我们更好的设计符合REST规范的Web应用的一个Django App,所以,本质上,它是一个Django App。

2.为什么使用DRF

从概念就可以看出,有了这样的一个App,能够帮助我们更好的设计符合RESTful规范的Web应用,实际上,没有它,我们也能自己设计符合规范的Web应用。下面的代码演示如何手动实现符合RESTful规范的Web应用。

class CoursesView(View):
    def get(self, request):
        courses = list()

        for item in Courses.objects.all():
            course = {
                "title": item.title,
                "price": item.price,
                "publish_date": item.publish_date,
                "publish_id": item.publish_id
            }

            courses.append(course)

        return HttpResponse(json.dumps(courses, ensure_ascii=False))

如上代码所示,我们获取所有的课程数据,并根据REST规范,将所有资源的通过对象列表返回给用户。

可见,就算没有DRF我们也能够设计出符合RESTful规范的接口甚至是整个Web App,但是,如果所有的接口都自定义,难免会出现重复代码,为了提高工作效率,我们建议使用优秀的工具。

DRF就是这样一个优秀的工具,另外,它不仅仅能够帮助我们快速的设计符合REST规范的接口,还提供诸如认证、权限等等其他的强大功能。

什么时候使用DRF?

前面提到,REST是目前最流行的 API 设计规范,如果使用Django开发你的Web应用,那么请尽量使用DRF,如果使用的是Flask,可以使用Flask-RESTful。

3.Django View请求流程

首先安装Django,然后安装DRF:

pip install django
pip install djangorestframework

安装完成之后,我们就可以开始使用DRF框架来实现咱们的Web应用了,本篇文章包括以下知识点:

  • APIView
  • 解析器组件
  • 序列化组件

介绍DRF,必须要介绍APIView,它是重中之重,是下面所有组件的基础,因为所有的请求都是通过它来分发的,至于它究竟是如何分发请求的呢?

想要弄明白这个问题,我们就必须剖析它的源码,而想要剖析DRF APIView的源码,我们需要首先剖析django中views.View类的源码,为什么使用视图类调用as_view()之后,我们的请求就能够被不同的函数处理呢?

源码中最后会通过getattr在self中查找request.method.lower(),也就是get、post或者delete这些方法中的一个,那么,self是谁,就是至关重要的一点,前面讲到过,谁调用类中的方法,self就指向谁,此时,一层层往回找,我们会发现,self = cls(**initkwargs),self就是我们视图类的实例化对象,所以,dispatch函数肯定会到该视图类中找对应的方法(get或者post)。

接下来是提问时间,请问如果有如下函数,不修改函数内容的情况下,如何给函数新增一个统计执行时间的功能:

def outer(func):
    def inner(*args, **kwargs):
        import time
        start_time = time.time()
        ret = func(*args, **kwargs)
        end_time = time.time()
        print("This function elapsed %s" % str(end_time - start_time))
        return ret
    return inner


@outer
def add(x, y):
    return x + y

这是函数,如果是类呢?面向对象编程,如何扩展你的程序,比如有如下代码:

class Person(object):
    def show(self):
        print("Person's show method executed!")
        

class MyPerson(Person):
    def show(self):
        print("MyPerson's show method executed")
        super().show()
        

mp = MyPerson()
mp.show()

这就是面向对象的程序扩展,现在大家是否对面向对象有了更加深刻的认识呢?接下来给大家十分钟时间,消化一下上面两个概念,然后请思考,那么假设你是Django RestFramework的开发者,你想自定制一些自己想法,如何实现。

好了,相信大家都已经有了自己的想法,接下来,我们一起来分析一下,Django RestFramework的APIView是如何对Django框架的View进行功能扩展的。

from django.shortcuts import HttpResponse

import json

from .models import Courses

# 引入APIView
from rest_framework.views import APIView
# Create your views here.


class CoursesView(APIView):  # 继承APIView而不是原来的View
    def get(self, request):
        courses = list()

        for item in Courses.objects.all():
            course = {
                "title": item.title,
                "description": item.description
            }

            courses.append(course)

        return HttpResponse(json.dumps(courses, ensure_ascii=False))

以上就是Django RestFramework APIView的请求处理流程,我们可以通过重写dispatch()方法或者重写as_view()方法来自定制自己的想法。

那么,Django RestFramework到底自定制了哪些内容呢?在本文的最开始,我们已经介绍过了,就是那些组件,比如解析器组件、序列化组件、权限、频率组件等。

Ajax发送Json数据给服务器

接下来,我们就开始介绍Django RestFramework中的这些组件,首先,最基本的,就是解析器组件,在介绍解析器组件之前,我提一个问题,请大家思考,如何发送Json格式的数据给后端服务器?

好了,时间到,请看下面的代码,通过ajax请求,我们可以发送json格式的数据到后端:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
  <script src="/static/jquery-1.10.2.min.js"></script>
</head>
<body>
  <form action="" method="post" enctype="application/x-www-form-urlencoded">
    {% csrf_token %}
    用户名: <input type="text" name="username"/>
    密码:  <input type="password" name="password"/>
    提交:  <input type="submit" value="提交"/>
  </form>

  <hr>
  <button class="btn">点击发送Ajax请求</button>

  <script>
    $(".btn").click(function () {
      $.ajax({
        url: '',
        type: 'post',
        contentType: 'application/json',
        data: JSON.stringify({
          username: "alex",
          password: 123
        }
        ),
        success: function (data) {
          console.log(data);
        }
      })
    })

  </script>

</body>
</html>

通过上文的知识点复习我们已经知道,Content-Type用来定义发送数据的编码协议,所以,在上面的代码中,我们指定Content-Type为application/json,即可将我们的Json数据发送到后端,那么后端如何获取呢?

服务器对Json数据的处理方式

按照之前的方式,我们使用request.POST, 如果打印该值,会发现是一个空对象:request post <QueryDict: {}>,该现象证明Django并不能处理请求协议为application/json编码协议的数据,我们可以去看看request源码,可以看到下面这一段:

if self.content_type == 'multipart/form-data':
    if hasattr(self, '_body'):
        # Use already read data
        data = BytesIO(self._body)
    else:
        data = self
    try:
        self._post, self._files = self.parse_file_upload(self.META, data)
    except MultiPartParserError:
        # An error occurred while parsing POST data. Since when
        # formatting the error the request handler might access
        # self.POST, set self._post and self._file to prevent
        # attempts to parse POST data again.
        # Mark that an error occurred. This allows self.__repr__ to
        # be explicit about it instead of simply representing an
        # empty POST
        self._mark_post_parse_error()
        raise
elif self.content_type == 'application/x-www-form-urlencoded':
    self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
else:
    self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict()

可见Django原生解析器并不处理application/json编码协议的数据请求,好了,有了这样的认识之后,咱们就可以开始正式介绍DRF的解析器了,解析器,顾名思义,就是用来解析数据的请求的。

虽然Django的原生解析器不支持application/json编码协议,但是我们可以通过拿到原始的请求数据(request.body)来手动处理application/json请求,虽然这种方式不方便,也并不推荐,请看如下代码:

class LoginView(View):
    def get(self, request):
        return render(request, 'classbasedview/login.html')

    def post(self, request):
        print(request.POST)  # <QueryDict: {}>
        print(request.body)  # b'{"username":"alex","password":123}'
        data = request.body.decode('utf-8')
        dict_data = json.loads(data)

        username = dict_data['username']
        password = dict_data['password']

        return HttpResponse(json.dumps(dict_data))

通过上面的代码,我们可以通过request.body手动处理application/json请求,不过,如上文所说,并不推荐。

4.DRF 解析器组件

首先,来看看解析器组件的使用,稍后我们一起剖析其源码:

from django.http import JsonResponse

from rest_framework.views import APIView
from rest_framework.parsers import JSONParser, FormParser
# Create your views here.


class LoginView(APIView):
    parser_classes = [FormParser]

    def get(self, request):
        return render(request, 'parserver/login.html')

    def post(self, request):
        # request是被drf封装的新对象,基于django的request
        # request.data是一个property,用于对数据进行校验
        # request.data最后会找到self.parser_classes中的解析器
        # 来实现对数据进行解析
        
        print(request.data)  # {'username': 'alex', 'password': 123}

        return JsonResponse({"status_code": 200, "code": "OK"})

使用方式非常简单,分为如下两步:

  • from rest_framework.views import APIView
  • 继承APIView
  • 直接使用request.data就可以获取Json数据

如果你只需要解析Json数据,不允许任何其他类型的数据请求,可以这样做:

  • from rest_framework.parsers import JsonParser
  • 给视图类定义一个parser_classes变量,值为列表类型[JsonParser]
  • 如果parser_classes = [], 那就不处理任何数据类型的请求了

问题来了,这么神奇的功能,DRF是如何做的?因为昨天讲到Django原生无法处理application/json协议的请求,所以拿json解析来举例,请同学们思考一个问题,如果是你,你会在什么地方加入新的Json解析功能?

首先,需要明确一点,我们肯定需要在request对象上做文章,为什么呢?

因为只有有了用户请求,我们的解析才有意义,没有请求,就没有解析,更没有处理请求的逻辑,所以,我们需要弄明白,在整个流程中,request对象是什么时候才出现的,是在绑定url和处理视图之间的映射关系的时候吗?我们来看看源码:

@classonlymethod
def as_view(cls, **initkwargs):
    """Main entry point for a request-response process."""
    for key in initkwargs:
        if key in cls.http_method_names:
            raise TypeError("You tried to pass in the %s method name as a "
                            "keyword argument to %s(). Don't do that."
                            % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

def view(request, *args, **kwargs):
    self = cls(**initkwargs)
    if hasattr(self, 'get') and not hasattr(self, 'head'):
        self.head = self.get
        self.request = request
        self.args = args
        self.kwargs = kwargs
        return self.dispatch(request, *args, **kwargs)
    view.view_class = cls
    view.view_initkwargs = initkwargs

    # take name and docstring from class
    update_wrapper(view, cls, updated=())

    # and possible attributes set by decorators
    # like csrf_exempt from dispatch
    update_wrapper(view, cls.dispatch, assigned=())
    return view

看到了吗?在执行view函数的时候,那么什么时候执行view函数呢?当然是请求到来,根据url查找映射表,找到视图函数,然后执行view函数并传入request对象,所以,如果是我,我可以在这个视图函数里面加入处理application/json的功能:

@classonlymethod
def as_view(cls, **initkwargs):
    """Main entry point for a request-response process."""
    for key in initkwargs:
        if key in cls.http_method_names:
            raise TypeError("You tried to pass in the %s method name as a "
                            "keyword argument to %s(). Don't do that."
                            % (key, cls.__name__))
            if not hasattr(cls, key):
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

def view(request, *args, **kwargs):
    if request.content_type == "application/json":
        import json
        return HttpResponse(json.dumps({"error": "Unsupport content type!"}))

    self = cls(**initkwargs)
    if hasattr(self, 'get') and not hasattr(self, 'head'):
        self.head = self.get
        self.request = request
        self.args = args
        self.kwargs = kwargs
        return self.dispatch(request, *args, **kwargs)
    view.view_class = cls
    view.view_initkwargs = initkwargs

    # take name and docstring from class
    update_wrapper(view, cls, updated=())

    # and possible attributes set by decorators
    # like csrf_exempt from dispatch
    update_wrapper(view, cls.dispatch, assigned=())
    return view

看到了吧,然后我们试试发送json请求,看看返回结果如何?是不是非常神奇?事实上,你可以在这里,也可以在这之后的任何地方进行功能的添加。

那么,DRF是如何做的呢?我们在使用的时候只是继承了APIView,然后直接使用request.data,所以,我斗胆猜测,功能肯定是在APIView中定义的,废话,具体在哪个地方呢?

接下来,我们一起来分析一下DRF解析器源码,看看DRF在什么地方加入了这个功能。

上图详细描述了整个过程,最重要的就是重新定义的request对象,和parser_classes变量,也就是我们在上面使用的类变量。好了,通过分析源码,验证了我们的猜测。

5.序列化组件

首先我们要学会使用序列化组件。定义几个 model:

from django.db import models

# Create your models here.


class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()

    def __str__(self):
        return self.name


class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    def __str__(self):
        return self.name


class Book(models.Model):
    title = models.CharField(max_length=32)
    publishDate = models.DateField()
    price = models.DecimalField(max_digits=5, decimal_places=2)
    publish = models.ForeignKey(to="Publish", to_field="nid", on_delete=models.CASCADE)
    authors = models.ManyToManyField(to="Author")

    def __str__(self):
        return self.title

通过序列化组件进行GET接口设计

设计url,本次我们只设计GET和POST两种接口:

from django.urls import re_path

from serializers import views

urlpatterns = [
    re_path(r'books/$', views.BookView.as_view())
]

我们新建一个名为app_serializers.py的模块,将所有的序列化的使用集中在这个模块里面,对程序进行解耦:

# -*- coding: utf-8 -*-
from rest_framework import serializers

from .models import Book


class BookSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=128)
    publish_date = serializers.DateTimeField()
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish = serializers.CharField(max_length=32)
    authors = serializers.CharField(max_length=32)

接着,使用序列化组件,开始写视图类:

# -*- coding: utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response

# 当前app中的模块
from .models import Book
from .app_serializer import BookSerializer

# Create your views here.

class BookView(APIView):
    def get(self, request):
        origin_books = Book.objects.all()
        serialized_books = BookSerializer(origin_books, many=True)

        return Response(serialized_books.data)

如此简单,我们就已经,通过序列化组件定义了一个符合标准的接口,定义好model和url后,使用序列化组件的步骤如下:

  • 导入序列化组件:from rest_framework import serializers
  • 定义序列化类,继承serializers.Serializer(建议单独创建一个专用的模块用来存放所有的序列化类):class BookSerializer(serializers.Serializer):pass
  • 定义需要返回的字段(字段类型可以与model中的类型不一致,参数也可以调整),字段名称必须与model中的一致
  • 在GET接口逻辑中,获取QuerySet
  • 开始序列化:将QuerySet作业第一个参数传给序列化类,many默认为False,如果返回的数据是一个列表嵌套字典的多个对象集合,需要改为many=True
  • 返回:将序列化对象的data属性返回即可

上面的接口逻辑中,我们使用了Response对象,它是DRF重新封装的响应对象。该对象在返回响应数据时会判断客户端类型(浏览器或POSTMAN),如果是浏览器,它会以web页面的形式返回,如果是POSTMAN这类工具,就直接返回Json类型的数据。

此外,序列化类中的字段名也可以与model中的不一致,但是需要使用source参数来告诉组件原始的字段名,如下:

class BookSerializer(serializers.Serializer):
    BookTitle = serializers.CharField(max_length=128, source="title")
    publishDate = serializers.DateTimeField()
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    # source也可以用于ForeignKey字段
    publish = serializers.CharField(max_length=32, source="publish.name")
    authors = serializers.CharField(max_length=32)

下面是通过POSTMAN请求该接口后的返回数据,大家可以看到,除ManyToManyField字段不是我们想要的外,其他的都没有任何问题:

[
    {
        "title": "Python入门",
        "publishDate": null,
        "price": "119.00",
        "publish": "浙江大学出版社",
        "authors": "serializers.Author.None"
    },
    {
        "title": "Python进阶",
        "publishDate": null,
        "price": "128.00",
        "publish": "清华大学出版社",
        "authors": "serializers.Author.None"
    }
]

那么,多对多字段如何处理呢?如果将source参数定义为”authors.all”,那么取出来的结果将是一个QuerySet,对于前端来说,这样的数据并不是特别友好,我们可以使用如下方式:

class BookSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=32)
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    publishDate = serializers.DateField()
    publish = serializers.CharField()
    publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
    publish_email = serializers.CharField(max_length=32, read_only=True, source='publish.email')
    # authors = serializers.CharField(max_length=32, source='authors.all')
    authors_list = serializers.SerializerMethodField()

    def get_authors_list(self, authors_obj):
        authors = list()
        for author in authors_obj.authors.all():
            authors.append(author.name)

        return authors

请注意,get_必须与字段名称一致,否则会报错。

通过序列化组件进行POST接口设计

接下来,我们设计POST接口,根据接口规范,我们不需要新增url,只需要在视图类中定义一个POST方法即可,序列化类不需要修改,如下:

# -*- coding: utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response

# 当前app中的模块
from .models import Book
from .app_serializer import BookSerializer

# Create your views here.


class BookView(APIView):
    def get(self, request):
        origin_books = Book.objects.all()
        serialized_books = BookSerializer(origin_books, many=True)

        return Response(serialized_books.data)

    def post(self, request):
        verified_data = BookSerializer(data=request.data)

        if verified_data.is_valid():
            book = verified_data.save()
            # 可写字段通过序列化添加成功之后需要手动添加只读字段
            authors = Author.objects.filter(nid__in=request.data['authors'])
            book.authors.add(*authors)

            return Response(verified_data.data)
        else:
            return Response(verified_data.errors)

POST接口的实现方式,如下:

  • url定义:需要为post新增url,因为根据规范,url定位资源,http请求方式定义用户行为
  • 定义post方法:在视图类中定义post方法
  • 开始序列化:通过我们上面定义的序列化类,创建一个序列化对象,传入参数data=request.data(application/json)数据
  • 校验数据:通过实例对象的is_valid()方法,对请求数据的合法性进行校验
  • 保存数据:调用save()方法,将数据插入数据库
  • 插入数据到多对多关系表:如果有多对多字段,手动插入数据到多对多关系表
  • 返回:将插入的对象返回

请注意,因为多对多关系字段是我们自定义的,而且必须这样定义,返回的数据才有意义,而用户插入数据的时候,serializers.Serializer没有实现create,我们必须手动插入数据,就像这样:

# 第二步, 创建一个序列化类,字段类型不一定要跟models的字段一致
class BookSerializer(serializers.Serializer):
    # nid = serializers.CharField(max_length=32)
    title = serializers.CharField(max_length=128)
    price = serializers.DecimalField(max_digits=5, decimal_places=2)
    publish = serializers.CharField()
    # 外键字段, 显示__str__方法的返回值
    publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
    publish_city = serializers.CharField(max_length=32, read_only=True, source='publish.city')
    # authors = serializers.CharField(max_length=32) # book_obj.authors.all()

    # 多对多字段需要自己手动获取数据,SerializerMethodField()
    authors_list = serializers.SerializerMethodField()

    def get_authors_list(self, book_obj):
        author_list = list()

        for author in book_obj.authors.all():
            author_list.append(author.name)

        return author_list

    def create(self, validated_data):
        # {'title': 'Python666', 'price': Decimal('66.00'), 'publish': '2'}
        validated_data['publish_id'] = validated_data.pop('publish')
        book = Book.objects.create(**validated_data)

        return book

    def update(self, instance, validated_data):
        # 更新数据会调用该方法
        instance.title = validated_data.get('title', instance.title)
        instance.publishDate = validated_data.get('publishDate', instance.publishDate)
        instance.price = validated_data.get('price', instance.price)
        instance.publish_id = validated_data.get('publish', instance.publish.nid)

        instance.save()

        return instance

这样就会非常复杂化程序,如果我希望序列化类自动插入数据呢?

这是问题一:如何让序列化类自动插入数据?

另外问题二:如果字段很多,那么显然,写序列化类也会变成一种负担,有没有更加简单的方式呢?

答案是肯定的,我们可以这样做:

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = Book

        fields = ('title',
                  'price',
                  'publish',
                  'authors',
                  'author_list',
                  'publish_name',
                  'publish_city'
                  )
        extra_kwargs = {
            'publish': {'write_only': True},
            'authors': {'write_only': True}
        }

    publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
    publish_city = serializers.CharField(max_length=32, read_only=True, source='publish.city')

    author_list = serializers.SerializerMethodField()

    def get_author_list(self, book_obj):
        # 拿到queryset开始循环 [{}, {}, {}, {}]
        authors = list()

        for author in book_obj.authors.all():
            authors.append(author.name)

        return authors

步骤如下:

  • 继承ModelSerializer:不再继承Serializer
  • 添加extra_kwargs类变量:extra_kwargs = {‘publish’: {‘write_only’: True}}

使用ModelSerializer完美的解决了上面两个问题。好了,这就是今天的全部内容。

参考资料:

https://pizzali.github.io/2018/12/07/DRF%E4%B9%8BREST%E8%A7%84%E8%8C%83%E4%BB%8B%E7%BB%8D%E5%8F%8AView%E8%AF%B7%E6%B1%82%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/

https://pizzali.github.io/2018/12/07/DRF%E4%B9%8B%E8%A7%A3%E6%9E%90%E5%99%A8%E7%BB%84%E4%BB%B6%E5%8F%8A%E5%BA%8F%E5%88%97%E5%8C%96%E7%BB%84%E4%BB%B6/

我们的文章到此就结束啦,如果你喜欢今天的 Python 教程,请持续关注Python实用宝典。

有任何问题,可以在公众号后台回复:加群,回答相应验证信息,进入互助群询问。

原创不易,希望你能在下面点个赞和在看支持我继续创作,谢谢!

给作者打赏,选择打赏金额
¥1¥5¥10¥20¥50¥100¥200 自定义

​Python实用宝典 ( pythondict.com )
不只是一个宝典
欢迎关注公众号:Python实用宝典