Django Rest Framewrok: получен KeyError при написании теста для создания (post) вложенного сериализатора

Я пытаюсь написать тест для создания данных о продажах продолжаю получать KeyError: 'content' при запуске python manage.py test.

тест должен убедиться, что пользователь может добавлять/создавать данные о продажах с их деталями (вложенные) с this в качестве ссылки для создания записываемых вложенных сериализаторов

models.py

# abstract base table for transactions
class Base_transaction(models.Model):
    is_paid_off = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    pass

    class Meta:
        abstract = True


# sales table to store surface level sales information
# consist of sales_id as pk, customer_name, sub_total, discount, user_id,
# total, is_paid_off, created_at, updated_at
class Sales(Base_transaction):
    sales_id = models.BigAutoField(
        primary_key=True,
        unique=True
    )
    customer_name = models.CharField(max_length=25, blank=True)
    customer_contact = models.CharField(max_length=13, blank=True)
    user_id = models.ForeignKey(
        User,
        on_delete=models.PROTECT,
        null=True,
        db_column='user_id'
    )

    def __str__(self) -> str:
        return f'{self.sales_id} at {self.created_at} | Lunas={self.is_paid_off}'

    class Meta:
        db_table = 'sales'


# sales_detail table store the detail of sales per sparepart
# consist of sales_detail_id as pk, quantity, individual_price, total_price
# sales_id
class Sales_detail(models.Model):
    sales_detail_id = models.BigAutoField(
        primary_key=True,
        unique=True
    )
    quantity = models.PositiveSmallIntegerField()
    is_grosir = models.BooleanField(default=False)
    sales_id = models.ForeignKey(
        Sales,
        on_delete=models.CASCADE,
        db_column='sales_id'
    )
    sparepart_id = models.ForeignKey(
        'Sparepart',
        on_delete=models.SET_NULL,
        null=True,
        db_column='supplier_id'
    )

    def __str__(self) -> str:
        return f'{self.sales_id} - {self.sparepart_id}'

serializers.py

class SalesDetailSerializers(serializers.ModelSerializer):
    sparepart = serializers.ReadOnlyField(source='sparepart_id.name')

    class Meta:
        model = Sales_detail
        fields = ['sales_detail_id', 'sparepart', 'quantity', 'is_grosir']


class SalesSerializers(serializers.ModelSerializer):
    content = SalesDetailSerializers(many=True, source='sales_detail_set')

    class Meta:
        model = Sales
        fields = ['sales_id', 'customer_name', 'customer_contact', 'is_paid_off', 'content']

    def create(self, validated_data):
        details = validated_data.pop('content')

        sales = Sales.objects.create(**validated_data)
        for detail in details:
            Sales_detail.objects.create(sales_id=sales, **detail)
        return sales

test.py

class SalesAddTestCase(APITestCase):
    sales_url = reverse('sales_add')

    def setUp(self) -> None:
        # Setting up sparepart data
        for i in range(3):
            Sparepart.objects.create(
                name=f'random name{i}',
                partnumber=f'0Y3AD-FY{i}',
                quantity=50,
                motor_type='random m',
                sparepart_type='random s',
                price=5400000,
                grosir_price=5300000,
                brand_id=None
            )

        self.spareparts = Sparepart.objects.all()

        # Creating data that gonna be use as input
        self.data = {
            'customer_name': 'someone',
            'customer_contact': '085634405602',
            'is_paid_off': False,
            'content': [
                {
                    'sparepart': self.spareparts[1].sparepart_id,
                    'quantity': 1,
                    'is_grosir': False,
                },
                {
                    'sparepart': self.spareparts[0].sparepart_id,
                    'quantity': 30,
                    'is_grosir': True,
                }
            ]
        }

    def test_user_successfully_add_sales(self) -> None:
        """
        Ensure user can add new sales data with it's content
        """
        response = self.client.post(self.sales_url, self.data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['customer_name'], 'someone')
        self.assertEqual(len(response.data['content']), 2)

Представление, которое я использую generics.CreateAPIView

ожидаемый ответ будет таким же, как self.data, а поле sparepart станет именем этого sparepart пример ниже с использованием случайных данных для создания некоторого контекста

{
'sales_id': 5, # the pk of sales_id in this example just random number
'customer_name': 'someone',
'customer_contact': '085456105311',
'is_paid_off': False,
 'content': [
       {
         'sparepart': 'something1', # the name of sparepart 1
         'quantity': 5,
         'is_grosir': False,
       },
       {
          'sparepart': 'something2', # the name of sparepart 2
          'quantity': 3,
          'is_grosir': False,
       }]
}

я попытался изменить sparepart в содержимом на = self.spareparts[1], но теперь выдает type error Object of type Sparepart is not JSON serializable, изменение на self.spareparts[1].name дает ту же KeyError, что и раньше (содержимое).

я подозреваю ошибку, потому что поле sparepart readonlyfield в SalesDetailSerializer тогда я изменил его на charfield, но все равно получил тот же KeyError

Я действительно запутался, что заставляет эту ошибку возникать в первую очередь.

Я нашел проблему:

  1. sparepart поле в SalesDetailSerializers является ReadOnly и не может быть использовано для моей цели.

  2. content поле на SalesSerializers использует источник аргументов sales_detail_set. Я не знаю почему, но ключевое поле content в validated_data стало sales_detail_set, а не content.

Мои решения:

  1. Поскольку я уже написал тест, чтобы убедиться, что пользователь может получить список продаж с их деталями, который требует sparepart поле для отображения названия запчасти, я не могу изменить его. Вместо этого я создаю другой сериализатор с именами SalesDetailPostSerializers и SalesPostSerializers следующим образом.
class SalesDetailPostSerializers(serializers.ModelSerializer):
    class Meta:
        model = Sales_detail
        fields = ['sales_detail_id', 'sparepart_id', 'quantity', 'is_grosir']


class SalesPostSerializers(serializers.ModelSerializer):
    content = SalesDetailPostSerializers(many=True, source='sales_detail_set')

    class Meta:
        model = Sales
        fields = ['sales_id', 'customer_name', 'customer_contact', 'is_paid_off', 'content']

    def create(self, validated_data):
        details = validated_data.pop('sales_detail_set')
        sales = Sales.objects.create(**validated_data)
        for details in details:
            Sales_detail.objects.create(sales_id=sales, **details)
        return sales
  1. в SalesPostSerializer изменении
details = validated_data.pop('content')

To

details = validated_data.pop('sales_detail_set')

Затем измените имя ключа sparepart внутри self.data в test.py на sparepart_id

'sparepart_id': self.spareparts[1].sparepart_id,
Вернуться на верх