Skip to content

Instantly share code, notes, and snippets.

@codetalks-new
Created May 30, 2019 08:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codetalks-new/dc79f6055031aea154b91a02b27cbed2 to your computer and use it in GitHub Desktop.
Save codetalks-new/dc79f6055031aea154b91a02b27cbed2 to your computer and use it in GitHub Desktop.
Model 保存之后第延迟初始化的 CharField 字段实现
class BasicDeferredDefaultAttribute:
"""
实现支持延迟的 deferred_default 方法
在之前的 Video及Post, asset_id 相关的默认属性生成需要用到 pk, pk 是数据库保存之后自动生成的.
本 Descriptor 实现逻辑是相当于修改版本的 DeferredAttribute. 如果有 pk 并且对应的值为空值.则调用对应的函数读取值并设置.
然后再调用 save.
这个方法要求 Model 在 init 中设置对应的值为 models.DEFERRED
```py
def __init__(*args,**kwargs):
kwargs.setdefault('<字段名>', models.DEFERRED)
```
另一个实现上面逻辑的思路是使用 `post_save`, `post_save` 的一个小问题是,如果调用 `bulk_create` 创建的话,
`save` 及相关的 `post_save`. 不会调用.
参考 django.db.models.query_utils.DeferredAttribute
"""
def __init__(self, *, field, deferred_default: Callable[[models.Model], any]):
self.field_name = field.name
self.field = field
self.deferred_default = deferred_default
def __get__(self, instance, cls=None):
if instance is None:
return self
data: dict = instance.__dict__
old_val = data.get(self.field_name)
if old_val:
return old_val
if not instance.pk:
if old_val is None:
return self.field.get_default()
return old_val
val = self.deferred_default(instance)
assert val
data[self.field_name] = val
instance.save(update_fields=[self.field_name])
return val
class BasicDeferredDefaultFieldMixin:
"""模型属性实现为 DeferredDefaultAttribute 的字段 Mixin, 只支持简单的标量字段 """
def __init__(self, *args, **kwargs):
deferred_default = kwargs.pop("deferred_default", None)
if not (deferred_default and callable(deferred_default)):
raise ValueError("deferred_default 不能为空并必须是可调用的")
self.deferred_default = deferred_default
super(BasicDeferredDefaultFieldMixin, self).__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super(
BasicDeferredDefaultFieldMixin, self
).deconstruct()
kwargs["deferred_default"] = self.deferred_default
return name, path, args, kwargs
def contribute_to_class(self, cls, name, **kwargs):
super(BasicDeferredDefaultFieldMixin, self).contribute_to_class(
cls, name, **kwargs
)
setattr(
cls,
name,
BasicDeferredDefaultAttribute(
field=self, deferred_default=self.deferred_default
),
)
class DeferredCharField(BasicDeferredDefaultFieldMixin, models.CharField):
"""相关文档说明参考 BasicDeferredDefaultAttribute """
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment