是否可以创建一个Django代理字段,该字段可以访问另一个字段,但是没有为数据库保存自己的值,并且没有自己的数据库列?
这个用例是我们想要将值存储在JsonField
中,但是能够使用Django Fields的内置验证。这样做的第二个好处是能够添加新字段(具有验证功能)而不会影响数据库架构。
sudo代码可能看起来像这样:
from django.db import models
from django.contrib.postgres.fields import JsonField
class ProxyInitMixin(object):
def __init__(self, *args, *kwargs):
# some logic that will hold values if set on the Model
# but won't create a column or save anything to the
# database for this Field.
super(ProxyInitMixin, self).__init__(*args, **kwargs)
class ProxyIntegerField(ProxyInitMixin, models.Field):
pass
class ProxyCharField(ProxyInitMixin, models.Field):
pass
class MyModel(models.Model):
proxy_int = ProxyIntegerField()
proxy_char = ProxyCharField()
data = JsonField()
def save(self, *args, **kwargs):
self.data = {
'foo': self.proxy_int,
'bar': self.proxy_char
}
return super(MyModel, self).save(*args, **kwargs)
答案 0 :(得分:1)
Django中有代理模型,但是我不确定它是否具有类似代理字段的内容。
对于您的用例,您可以按照以下说明进行操作:
`
from django.db import models
from rest_framework.fields import IntegerField, FloatField, BooleanField, DateTimeField, CharField
class MyModel(models.Model):
FIELDS = [{'field_name': 'proxy_int', 'field_type': 'int', 'null_allowed': 'True'},
{'field_name': 'proxy_char', 'field_type': 'string', 'null_allowed': 'True'}]
data = JsonField()
def field_type(self, field):
if field.field_type == 'int':
return IntegerField()
elif field.field_type == 'float':
return FloatField()
elif field.field_type == 'bool':
return BooleanField()
elif field.field_type == 'date':
return DateTimeField()
elif self.value_type == 'string':
return CharField()
return CharField()
def save(self, *args, **kwargs):
data = kwargs.get('data', {})
new_data = {}
for (field in FIELDS)
field_name = field['field_name']
field_type = field['field_type']
field_value = data.get(field_name, None)
validated_value = self.field_type(field_type).run_validation(field_value)
new_data[field_name] = validated_value
kwargs['data'] = new_data
return super(MyModel, self).save(*args, **kwargs)`
如果需要,您可以尝试找出django的字段类(而不是DRF)并将其用于验证。
您可以继承这个新的MyModel类,以在其他模型中实现类似的功能并重用代码。
答案 1 :(得分:0)
为了使该字段虚拟,您需要:
Field.get_attname_column()
方法,该方法必须返回两元组attname, None
作为attname
和column
的值。private_only
方法中将True
参数设置为Field.contribute_to_class()
。代理字段也必须引用具体字段,以便能够访问它。在这里,我将使用concrete_field
参数。
class ProxyMixin(object):
"""
A mixin class that must be mixed-in with model fields.
The descriptor interface is also implemented in this mixin
class to keep value getting/setting logic on the Model.
"""
def __init__(self, *args, concrete_field=None, **kwargs):
self._concrete_field = concrete_field
super().__init__(*args, **kwargs)
def check(self, **kwargs):
return [
*super().check(**kwargs),
*self._check_concrete_field(),
]
def _check_concrete_field(self):
try:
self.model._meta.get_field(self._concrete_field)
except FieldDoesNotExist:
return [
checks.Error(
"The %s concrete field references the "
"nonexistent field '%s'." % (self.__class__.__name__, self._concrete_field),
obj=self,
id='myapp.E001',
)
]
else:
return []
def get_attname_column(self):
attname, column = super().get_attname_column()
return attname, None
def contribute_to_class(self, cls, name, private_only=False):
super().contribute_to_class(cls, name, private_only=True)
setattr(cls, name, self)
@property
def concrete_field(self):
"""
Returns the concrete Field instance.
"""
return self.model._meta.get_field(self._concrete_field)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
if self._concrete_field is not None:
kwargs['concrete_field'] = self._concrete_field
return name, path, args, kwargs
def __get__(self, instance, owner=None):
if instance is None:
return self
return getattr(instance, self._concrete_field)
def __set__(self, instance, value):
setattr(instance, self._concrete_field, value)
如果您确定具体字段表示类似dict的对象,则可以更改获取/设置值的逻辑。也许是这样的:
def __get__(self, instance, owner=None):
if instance is None:
return self
data = getattr(instance, self._concrete_field) or {}
return data.get(self.name, self.get_default())
def __set__(self, instance, value):
data = getattr(instance, self._concrete_field)
if data is None:
setattr(instance, self._concrete_field, {})
data = getattr(instance, self._concrete_field)
data[self.name] = value