djangodjango-simple-history

Django-simple-history: serializer m2m objects


I'm using django-simple-history==3.2.0 library and my models show below:

class Employee(models.Model):
  last_name: str | None = models.CharField(max_length=100, null=True)  # type: ignore
  first_name: str | None = models.CharField(max_length=100, null=True)  # type: ignore
  middle_name: str | None = models.CharField(max_length=100, null=True)  # type: ignore
  job_title: str | None = models.CharField(max_length=512, null=True)  # type: ignore  
  
  class Meta:
    app_label = "task_flow"

class Assignment(models.Model):
  overdue: int = models.IntegerField(null=True) # type: ignore
  # ManyToManyField
  executors: QuerySet[Employee] = models.ManyToManyField(Employee, related_name="executor_assignments", blank=True)  # type: ignore
  coordinators: QuerySet[Employee] = models.ManyToManyField(Employee, related_name="coordinator_assignments", blank=True)  # type: ignore
  approvers: QuerySet[Employee] = models.ManyToManyField(Employee, related_name="approver_assignments", blank=True)  # type: ignore
  # ForeignKey
  status: Status | None = models.ForeignKey(Status, on_delete=RESTRICT, related_name="assignments")  # type: ignore
  access: Access = models.ForeignKey(Access, on_delete=RESTRICT, related_name="assignments", default=Access.Choices.general, null=True, blank=False)  # type: ignore
  document_type: DocumentType | None = models.ForeignKey(DocumentType, on_delete=SET_NULL, null=True, related_name="assignments")  # type: ignore
  domain: Domain = models.ForeignKey(Domain, on_delete=RESTRICT,  null=False, related_name="assignments", default=Domain.Choices.internal)  # type: ignore
  source: str | None = models.CharField(max_length=2048, null=True, blank=True)  # type: ignore
  created_at: datetime = models.DateTimeField(auto_now_add=True)  # type: ignore
  updated_at: datetime = models.DateTimeField(auto_now=True)  # type: ignore
  history = HistoricalRecords(m2m_fields=[executors, coordinators, approvers])

So, now i want to serializer Assignment.history.all() queryset with employees all fields like this:

"results": [
        {
            "id": 551,
            "overdue": 243,
            "executors": [
                {
                    "id": 32,
                    "last_name": "Копытов",
                    "first_name": "О.",
                    "middle_name": "В.",
                    "job_title": "Генеральный директор ТОО \"Корпорация Казахмыс\"",
                    "user": 470,
                    "domain": 1
                },
             ...
         }
]

When I try to get instance.approvers.all() in returns me list of task_flow_historicalassignment_approvers table instances, not emplyees.


Solution

  • I resolved this issue by writing serializer class with custom to_representation method:

    class AssignmentHistorySerializer(AssignmentTableSerializer):
      history_user = UserLogsSerializer(many=False)
      history_type = serializers.CharField()
    
      def to_representation(self, instance):
        rep = super().to_representation(instance)
        approvers = Employee.objects.filter(id__in=instance.approvers.all().values_list('employee_id', flat=True))
        coordinators = Employee.objects.filter(id__in=instance.coordinators.all().values_list('employee_id', flat=True))
        executors = Employee.objects.filter(id__in=instance.executors.all().values_list('employee_id', flat=True))
        rep['approvers'] = EmployeeSerializer(approvers, many=True).data
        rep['coordinators'] = EmployeeSerializer(coordinators, many=True).data
        rep['executors'] = EmployeeSerializer(executors, many=True).data
        rep['history_date'] = instance.history_date
        rep['history_user'] = UserLogsSerializer(instance.history_user).data
        rep['history_type'] = instance.get_history_type_display()  # Assuming there's a method to get a displayable type.
        rep['changed_fields'] = self.changed_fields(instance)
        rep['history_id'] = instance.history_id
        return rep
    
      def changed_fields(self, obj):
          if obj.prev_record:
            delta = obj.diff_against(obj.prev_record, excluded_fields=['updated_at', 'created_at', 'overdue', 'search_field'])
            return delta.changed_fields
          return None