Django 应该可以说是我用过开发效率最高的框架了,开发体验非常好。

项目基础工具链

  • pipenv:为了让部署和协作更加方便和可重入,虚拟环境一般是有必要的
  • isort:依赖项排序
  • black:Black 是一个自动化代码格式化工具,它会自动调整 Python 代码的格式,使其符合一致的风格和规范。使用 Black 可以确保项目中的代码具有统一的格式,从而提高代码的可读性和可维护性
  • flake8:Flake8 是一个代码静态分析工具,它结合了一些常用的 Python 代码检查工具,如 PyFlakes、pycodestyle 和 McCabe,用于检查代码中的潜在问题和不规范之处。Flake8 可以帮助开发者在编写代码时发现并修复常见的错误,从而提高代码的质量和稳定性
  • pre-commit:在 Git 项目加入钩子,执行上述过程

创建一个 .pre-commit-config.yaml 文件,配置 pre-commit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
fail_fast: false
repos:
- repo: https://github.com/PyCQA/isort
rev: 5.12.0
hooks:
- id: isort
args: ["--profile", "black", "--filter-files"]
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
- repo: https://github.com/PyCQA/autoflake
rev: v2.2.1
hooks:
- id: autoflake
args:
[
"--in-place",
"--remove-all-unused-imports",
"--remove-unused-variables",
]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.4.0
hooks:
- id: flake8
args: [--max-line-length=150, "--ignore=F401,W503"]

手动运行 hook:

1
pre-commit run --all-files

Django 基础

Django(发音:JANG-oh,D 不发音)是一个基于 MVT(Model-view-template) 模式的框架。和 MVC(model-view-controller)类似,但又有一些不同。在 Django 中,view 仅仅描述你看到的数据,而不是你怎么看到它(外观)。view 在 Django 中是一个 基于 URL 的 Python 回调函数。

至于如果展示数据,则是由 template 决定。view 通常会将数据委托给 template,用于最终的显示。至于 controller,可能是框架本身。

创建项目

1
django-admin startproject mysite

manage.py:和 Django 交互的命令行工具

创建 app

1
python manage.py startapp myapp

开发服务器

1
python manage runserver

ORM

模糊搜索 __icontains

1
2
3
4
5
6
7
from django.db.models import Q

queryset = Media.objects.filter(
Q(filename__icontains=search_term) |
Q(original_name__icontains=search_term),
user=user
)

Q 对象用于构建复杂的查询条件,特别是需要 OR 或者需要动态构建查询条件的时候使用。

1
2
3
4
5
6
7
8
9
10
from django.db.models import Q

# OR 查询:查找名称包含"手机"或价格小于1000的商品
Product.objects.filter(Q(name__contains='手机') | Q(price__lt=1000))

# AND 查询:查找名称包含"手机"且价格小于1000的商品
Product.objects.filter(Q(name__contains='手机') & Q(price__lt=1000))

# 使用 NOT (~) 查询:查找名称不包含"手机"的商品
Product.objects.filter(~Q(name__contains='手机'))

常用的还有 F,作用是直接引用数据库中的字段值,用于数据库层面的操作:

1
2
3
4
5
6
7
8
9
10
11
12
# 不使用 F() 的方式更新
product = Product.objects.get(id=1)
product.inventory = product.inventory - 1
product.save()

# 使用 F() 的方式更新,更安全和高效
from django.db.models import F
Product.objects.filter(id=1).update(inventory=F('inventory') - 1)

# F() 还可以用于字段之间的比较
# 查找价格大于原价的促销商品
Product.objects.filter(sale_price__gt=F('original_price'))

Django Ninja

随着现代 Web 应用对 API 开发需求的增长,开发者们一直在寻找更优雅、更高效的解决方案,FastAPI 凭借其简洁的 API 实现,成为目前 Python 生态中非常流行的框架。

Django Ninja 汲取了 FastAPI 的优秀设计理念,同时完美融入 Django 生态系统。这个框架完美地结合了 Django 的强大功能和现代 API 开发的最佳实践,为开发者提供了类型提示、自动 API 文档生成、高性能等特性。对于那些既想保持 Django 稳定性和丰富生态,又渴望拥有现代化 API 开发体验的开发者来说,Django Ninja 无疑是一个理想的选择。

