在 Flask 中集成 Django 的 ORM 模块

欢迎转载,但请在开头或结尾注明原文出处【blog.chaosjohn.com】

前言

DjangoFlask 是笔者最喜欢的两个 Python Web 框架,但两者定位截然不同

  • Django -> "More is less": 是 "大而全" 的重量级 Web 框架,其自带大量的常用工具和组件(比如数据库ORM组件、用户认证、权限管理、分页、缓存), 甚至还自带了管理后台Admin,适合快速开发功能完善的企业级网站

  • Flask -> "Less is more": 是一个轻便灵活又易于扩展的 "微" 框架,默认情况下,Django 自带的那些工具和组件,Flask 通通都没有,只提供一个非常简洁高效的 "路由组件"

平日里,做些小工具小应用啥的,笔者还是比较喜欢 Flask 的,借助这样小巧的微框架,数十分钟就能撸一个出来。

但是上升到写比较偏大型一点的应用,笔者一般选择的都是 Django。最主要的原因无非就是:它的 ORM 模块实在太好用了。

而在 Flask 中,用的最多 ORM 框架的还是 SQLAlchemy,但是个人感觉其友好程度比不上 DjangoORM

所以笔者萌生了一个想法:Flask + DjangoORM

开工

手动创建项目

$ mkdir FlaskWithDjangoORM

pip 安装所需依赖库

$ pip install flask django mysqlclient

手动创建 app 应用

在标准的 Django 项目中,创建名为 app 的子应用,用到的命令为 $ python manage.py startapp app,该命令生成的子应用其目录下,一般会有这么几个文件/目录:

  • migrations/ 该目录存放数据库迁移文件
  • admin.py 该文件与管理后台相关
  • apps.py 该文件与子应用设置相关
  • models.py 该文件存放数据库模型
  • tests.py 该文件存放单元测试
  • views.py 该文件存放视图层代码

但如果我们只用 DjangoORM 模块的话,那么只需留下 migrations/models.py。故弃用命令,手动创建之:

$ cd FlaskWithDjangoORM
$ mkdir -p app/migrations
$ touch app/migrations/__init__.py

app 子应用初始化

$ cat >> app/__init__.py <<EOF
import os

from django.apps import apps
from django.conf import settings

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings")
apps.populate(settings.INSTALLED_APPS)
EOF

创建数据模型 Visit,记录每一次 Web 请求访问的时间

$ cat >> app/models.py <<EOF
from django.db import models

class Visit(models.Model):
    created_at = models.DateTimeField(auto_now_add=True, null=True)
EOF

settings.py 内配置数据库连接(本文示例采用 MySQL

$ cat >> settings.py <<EOF
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'flask-with-django-orm-sample',
        'USER': 'USERNAME',
        'PASSWORD': 'PASSWORD',
        'HOST': 'DATA_BASE_HOST',
        'PORT': '3306',
        'OPTIONS': {
            'charset': 'utf8mb4',
            # https://django-mysql.readthedocs.io/en/latest/checks.html#django-mysql-w002-innodb-strict-mode
            'init_command': "SET sql_mode='STRICT_TRANS_TABLES', innodb_strict_mode=1",
        },
    }
}

INSTALLED_APPS = ('app',)

SECRET_KEY = 'SOME_SECRET_KEY'

EOF

配置 manage.py 管理工具

这里从 标准的 Django 项目中搬运过来(需略改 setdefault 的参数)

$ cat >> manage.py <<EOF
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys


def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError(
            "Couldn't import Django. Are you sure it's installed and "
            "available on your PYTHONPATH environment variable? Did you "
            "forget to activate a virtual environment?"
        ) from exc
    execute_from_command_line(sys.argv)


if __name__ == '__main__':
    main()
EOF

至此,我们看一下目录结构:

.
├── app
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   └── models.py
├── manage.py
└── settings.py

仅这些文件,已经足以满足 Django ORM 运行的全部所需

将本地数据模型同步到数据库

创建迁移文件

$ python manage.py makemigrations
Migrations for 'app':
  app/migrations/0001_initial.py
    - Create model Visit

执行迁移

$ python manage.py migrate
Operations to perform:
  Apply all migrations: app
Running migrations:
  Applying app.0001_initial... OK

查看一下数据库

mysql> show databases;
+------------------------------+
| Database                     |
+------------------------------+
| flask-with-django-orm-sample |
| information_schema           |
| mysql                        |
+------------------------------+
3 rows in set (1.23 sec)

选择数据库并罗列所有数据库表

mysql> use flask-with-django-orm-sample;
Database changed

