I have created a set of models that allow users to define custom fields for specific objects in a Django project. This allows users to store data related to their particular use case without changing the database schema. For example, a project includes an embedded model Sitethat has a name, address, etc. The user can create a custom field for this model if they also wanted to save, for example, a named contact point for each site.
First, the user creates a custom field and assigns it to the model (s) in which they want to have this field. This is represented by an object CustomField. (Here is a simplified version of the model. A complete source is available here for anyone interested.)
class CustomField(models.Model):
obj_type = models.ManyToManyField(ContentType, related_name='custom_fields', verbose_name='Object(s)')
type = models.PositiveSmallIntegerField(choices=CUSTOMFIELD_TYPE_CHOICES, default=CF_TYPE_TEXT)
name = models.CharField(max_length=50, unique=True)
label = models.CharField(max_length=50, blank=True)
The second model contains custom field data for each object:
class CustomFieldValue(models.Model):
field = models.ForeignKey('CustomField', related_name='values')
obj_type = models.ForeignKey(ContentType, related_name='+', on_delete=models.PROTECT)
obj_id = models.PositiveIntegerField()
obj = GenericForeignKey('obj_type', 'obj_id')
serialized_value = models.CharField(max_length=255)
So, in our example, we will create a CustomField with a name point_of_contactfor the site model and an instance CustomFieldValuefor each site with POC.
I created a serializer to represent custom fields in the API as one child. For example, a site may appear as:
{
"id": 42,
"name": "My Site",
"slug": "site-1",
"physical_address": "123 Fake St",
...
"custom_fields": {
"point_of_contact": "Homer Simpson",
"decommissioned": false
}
}
The following is a simplified version of the serializer ( full version ):
class CustomFieldSerializer(serializers.Serializer):
"""
Extends a ModelSerializer to render any CustomFields and their values associated with an object.
"""
custom_fields = serializers.SerializerMethodField()
def get_custom_fields(self, obj):
fields = {cf.name: None for cf in self.context['view'].custom_fields}
for cfv in obj.custom_field_values.all():
fields[cfv.field.name] = cfv.value
return fields
The context is custom_fieldsprovided by custom APIView ( full version ):
class CustomFieldModelAPIView(object):
"""
Include the applicable set of CustomField in the view context.
"""
def __init__(self):
super(CustomFieldModelAPIView, self).__init__()
self.content_type = ContentType.objects.get_for_model(self.queryset.model)
self.custom_fields = self.content_type.custom_fields.all()
, , API. , , , .
CustomFieldValues ? .