一次性拥有 Django 开箱即用的生态和 FastAPI 快速开发 API 的能力

1
2
3
4
5
6
7
from ninja import NinjaAPI

api = NinjaAPI()

@api.get("/hello")
def hello(request):
return "Hello world"

校验

Pydantic 是一个使用 Python 类型注解进行数据验证和管理的模块。它可以在代码运行时强制进行类型验证,当数据类型不符或数据无效时抛出友好的错误提示。FastAPI 整合了 Pydantic,而 Django Ninja 同样整合了 Pydantic。

1
2
3
4
from ninja import NinjaAPI, Schema

class HelloSchema(Schema):
name: str = "world"

定制存储服务

Django 默认的 media 存储是基于文件系统的,即 django.core.files.storage.FileSystemStorage

如果需要切换文件系统,可以通过继承 Storage 实现。

注意一下,Django 初始化 storage system 的时候是不带任何参数的,这也意味着设置必须从 django.conf.settings 中获取。另外,_open()_save() 方法是必须要实现的。

一个简单的定制 Storage 接入 Minio 示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@deconstructible
class MinioMediaStorage(Storage):
def __init__(self):
self.client = Minio(
endpoint=settings.MINIO_STORAGE_ENDPOINT,
access_key=settings.MINIO_STORAGE_ACCESS_KEY,
secret_key=settings.MINIO_STORAGE_SECRET_KEY,
secure=settings.MINIO_STORAGE_USE_HTTPS,
)
self.bucket_name = settings.MINIO_STORAGE_MEDIA_BUCKET_NAME

# 确保 bucket 存在
if not self.client.bucket_exists(self.bucket_name):
self.client.make_bucket(self.bucket_name)

def _save(self, name, content):
# 确保文件名唯一
name = self.get_available_name(name)

# 获取文件类型
content_type = mimetypes.guess_type(name)[0]

# 上传文件
self.client.put_object(
bucket_name=self.bucket_name,
object_name=name,
data=content,
length=content.size,
content_type=content_type,
)

return name

def _open(self, name, mode="rb"):
# 获取文件数据
data = self.client.get_object(bucket_name=self.bucket_name, object_name=name)
return data

def delete(self, name):
try:
self.client.remove_object(bucket_name=self.bucket_name, object_name=name)
except Exception:
pass

def exists(self, name) -> bool:
try:
self.client.stat_object(bucket_name=self.bucket_name, object_name=name)
return True
except Exception:
return False

def url(self, name):
"""
返回文件的URL
"""
filename = name.replace("\\", "%5c")
if settings.ENV == "local":
return urljoin(
f"http://{settings.MINIO_STORAGE_ENDPOINT}/{self.bucket_name}/",
filename,
)

try:
return f"/{self.bucket_name}/{filename}"
except Exception:
return None

def get_available_name(self, name, max_length=None):
"""
生成可用的文件名,避免重复
"""
dir_name, file_name = os.path.split(name)
file_root, file_ext = os.path.splitext(file_name)

if not self.exists(name):
return name

# 添加时间戳
timestamp = int(time.time() * 1000)
new_name = os.path.join(dir_name, f"{file_root}_{timestamp}{file_ext}")

return new_name

url 假定的是 bucket 是公开的,如果需要生成临时连接,可以参考:

1
2
3
4
5
6
# 临时URL
url = self.client.presigned_get_object(
bucket_name=self.bucket_name,
object_name=name,
expires=timedelta(hours=1),
)

HTMX

(TODO)

小结

Django 的文档其实堪称开源项目文档的典范之一,非常详细,其实我不需要写太多的内容,基本上都可以在那近三千页的文档上找到需要的答案。加上框架非常经典,目前大模型的补全和生成代码的可用性已经非常高了。有了 Django Ninja 之后,Django 获得了类似 FastAPI 那样快速开发 API 的体验,非常适合快速实现原型。

参考资料

  1. Django documentation