Модель с внешними ключами занимает ~90 секунд на запрос (проблема модели с внешними ключами / сериализатора, я думаю)

У меня проблемы как с сериализатором, так и с моделями для таблицы, использующей внешние ключи. У меня есть представление для таблицы Cost (см. ниже), при запросе которого я получаю следующий результат примерно через 300-400 мс :

[
    {
        "id": 12,
        "hours1": 10,
        "hours2": 0,
        "hours3": 0,
        "hours4": 0,
        "date": "2021-07-12",
        "employee": 14,
        "job1": 417,
        "job2": 671,
        "job3": 671,
        "job4": 671
    },
    {
        "id": 13,
        "hours1": 8,
        "hours2": 0,
        "hours3": 0,
        "hours4": 0,
        "date": "2021-07-12",
        "employee": 10,
        "job1": 411,
        "job2": 671,
        "job3": 671,
        "job4": 671
    }
]

Поля employee, job1, job2, job3, job4 являются идентификаторами внешних ключей, для которых я хочу увидеть их основное/первичное значение (в данном случае имена!). Я использовал сериализатор для достижения этой цели, но проблема в том, что на один запрос уходит около 90 секунд и продолжает увеличиваться!

[
    {
        "id": 12,
        "employee": {
            "employee_name": "Person 1"
        },
        "job1": {
            "No": "30201"
        },
        "job2": {
            "No": "N/A"
        },
        "job3": {
            "No": "N/A"
        },
        "job4": {
            "No": "N/A"
        },
        "hours1": 10,
        "hours2": 0,
        "hours3": 0,
        "hours4": 0,
        "date": "2021-07-12"
    },
    {
        "id": 13,
        "employee": {
            "employee_name": "Person 2"
        },
        "job1": {
            "No": "30101"
        },
        "job2": {
            "No": "N/A"
        },
        "job3": {
            "No": "N/A"
        },
        "job4": {
            "No": "N/A"
        },
        "hours1": 8,
        "hours2": 0,
        "hours3": 0,
        "hours4": 0,
        "date": "2021-07-12"
    }
]

Моя схема и сериализаторы:

class Employee(models.Model):
    employee_name = models.CharField(max_length=200, unique=True)
    employee_email = models.CharField(max_length=200)
    employee_navID = models.CharField(max_length=200)
    employee_department = models.CharField(max_length=200)
    joining_date = models.DateField()
    leaving_date = models.DateField()
    weekly_salary = models.IntegerField(default=0)
    car_allowance = models.IntegerField()
    national_insurance = models.IntegerField()
    pension = models.IntegerField()
    created_at = models.DateField(auto_now_add=True)

    @property
    def status(self):
        if(self.leaving_date >= date.today()):
            return "active"
        else:
            return "inactive"

    @property
    def employee_cost(self):
        return self.weekly_salary + self.car_allowance + self.national_insurance + self.pension

    class Meta:
        verbose_name_plural = "Employees"

    def __str__(self):
        return self.employee_name


class ExcelJobsList(models.Model):
    No = models.CharField(max_length=200)
    Description = models.CharField(max_length=200)
    Custom_No = models.CharField(max_length=200)
    Sell_to_Name = models.CharField(max_length=200)
    Status = models.CharField(max_length=200)
    Person_Responsible = models.CharField(max_length=200)
    Region_Code = models.CharField(max_length=200)
    Market_Code = models.CharField(max_length=200)
    Agreement_Form_Code = models.CharField(max_length=200)
    Technology_Code = models.CharField(max_length=200)
    secondappr_Code = models.CharField(max_length=200)
    Search_Description = models.CharField(max_length=200)
    Work_in_Progress_Status = models.CharField(max_length=200)
    Job_Category = models.CharField(max_length=200)
    Project_Manager = models.CharField(max_length=200)
    Sales_Person = models.CharField(max_length=200)
    Payment_Terms_Code = models.CharField(max_length=200)
    First_Agreement_No = models.CharField(max_length=200)
    No_of_Service_Agreements = models.CharField(max_length=200)
    CRM_Reference = models.CharField(max_length=200)

    class Meta:
        verbose_name_plural = "Jobs"

    def __str__(self):
        return self.No

Они заполняются уникальным списком сотрудников и рабочих мест соответственно. Затем я использую это для создания базовой формы табеля учета рабочего времени, в которой пользователи могут назначать часы на работу (до 4). Это размещается в приведенной ниже модели, которая имеет внешние ключи к таблице сотрудников и вакансий.

class Cost(models.Model):
    employee = models.ForeignKey(
        Employee, default=1, on_delete=SET_DEFAULT)
    job1 = models.ForeignKey(ExcelJobsList, default=0, on_delete=SET_DEFAULT)
    hours1 = models.IntegerField()
    job2 = models.ForeignKey(
        ExcelJobsList, default=0, on_delete=SET_DEFAULT, related_name="job2")
    hours2 = models.IntegerField()
    job3 = models.ForeignKey(
        ExcelJobsList, default=0, on_delete=SET_DEFAULT, related_name="job3")
    hours3 = models.IntegerField()
    job4 = models.ForeignKey(
        ExcelJobsList, default=0, on_delete=SET_DEFAULT, related_name="job4")
    hours4 = models.IntegerField()
    date = models.DateField()

После выбора нужных мне полей из сериализатора получается следующий результат:

class JobModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = ExcelJobsList
        fields = ['No']

class EmployeeModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Employee
        fields = ['employee_name']

class CostModelSerializer(serializers.ModelSerializer):
        employee = EmployeeModelSerializer()
        job1 = JobModelSerializer()
        job2 = JobModelSerializer()
        job3 = JobModelSerializer()
        job4 = JobModelSerializer()

        class Meta:
            model = Cost
            fields = ('__all__')

class CostListFilter(filters.FilterSet):

    class Meta:
        model = Cost
        fields = {
            'employee': ['exact'],
        }

class JoinedFilterCostList(generics.ListAPIView):
    queryset = Cost.objects.filter()
    serializer_class = CostModelSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_class = CostListFilter

Я пробовал использовать to_field= 'xyz', однако это возвращает null, так что либо я что-то упускаю в своей модели, либо сериализатор неправильно настроен для этого... либо и то, и другое!

Любая помощь будет очень признательна.

Замедление вызвано тем, что приходится обращаться к базе данных 4 раза для каждого экземпляра Cost, чтобы получить все связанные ExcelJobsList.

Чтобы избежать этого и сделать его более эффективным, вы можете использовать select_related к связанным ExcelJobsList следующим образом:

class JoinedFilterCostList(generics.ListAPIView):
    ...
    queryset = Cost.objects.select_related('job1', 'job2', 'job3', 'job4')
    ...

В итоге должен получиться один запрос, с внутренними соединениями на связанных ExcelJobsList.

Вернуться на верх