Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def pytest_configure(config):
'rest_framework.authtoken',
'tests.authentication',
'tests.generic_relations',
'tests.issue',
'tests.importable',
'tests',
),
Expand Down
Empty file added tests/issue/__init__.py
Empty file.
42 changes: 42 additions & 0 deletions tests/issue/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by Django 3.1.13 on 2022-01-23 09:00

Comment on lines +1 to +2
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This migration includes an auto-generated header with a specific Django version/date ("Generated by Django 3.1.13 on 2022-01-23"). Other test apps' migrations in this repo are kept minimal and don't embed generator metadata (e.g., tests/authentication/migrations/0001_initial.py). Consider removing the header to avoid confusion and unnecessary churn across Django versions.

Suggested change
# Generated by Django 3.1.13 on 2022-01-23 09:00

Copilot uses AI. Check for mistakes.
import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Item',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=10)),
],
),
migrations.CreateModel(
name='ItemAmount',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('amount', models.IntegerField()),
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='issue.item')),
],
),
migrations.CreateModel(
name='Summary',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('items', models.ManyToManyField(through='issue.ItemAmount', to='issue.Item')),
],
),
migrations.AddField(
model_name='itemamount',
name='summary',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='issue.summary'),
),
]
Empty file.
15 changes: 15 additions & 0 deletions tests/issue/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.db import models


class Item(models.Model):
name = models.CharField(max_length=10)


class ItemAmount(models.Model):
summary = models.ForeignKey('Summary', on_delete=models.CASCADE)
item = models.ForeignKey(Item, on_delete=models.CASCADE)
amount = models.IntegerField()


class Summary(models.Model):
items = models.ManyToManyField(Item, through=ItemAmount)
30 changes: 30 additions & 0 deletions tests/issue/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from rest_framework import serializers

from . import models


class ItemAmountSerializer(serializers.ModelSerializer):
item = serializers.PrimaryKeyRelatedField(queryset=models.Item.objects.all())

class Meta:
model = models.ItemAmount
fields = ('item', 'amount')


class SummarySerializer(serializers.ModelSerializer):
items = ItemAmountSerializer(source='itemamount_set', many=True)

def create(self, validated_data):
items = validated_data.pop('itemamount_set')
instance = super().create(validated_data)
for item in items:
instance.items.add(
item['item'], through_defaults=dict(
amount=item['amount']
)
)
return instance

class Meta:
model = models.Summary
fields = ('items', )
65 changes: 65 additions & 0 deletions tests/issue/test_issue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from django.test import TestCase, override_settings
from django.urls import reverse

from rest_framework.test import APIClient
from rest_framework.utils import json

from . import models, serializers


class TestSerializer(TestCase):
def test_create(self):
item = models.Item.objects.create(name='test')
data = {
"items": [
{
"item": item.id,
"amount": 100,
}
],
}
serializer = serializers.SummarySerializer(data=data)
serializer.is_valid(raise_exception=True)
expected_data = {
"itemamount_set": [
{
"item": item,
"amount": 100,
}
],
}
assert serializer.validated_data == expected_data
serializer.save()
assert models.Summary.objects.count() == 1


@override_settings(ROOT_URLCONF='tests.issue.urls')
class TestIssueViewSet(TestCase):
def test_create(self):
api_client = APIClient()
item = models.Item.objects.create(name='test')
data = {
"items": [
{
"item": item.id,
"amount": 100,
}
],
}
response = api_client.post(reverse('summary-list'), data)
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

APIClient.post(..., data) without an explicit format uses the default test request format (multipart). DRF's MultiPartRenderer explicitly does not support nested structures, and existing tests expect an AssertionError with a helpful message for nested multipart data (see tests/test_testing.py::test_invalid_multipart_data). This test currently expects a 201, but it should either (a) use format='json'/content_type='application/json' if the intent is to test nested JSON, or (b) assert that an AssertionError (or 400 with a specific error) is raised when trying to send nested data as multipart.

Suggested change
response = api_client.post(reverse('summary-list'), data)
response = api_client.post(reverse('summary-list'), data, format='json')

Copilot uses AI. Check for mistakes.
print(response.content)
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

print(response.content) will add noisy output to the test suite and can mask failures in CI logs. Please remove the print and assert on the relevant response content instead (e.g., status + response.data/error details).

Suggested change
print(response.content)

Copilot uses AI. Check for mistakes.
assert response.status_code == 201

def test_create_with_json(self):
api_client = APIClient()
item = models.Item.objects.create(name='test')
data = {
"items": [
{
"item": item.id,
"amount": 100,
}
],
}
response = api_client.post(reverse('summary-list'), json.dumps(data), content_type='application/json')
assert response.status_code == 201
Comment on lines +64 to +65
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency with the rest of the test suite, prefer api_client.post(..., data, format='json') over manually calling json.dumps + content_type='application/json'. This keeps tests shorter and ensures the DRF test client codepath is exercised consistently.

Copilot uses AI. Check for mistakes.
10 changes: 10 additions & 0 deletions tests/issue/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from rest_framework.routers import DefaultRouter

from . import views

app_name = "issue"
router = DefaultRouter()

router.register(r'summary', views.SummaryViewSet, basename='summary')

urlpatterns = router.urls
13 changes: 13 additions & 0 deletions tests/issue/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from rest_framework import viewsets
from rest_framework.permissions import AllowAny

from . import models, serializers


class SummaryViewSet(viewsets.ModelViewSet):
"""
A simple ViewSet for viewing and editing accounts.
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The viewset docstring says it edits "accounts", but this viewset is for Summary. Updating the docstring (or removing it) will avoid misleading documentation in the test app.

Suggested change
A simple ViewSet for viewing and editing accounts.
A simple ViewSet for viewing and editing summaries.

Copilot uses AI. Check for mistakes.
"""
queryset = models.Summary.objects.all()
serializer_class = serializers.SummarySerializer
permission_classes = [AllowAny]
Loading