本文档将展示如何使用 Django 构建一个 TiDB Web 应用程序。使用 django-tidb 模块作为数据访问能力的框架。示例应用程序的代码可从 Github 下载。
这是一个较为完整的构建 Restful API 的示例应用程序,展示了一个使用 TiDB 作为数据库的通用 Django 后端服务。该示例设计了以下过程,用于还原一个现实场景:
coinsgoodsid
你可以以此示例为基础,构建自己的应用程序。
第 1 步:启动你的 TiDB 集群
本节将介绍 TiDB 集群的启动方法。
使用 TiDB Serverless 集群
详细步骤,请参考:创建 TiDB Serverless 集群。
使用本地集群
详细步骤,请参考:部署本地测试 TiDB 集群或部署正式 TiDB 集群。
第 2 步:安装 Python
请在你的计算机上下载并安装 Python。本文的示例使用 Django 3.2.16 版本。根据 Django 文档,Django 3.2.16 版本支持 Python 3.6、3.7、3.8、3.9 和 3.10 版本,推荐使用 Python 3.10 版本。
第 3 步:获取应用程序代码
django_example
第 4 步:运行应用程序
python manage.py migratedjangoplayer
如果你想了解有关此应用程序的代码的详细信息,可参阅实现细节部分。
第 4 步第 1 部分:TiDB Cloud 更改参数
example_project/settings.pyDATABASES
DATABASES = {
'default': {
'ENGINE': 'django_tidb',
'NAME': 'django',
'USER': 'root',
'PASSWORD': '',
'HOST': '127.0.0.1',
'PORT': 4000,
},
}
123456
xxx.tidbcloud.com40002aEp24QWEDLqRFs.root
下面以 macOS 为例,应将参数更改为:
DATABASES = {
'default': {
'ENGINE': 'django_tidb',
'NAME': 'django',
'USER': '2aEp24QWEDLqRFs.root',
'PASSWORD': '123456',
'HOST': 'xxx.tidbcloud.com',
'PORT': 4000,
'OPTIONS': {
'ssl': {
"ca": "<ca_path>"
},
},
},
}
第 4 步第 2 部分:运行
cd <path>/tidb-example-python
pip install -r requirement.txt
cd django_example
python manage.py migrate
python manage.py runserver
第 4 步第 3 部分:输出
输出的最后部分应如下所示:
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
December 12, 2022 - 08:21:50
Django version 3.2.16, using settings 'example_project.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
如果你想了解有关此应用程序的代码的详细信息,可参阅实现细节部分。
第 5 步:HTTP 请求
http://localhost:8000
- 使用 Postman(推荐)
- 使用 curl
- 使用 Shell 脚本
实现细节
本小节介绍示例应用程序项目中的组件。
总览
本示例项目的目录树大致如下所示:
.
├── example_project
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── player
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ ├── urls.py
│ └── views.py
└── manage.py
其中:
__init__.pymanage.pyexample_projectsettings.pyurls.pyplayerPlayerpython manage.py startapp playerplayermodels.pyPlayermigrationspython manage.py makemigrations playermodels.pyurls.pyviews.py
项目配置
example_projectsettings.pysettings.py
...
# Application definition
INSTALLED_APPS = [
'player.apps.PlayerConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
...
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django_tidb',
'NAME': 'django',
'USER': 'root',
'PASSWORD': '',
'HOST': '127.0.0.1',
'PORT': 4000,
},
}
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
...
其中:
INSTALLED_APPSMIDDLEWARECsrfViewMiddlewareDATABASESENGINEdjango_tidb
根路由
example_projecturls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('player/', include('player.urls')),
path('admin/', admin.site.urls),
]
player/player.urlsplayerurls.pyplayer/
player 应用
playerPlayer
数据模型
models.pyPlayer
from django.db import models
# Create your models here.
class Player(models.Model):
id = models.AutoField(primary_key=True)
coins = models.IntegerField()
goods = models.IntegerField()
objects = models.Manager()
class Meta:
db_table = "player"
def as_dict(self):
return {
"id": self.id,
"coins": self.coins,
"goods": self.goods,
}
Metadb_tableplayer
idcoinsgoods
idmodels.AutoField(primary_key=True)coinsmodels.IntegerField()goodsmodels.IntegerField()
关于数据模型的详细信息,可查看 Django 模型文档。
数据模型迁移
models.pyPlayerpython manage.py makemigrations playermigrations0001_initial.py
# Generated by Django 3.2.16 on 2022-11-16 11:09
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Player',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
('coins', models.IntegerField()),
('goods', models.IntegerField()),
],
options={
'db_table': 'player',
},
),
]
python manage.py sqlmigrate ...python manage.py sqlmigrate player 0001
--
-- Create model Player
--
CREATE TABLE `player` (`id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, `coins` integer NOT NULL, `goods` integer NOT NULL);
python manage.py migrate
应用路由
player/player.urlsplayerurls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.create, name='create'),
path('count', views.count, name='count'),
path('limit/<int:limit>', views.limit_list, name='limit_list'),
path('<int:player_id>', views.get_by_id, name='get_by_id'),
path('trade', views.trade, name='trade'),
]
应用路由注册了 5 个路径:
''views.create'count'views.count'limit/'views.limit_listintintlimitlimit''views.get_by_id'trade'views.trade
player/
''http(s)://(:)/player'count'http(s)://(:)/player/count'limit/'limit3http(s)://(:)/player/limit/3
逻辑实现
playerviews.py
from django.db import transaction
from django.db.models import F
from django.shortcuts import get_object_or_404
from django.http import HttpResponse, JsonResponse
from django.views.decorators.http import *
from .models import Player
import json
@require_POST
def create(request):
dict_players = json.loads(request.body.decode('utf-8'))
players = list(map(
lambda p: Player(
coins=p['coins'],
goods=p['goods']
), dict_players))
result = Player.objects.bulk_create(objs=players)
return HttpResponse(f'create {len(result)} players.')
@require_GET
def count(request):
return HttpResponse(Player.objects.count())
@require_GET
def limit_list(request, limit: int = 0):
if limit == 0:
return HttpResponse("")
players = set(Player.objects.all()[:limit])
dict_players = list(map(lambda p: p.as_dict(), players))
return JsonResponse(dict_players, safe=False)
@require_GET
def get_by_id(request, player_id: int):
result = get_object_or_404(Player, pk=player_id).as_dict()
return JsonResponse(result)
@require_POST
@transaction.atomic
def trade(request):
sell_id, buy_id, amount, price = int(request.POST['sellID']), int(request.POST['buyID']), \
int(request.POST['amount']), int(request.POST['price'])
sell_player = Player.objects.select_for_update().get(id=sell_id)
if sell_player.goods < amount:
raise Exception(f'sell player {sell_player.id} goods not enough')
buy_player = Player.objects.select_for_update().get(id=buy_id)
if buy_player.coins < price:
raise Exception(f'buy player {buy_player.id} coins not enough')
Player.objects.filter(id=sell_id).update(goods=F('goods') - amount, coins=F('coins') + price)
Player.objects.filter(id=buy_id).update(goods=F('goods') + amount, coins=F('coins') - price)
return HttpResponse("trade successful")
下面将逐一解释代码中的重点部分:
dict_players = json.loads(request.body.decode('utf-8'))
players = list(map(
lambda p: Player(
coins=p['coins'],
goods=p['goods']
), dict_players))
result = Player.objects.bulk_create(objs=players)
return HttpResponse(f'create {len(result)} players.')
if limit == 0:
return HttpResponse("")
players = set(Player.objects.all()[:limit])
dict_players = list(map(lambda p: p.as_dict(), players))
return JsonResponse(dict_players, safe=False)
result = get_object_or_404(Player, pk=player_id).as_dict()
return JsonResponse(result)
sell_id, buy_id, amount, price = int(request.POST['sellID']), int(request.POST['buyID']), \
int(request.POST['amount']), int(request.POST['price'])
sell_player = Player.objects.select_for_update().get(id=sell_id)
if sell_player.goods < amount:
raise Exception(f'sell player {sell_player.id} goods not enough')
buy_player = Player.objects.select_for_update().get(id=buy_id)
if buy_player.coins < price:
raise Exception(f'buy player {buy_player.id} coins not enough')
Player.objects.filter(id=sell_id).update(goods=F('goods') - amount, coins=F('coins') + price)
Player.objects.filter(id=buy_id).update(goods=F('goods') + amount, coins=F('coins') - price)
return HttpResponse("trade successful")
创建相同依赖空白程序(可选)
django-admindjango_example
pip install -r requirement.txt
django-admin startproject copy_django_example
cd copy_django_example
DATABASES = {
'default': {
'ENGINE': 'django_tidb',
'NAME': 'django',
'USER': 'root',
'PASSWORD': '',
'HOST': '127.0.0.1',
'PORT': 4000,
},
}
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
至此,你已经完成了一个空白的应用程序,此应用程序与示例应用程序的依赖完全相同。如果需要进一步了解 Django 的使用方法,参考: