2👍
In order to make the field virtual, you need to:
- Override the
Field.get_attname_column()
method, which must return two-tupleattname, None
as the value forattname
andcolumn
. - Set the
private_only
parameter toTrue
in theField.contribute_to_class()
method.
A proxy field must also have a reference to the concrete field in order to be able to access to it. Here I will use the concrete_field
parameter.
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)
If you are sure that the concrete field represents a dict-like object, then you can change the logic for getting / setting value. Maybe something like this:
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
1👍
There are proxy models in django, But I am not sure if it has something like proxy fields.
For your use case, you can do as mentioned below:
- Create a list of fields with each field containing name, type, nullable, etc.
- Add a function in your model to return actual django rest framework (DRF) field class instance, corresponding to each field type passed to it.
- Use DRF inbuilt field class validation to validate your field data against specified field type in save().
- In addition to automatic validation, you will also get automatic type conversion. Eg. If user entered number 1 as text: “1” for a integer field then it will automatically convert it back to integer 1. Same way, it will work for float, Bool, Char, etc
`
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)`
You may try and figure out django’s field classes (Instead of DRF) and use them for validation, if required.
You can inherit this new MyModel class to achieve similar capability in other models and to reuse code.
- Django annotating with a first element of a related queryset
- Django-admin.py and python path on EC2 Amazon Beanstalk
- How do you update a Django Form Meta class fields dynamically from the form constructor?
- X-editable inline editing in Django – how to get CSRF protection?
- Overriding Size of Django Admin Multi-select Widget
Source:stackexchange.com