This is a Django REST Framework's PrimaryKeyRelatedField
like field which
allows you to pass custom fields (instead of default pk) to serialize relation.
- Python 3.6+
- Django 2+
- djangorestframework 3+
pip install drf-custom-related-field
For example, we have following model structure:
from django.db import models
class Company(models.Model):
name = models.CharField(max_length=100)
country = models.CharField(max_length=100)
def upper_name(self):
return self.name.upper()
class Address(models.Model):
street = models.CharField(max_length=255)
building = models.CharField(max_length=255)
def full_address(self):
return f'{self.street}, {self.building}'
class WorkingBuilding(models.Model):
capacity = models.IntegerField(default=0)
address = models.ForeignKey(Address, on_delete=models.CASCADE)
class Employee(models.Model):
username = models.CharField(max_length=100)
company = models.ForeignKey(Company, on_delete=models.CASCADE)
workplace = models.ForeignKey(WorkingBuilding, null=True, on_delete=models.CASCADE, related_name='employees')
And we have following instances:
work_address = Address.objects.create(street='Main st.', building='10')
workplace = WorkingBuilding.objects.create(capacity=200, address=work_address)
company = Company.objects.create(name='Great Inc.', country='US', )
employee = Employee.objects.create(username='ckkz', company=company, workplace=workplace)
Now we ready to use our field. To specify which field we want to map for relation,
pass required field_name
argument to the CustomRelatedField
. It can be either
str
or simple callable (with no arguments) that returns str
.
For example:
CustomRelatedField(field_name='name', read_only=True)
def get_field_name():
return 'some_field'
CustomRelatedField(field_name=get_field_name, read_only=True)
Usage examples:
- Map custom field for read only.
class EmployeeSerializer(serializers.ModelSerializer):
company = CustomRelatedField(queryset=Company.objects, field_name='name')
class Meta:
model = Employee
fields = ('username', 'company')
serializer = EmployeeSerializer(employee)
assert serializer.data['company'] == company.name
{
"username": "ckkz",
"company": "Great Inc."
}
- Assign new value by custom field (
name
in this case)
class EmployeeSerializer(serializers.ModelSerializer):
company = CustomRelatedField(queryset=Company.objects, field_name='name')
class Meta:
model = Employee
fields = ('username', 'company')
new_company = Company.objects.create(name='New Company', country='RU')
serializer = EmployeeSerializer(employee, data={'company': new_company.name}, partial=True)
serializer.is_valid(raise_exception=True)
serializer.save()
employee.refresh_from_db()
assert employee.company_id == new_company.id
{
"username": "ckkz",
"company": "New Company"
}
- Use
many=True
class WorkingBuildingSerializer(serializers.ModelSerializer):
employees = CustomRelatedField(field_name='username', many=True, read_only=True)
class Meta:
model = WorkingBuilding
fields = ('capacity', 'employees')
serializer = WorkingBuildingSerializer(workplace)
assert len(serializer.data['employees']) == workplace.employees.count()
{
"capacity": 200,
"employees": ["ckkz"]
}
- Use nested (dotted) relations and callable model fields
class EmployeeSerializer(serializers.ModelSerializer):
workplace = CustomRelatedField(source='workplace.address', field_name='full_address', read_only=True)
class Meta:
model = Employee
fields = ('username', 'workplace')
serializer = EmployeeSerializer(employee)
assert serializer.data['workplace'] == employee.workplace.address.full_address()
{
"username": "ckkz",
"workplace": "Main st., 10"
}