为Django模型提供花哨的ID字段。
项目描述
django-spicy-id
这是Django的AutoField
的一个替代品,它为您提供类似Stripe风格的自我标识的字符串对象ID,如user_1234
。
状态: 稳定。无保修,见LICENSE.txt
。
目录
什么是“花哨”的ID?
这是一个虚构的名字(因为我没有想到更好的名字),用于表示一种数值主键类型,以字符串形式显示和处理,前面有一个固定值。
以下是几个示例。您可以使用这个库使您的Django行ID看起来像
user_1234
account-00000000deadbeef
bloop:1a2k3841x
尽管你应该始终将这些值视为不透明的,并且永远不要在其他地方解码或解析字符串的内容(见错误),但你可以将每个spicy id看作是由以下部分组成的
<prefix> <separator> <encoded_value>
前缀
:一个固定字符串值,对于此记录类型的所有ID,永远不变。分隔符
:一个可配置的分隔符,与前缀
一样,永远不变;通常为_
(默认)或-
(另一种流行选择)。编码值
:ID的数字部分。此库支持使用16进制(十六进制)或62进制。
重要的是,底层数据库值仍然以数字类型存储和检索,就像AutoField
、SmallAutoField
或BigAutoField
一样。
为什么使用花哨的ID?
简要来说:因为它们对人类来说更容易操作。
- 可读性:虽然行级主键通常被视为“匿名”(例如,“没有人需要关心的事情”),但事实上,这些值仍然在许多情况下出现:它们在URL中,被记录在日志文件中,在查询中显示,等等。在这些情况下,当标识符本身告诉你其类型时,理解“我在看什么”只是更快。
- 冲突和意外预防:当你的系统需要你传递像
acct_1234
和invoice_5432beef
这样的带类型标识符时,某些类型的意外变得不可能。例如,HTTP DELETE /users/invoice_21
会快速失败。 - 对未来进行保障:采用spicy IDs意味着你的系统和API是开发为接受一个基本不透明的字符串作为ID的。虽然它们的底层类型是数字,但在非常高级的情况下,你可能能够迁移到不同的类型或数据存储“在字符串ID创建的抽象后面”。
有关此模式的更多详细信息,请参阅Stripe的“Object IDs:Designing APIs for Humans”。
安装
需求
此包支持并针对以下最新补丁版本进行了测试
- Python 3.9, 3.10, 3.11, 3.12
- Django 3.2, 4.2
- MySQL 5.7, 8.0
- PostgreSQL 9.5, 10, 11, 12
- SQLite 3.9.0+
所有数据库后端都使用其驱动程序的最新版本进行了测试。SQLite还在GitHub Actions的最新macOS虚拟环境中进行了测试。
注意:建议使用Django 4.x,因为3.x中存在一个上游错误。有关详细信息,请参阅#6。
说明
pip install django_spicy_id
用法
给定以下示例模型
from django.db import models
from django_spicy_id import SpicyBigAutoField
class User(models.model):
id = SpicyBigAutoField(primary_key=True, prefix='usr')
示例用法
>>> u = models.User.objects.create()
>>> u.id
'usr_1'
>>> u2 = models.User.objects.create(id=123456789)
>>> u2.id
'usr_8M0kX'
>>> found_user = models.User.objects.filter(id='usr_8M0kX').first()
>>> found_user == u2
True
字段类型
SpicyBigAutoField
:一个由BigAutoField
(即64位整型)列支持的spicy id。SpicyAutoField
:一个由AutoField
(即32位整型)列支持的spicy id。SpicySmallAutoField
:一个由SmallAutoField
(即16位整型)列支持的spicy id。
必需参数
在声明时需要以下参数
前缀
:在编码形式中使用的前缀。通常这是一个简短且描述性的字符串,如user
或acct
等。 注意:此库不确保你提供的字符串在项目中是唯一的。你应该确保这一点。
可选参数
除了所有可提供的参数外,上述每种字段类型还支持以下额外的可选参数
编码
:使用哪种数字编码方案。可以是django_spicy_id.ENCODING_BASE_62
(默认)、django_spicy_id.ENCODING_BASE_58
或django_spicy_id.ENCODING_HEX
之一。sep
:分隔符字符。默认为_
。可以是任何字符串。pad
:ID的编码部分是否应该用零填充,以便所有值都具有相同的字符串长度。可以是False
(默认)或True
。- 无填充的示例:
user_8M0kX
- 带填充的示例:
user_0000008M0kX
- 无填充的示例:
randomize
:如果设置为True
,新记录的默认值将使用secrets.randbelow()
随机生成。如果设置为False
(默认值),则与正常AutoField
一样工作,即默认值来自数据库的INSERT
操作。- 当设置
randomize
时,如果同时设置了default
,将会抛出错误,因为randomize
本质上是一个特殊且内置的default
函数。 - 由于
randomize
安装了一个特殊的default
函数,新的但未保存的模型实例将具有非None
的object.id
/object.pk
值。您必须使用object._state.adding
来确定实例是否为新的但未保存的对象。 - 如果您使用此功能,请注意其风险
- 生成的ID可能与现有行冲突,概率由生日问题确定(即列大小和现有数据集的大小)。
- 如果两个进程为
secrets.randbelow()
生成相同的值(即如果系统熵相同或由于某些原因配置不当),也可能出现冲突。
- 当设置
注册URL
当安装必须匹配特定香辛料ID的路由时,您可以使用get_url_converter()
辅助方法安装Django 自定义路径转换器。
使用此方法将确保只向视图呈现该字段的有效香辛料ID字符串。
示例
# models.py
class User(models.model):
id = SpicyBigAutoField(primary_key=True, prefix='usr')
# urls.py
from . import models
from django.urls import path, register_converter
from django_spicy_id import get_url_converter
# Register the pattern for `User.id` as "spicy_user_id". You should do this
# once for each unique spicy ID field.
register_converter(get_url_converter(models.User, 'id'), 'spicy_user_id')
urlpatterns = [
path('users/<spicy_user_id:id>', views.user_detail),
...
]
# views.py
def user_detail(request, id):
user = models.User.objects.get(id=id)
...
Django REST Framework
Django REST Framework (DRF)基本上与django-spicy-id
没有问题。但是,需要额外步骤,以便DRF将香辛料ID字段视为字符串,而不是整数,在序列化器中。
您可以使用包含的实用函数来修补DRF。可以安全地多次调用此方法。
from django_spicy_id import monkey_patch_drf
monkey_patch_drf()
字段属性
一旦构造,字段上就有以下属性
.validate_string(strval)
检查strval
是否为字段的合法值,如果不合法则抛出django_spicy_id.errors.MalformedSpicyIdError
。
.re
一个可以用来验证字符串的编译正则表达式。
.re_pattern
一个可以用来验证字符串的字符串正则表达式模式。与re
中使用的模式不同,此模式不包括前导的^
和尾随的$
边界字符,这使得它在像Django URL模式这样的东西中使用起来更容易。
您可能不需要直接使用此属性,而是查看get_url_converter()
。
实用方法
这些实用方法在顶层django_spicy_id
模块上提供。
get_url_converter(model_class, field_name)
返回用于在model_class
上的field_name
的Django 自定义路径转换器。
有关示例用法,请参阅注册URL。
错误
django.db.utils.ProgrammingError
当尝试使用非法值访问或查询此字段时抛出。以下是一些这种情况的示例
- 提供具有错误前缀或分隔符的香辛料ID(例如,预期的
id="invoice_1234"
,但提供的是id="acct_1234"
)。 - 提供一个包含非法字符的字符串(即编码部分不可解码)。
- 在启用填充时提供未填充的值。
- 在禁用填充时提供填充的值。
您可以将这些情况视为向其他任何字段类型提供错误类型对象的情况,例如SomeModel.objects.filter(id=object())
。
您可以通过首先验证输入来避免这种情况。请参阅字段属性。
🚨 警告:无论字段配置如何,香辛料ID的字符串值必须始终被视为一个精确值。就像您永远不会修改UUID4
的内容一样,香辛料ID字符串永远不应该被翻译、重新解释或由客户端更改。
django_spicy_id.MalformedSpicyIdError
ValueError
的一个子类,由.validate_string(strval)
在提供的字符串对于字段的配置无效时抛出。
技巧和窍门
不要更改字段配置
在开始使用字段之后更改prefix
、sep
、pad
或encoding
应被视为对外部调用者的破坏性更改。
虽然存储的行ID永远不会改变,但以前使用不同编码配置生成的任何辣条ID现在可能无效,或者(可能灾难性地)解析为不同的对象。
例如,user_10
如果解析为hex
与base62
或base58
,则自然地会指代不同的数字行ID。您应避免更改字段配置。
变更日志
请参阅CHANGELOG.md
以获取变更摘要。
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪一个,请了解有关安装包的更多信息。
源分发
构建分发
django-spicy-id-1.0.0.tar.gz的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | bdfc68624508e21265b89479dd5c0b96291f680ee922227a3adc9566b8434418 |
|
MD5 | 9475ed7522fa145dd4f3bff506c0a1e6 |
|
BLAKE2b-256 | aa6d98c22b42cb83dbe84a1c49e99cbbda55f11c7d67492952835367358d2d1f |
django_spicy_id-1.0.0-py3-none-any.whl的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 690ca1afee4a8b4b10ce92c24e02de96d6d3c9d1ec341c7581bed322512c80b0 |
|
MD5 | 9f58ee6ed1db408491536573becacbb6 |
|
BLAKE2b-256 | b95a19f5a6af8daed55a9ab9e0aa9cc556e9b490c584ca32b139920187897eca |