Django代理字段

时间:2016-05-24 23:06:32

标签: python django

是否可以创建一个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)

2 个答案:

答案 0 :(得分:1)

Django中有代理模型,但是我不确定它是否具有类似代理字段的内容。

对于您的用例,您可以按照以下说明进行操作:

  1. 创建一个字段列表,每个字段包含名称,类型,可为空等。
  2. 在模型中添加一个函数,以返回与传递给它的每种字段类型相对应的实际django rest框架(DRF)字段类实例。
  3. 使用DRF内置字段类验证来针对save()中的指定字段类型验证字段数据。
  4. 除了自动验证之外,您还将获得自动类型转换。例如。如果用户输入数字1作为文本:整数字段为“ 1”,它将自动将其转换回整数1。同样,它将适用于float,Bool,Char等

`

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)

为了使该字段虚拟,您需要:

  1. 重写Field.get_attname_column()方法,该方法必须返回两元组attname, None作为attnamecolumn的值。
  2. 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