0

I have four models where I need to serialize three into one for my API. I can get the individual serializers to work as expected, but when combining them into one, I do not get the result I was expecting to see.

models.py

class President(models.Model):
    name = models.CharField('Name', max_length=50,)
    staff = models.ManyToManyField('Member',)
    def __str__(self):
        return self.name

"""
user creates a new member in this model below
if a new member is an employee, the object is copied into the Employee model
    and the user chooses the manager of the employee in the manager field
if a new member is a manager, the object is copied into the Manager model
"""
class Member(models.Model):
    president = models.ForeignKey(
        President, on_delete=models.CASCADE, related_name='members',
    )
    manager = models.ForeignKey(
        'Manager', on_delete=models.CASCADE, related_name='manager',
    )
    name = models.CharField('Name', max_length=50,)
    email = models.EmailField('Email Address',)
    title = models.CharField('Title', max_length=50,)
    staff_type = (
        ('Manager', 'Manager'),
        ('Employee', 'Employee'),
    )
    def __str__(self):
        return self.name

class Employee(models.Model):
    president = models.ForeignKey(
        President, on_delete=models.CASCADE, related_name='employeePresident'
    )
    manager = models.ForeignKey(
        'Manager', on_delete=models.CASCADE, related_name='employeeManager'
    )
    name = models.CharField('Name', max_length=50,)
    email = models.EmailField('Email Address',)
    title = models.CharField('Title', max_length=50,)
    def __str__(self):
        return self.name

class Manager(models.Model):
    president = models.ForeignKey(
        President, on_delete=models.CASCADE, related_name='managerPresident'
    )
    name = models.CharField('Name', max_length=50,)
    department = models.CharField('Department', max_length=50,)
    def __str__(self):
        return self.name

serializers.py

class PresidentSerializer(serializers.ModelSerializer):
    class Meta:
        model = President
        fields = '__all__'
class EmployeeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Employee
        fields = '__all__'
class ManagerSerializer(serializers.ModelSerializer):
    members = EmployeeSerializer(many=True,)
    class Meta:
        model = Manager
        fields = ('president', 'name', 'department', 'members')

Up to this point all of these serializers work as expected. The ManagerSerializer exposes the manager's name and the employees underneath them in a tree view like I want.

But, an employee does not necessarily have to be under a manager, for instance they could report directly to the President object, like a Presidents assistant.

How would I combine these three serializers into one where my API would be as follows:

{
    "name": "Presidents Name",
    "staff": [
        {
            "name": "Employee No Manager",
            "title": "Presidents Asst"
        },
        {
            "name": "John Doe",
            "title": "Finance Manager",
            "employees": [
                {
                    "name": "Mary Simpson",
                    "title": "Finance Asst"
                }
            ]
        }
}

Not sure if something needs to change in my model design where the President model can take in both Employee and Manager models. Any help would be greatly appreciated.

  • yes, it's a bad db structure. The point is that obj can have obj up and obj down. You just need a tree, so you should have just one model with an FK to itself e.g. boss = models.ForeignKey('self', related_name = 'employee', null=True). The obj with boss == None is your president – Goran Apr 12 '19 at 14:38

1 Answers1

0

I would simplify your db structure to just 1 class for every employee (and 2 extra classes for db normalization):

class JobTitle(models.Model):
    title = models.CharField('Title', max_length=50)

    def __str__(self):
        return self.title


class Department(models.Model):
    name = models.CharField('Department', max_length=50)

    def __str__(self):
        return self.name


class Employee(models.Model):
    name = models.CharField('Name', max_length=50)
    boss = models.ForeignKey("Employee", on_delete=models.CASCADE, related_name="staff", blank=True, null=True)
    department = models.ForeignKey("Department", on_delete=models.CASCADE)
    email = models.EmailField('Email Address')
    title = models.ForeignKey("JobTitle", on_delete=models.CASCADE)

    @property
    def is_president(self):
        return self.boss is None

    @property
    def is_manager(self):
        return len(self.staff) > 0

    def __str__(self):
        return self.name

Then you can use recursive serializer from this answer:

class RecursiveField(serializers.Serializer):
    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data

class EmployeeSerializer(serializers.ModelSerializer):
    staff = RecursiveField(many=True)

    class Meta:
        model = Employee
        fields = '__all__'
Adrian Krupa
  • 1,877
  • 1
  • 15
  • 24
  • I tried running it this way but the serializer just gives me a list of all the employees, not in a tree view like: President -> Boss -> Employee – Justin Futo Apr 12 '19 at 19:38
  • @JustinFuto I've edited the answer and added serializer. – Adrian Krupa Apr 15 '19 at 08:49
  • Adrian, thanks for your help. Those serializers make it work but at the end of my tree, the employees repeat themselves. Meaning, the president has it's managers and managers have their staff, but at the end, the serializer shows the all of the employees again. – Justin Futo Apr 15 '19 at 14:42