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
Я действительно запутался, что заставляет эту ошибку возникать в первую очередь.
Я нашел проблему:
sparepart
поле вSalesDetailSerializers
является ReadOnly и не может быть использовано для моей цели.content
поле наSalesSerializers
использует источник аргументовsales_detail_set
. Я не знаю почему, но ключевое полеcontent
в validated_data сталоsales_detail_set
, а неcontent
.
Мои решения:
- Поскольку я уже написал тест, чтобы убедиться, что пользователь может получить список продаж с их деталями, который требует
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
- в
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,