mysql> show tables;
+----------------------------------------+
| Tables_in_flask-with-django-orm-sample |
+----------------------------------------+
| app_visit                              |
| django_migrations                      |
+----------------------------------------+
2 rows in set (0.04 sec)

我们可以看到有一个迁移表 django_migrations,记录的是数据模型变更迁移的所有记录。

我们查询一下该表,可以看到已存在 0001_initial 这条记录,与之前运行 makemigrations 所生成的迁移文件 app/migrations/0001_initial.py 相吻合

mysql> select * from django_migrations;
+----+-----+--------------+----------------------------+
| id | app | name         | applied                    |
+----+-----+--------------+----------------------------+
|  1 | app | 0001_initial | 2020-12-20 08:01:16.618904 |
+----+-----+--------------+----------------------------+
1 row in set (0.22 sec)

看一下 app_visit 表结构,与我们在 app/models.py 里定义的 Visit 相吻合(Django ORM 默认创建的表名为 子应用名_数据模型类名

mysql> desc app_visit;
+------------+-------------+------+-----+---------+----------------+
| Field      | Type        | Null | Key | Default | Extra          |
+------------+-------------+------+-----+---------+----------------+
| id         | int(11)     | NO   | PRI | NULL    | auto_increment |
| created_at | datetime(6) | YES  |     | NULL    |                |
+------------+-------------+------+-----+---------+----------------+
2 rows in set (0.25 sec)

创建 server.py 作为 flask 的主文件

$ cat >> server.py <<EOF
from flask import Flask

from app.models import Visit

app = Flask(__name__)


@app.route("/")
def index():
    Visit.objects.create()
    return str(Visit.objects.count())


if __name__ == '__main__':
    app.run()
EOF

server.py 运行起来

$ python server.py
* Serving Flask app "server" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

测试效果

在新终端多次访问 localhost:5000/,可以看到返回从 1 开始依次递增

访问返回从 1 开始依次递增

查看下程序日志

 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [20/Dec/2020 09:52:56] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:52:58] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:00] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:01] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:03] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:04] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:05] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:06] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:07] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:08] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:12] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:13] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:14] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:16] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:17] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:18] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:19] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [20/Dec/2020 09:53:26] "GET / HTTP/1.1" 200 -

再检查一下数据库

mysql> select * from app_visit;
+----+----------------------------+
| id | created_at                 |
+----+----------------------------+
|  1 | 2020-12-20 09:52:56.660151 |
|  2 | 2020-12-20 09:52:58.761042 |
|  3 | 2020-12-20 09:53:00.393140 |
|  4 | 2020-12-20 09:53:01.811832 |
|  5 | 2020-12-20 09:53:03.216767 |
|  6 | 2020-12-20 09:53:04.471404 |
|  7 | 2020-12-20 09:53:05.639631 |
|  8 | 2020-12-20 09:53:06.790564 |
|  9 | 2020-12-20 09:53:07.823484 |
| 10 | 2020-12-20 09:53:08.836709 |
| 11 | 2020-12-20 09:53:12.069902 |
| 12 | 2020-12-20 09:53:13.293624 |
| 13 | 2020-12-20 09:53:14.799734 |
| 14 | 2020-12-20 09:53:15.993364 |
| 15 | 2020-12-20 09:53:17.022426 |
| 16 | 2020-12-20 09:53:18.141073 |
| 17 | 2020-12-20 09:53:19.499833 |
| 18 | 2020-12-20 09:53:26.608735 |
+----+----------------------------+
18 rows in set (0.02 sec)

日志里的访问记录和数据库里存储的访问记录,完全一致,大功告成!收工睡觉!

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

推荐阅读更多精彩内容

  • 梗概 ORM的核心是中间的R, 关系的建立以及关系的使用 一对多表, 一表是主表, 多表是从表主表和从表的时间没有...
    奇奇乌布里阅读 3,110评论 0 6
  • PythonWeb框架要点、Django介绍、工程搭建、配置、静态文件与路由 1.Python Web 框架要点 ...
    Cestine阅读 1,491评论 0 6
  • Django介绍 1.简介 ​ Django,发音为[`dʒæŋɡəʊ],是用python语言写的开源web开...
    懵懂_傻孩纸阅读 484评论 0 1
  • django 模型是数据库中表的映射,也叫对象关系映射(Object Relational Mapping,简称O...
    eeert2阅读 63评论 0 0
  • ORM模型迁移 迁移命令: makemigrations:将模型生成迁移脚本。模型所在的app,必须放在setti...
    yungege阅读 820评论 1 0