Fixes #23154: Correct required=False mismatch on L2VPN.type and RackType.form_factor
Both are non-nullable CharFields with choices, no default, and blank=False -- so the model genuinely requires a value, but their serializer fields were declared (or inherited) as optional, causing OPTIONS to advertise them as required=False while POST rejects their omission with a confusing model-level "This field cannot be blank." error instead of a normal field-required error. - vpn.L2VPN.type: the serializer explicitly set required=False on a field with no model default; changed to required=True. - dcim.RackType.form_factor: inherited its field declaration from RackBaseSerializer, which is correct for Rack.form_factor (genuinely optional/nullable) but wrong for RackType.form_factor (required). Added an override in RackTypeSerializer with no required/allow_blank/allow_null kwargs, so it defaults to required=True like every other undeclared ChoiceField. Searched the rest of the codebase for the same shape (choices field, no default, blank=False, with an explicit required=False override) -- these were the only two instances. Adds a regression test to each app's test_api.py asserting OPTIONS reports required=True and a POST omitting the field returns 400 naming the field.
This commit is contained in:
parent
fc5172f170
commit
f840ed81d1
|
|
@ -86,6 +86,9 @@ class RackBaseSerializer(PrimaryModelSerializer):
|
|||
|
||||
|
||||
class RackTypeSerializer(RackBaseSerializer):
|
||||
# Unlike Rack.form_factor (optional & nullable), RackType.form_factor is required
|
||||
# (blank=False, no default), so override RackBaseSerializer's optional declaration.
|
||||
form_factor = ChoiceField(choices=RackFormFactorChoices)
|
||||
manufacturer = ManufacturerSerializer(nested=True)
|
||||
rack_count = serializers.IntegerField(read_only=True)
|
||||
|
||||
|
|
|
|||
|
|
@ -1206,6 +1206,28 @@ class RackTypeTestCase(APIViewTestCases.APIViewTestCase):
|
|||
},
|
||||
]
|
||||
|
||||
def test_form_factor_required(self):
|
||||
"""
|
||||
Regression test for #23154: form_factor must be reported as required by
|
||||
OPTIONS, and a POST omitting it must be rejected with a normal "required"
|
||||
validation error rather than a model-level "cannot be blank" error.
|
||||
"""
|
||||
self.add_permissions('dcim.add_racktype')
|
||||
|
||||
response = self.client.options(self._get_list_url(), **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertTrue(response.data['actions']['POST']['form_factor']['required'])
|
||||
|
||||
manufacturer = Manufacturer.objects.first()
|
||||
data = {
|
||||
'manufacturer': manufacturer.pk,
|
||||
'model': 'Rack Type Missing Form Factor',
|
||||
'slug': 'rack-type-missing-form-factor',
|
||||
}
|
||||
response = self.client.post(self._get_list_url(), data, format='json', **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('form_factor', response.data)
|
||||
|
||||
|
||||
class RackTestCase(APIViewTestCases.APIViewTestCase):
|
||||
model = Rack
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ __all__ = (
|
|||
|
||||
|
||||
class L2VPNSerializer(PrimaryModelSerializer):
|
||||
type = ChoiceField(choices=L2VPNTypeChoices, required=False)
|
||||
type = ChoiceField(choices=L2VPNTypeChoices, required=True)
|
||||
import_targets = SerializedPKRelatedField(
|
||||
queryset=RouteTarget.objects.all(),
|
||||
serializer=RouteTargetSerializer,
|
||||
|
|
|
|||
|
|
@ -601,6 +601,26 @@ class L2VPNTestCase(APIViewTestCases.APIViewTestCase):
|
|||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertEqual(response_data['count'], 1)
|
||||
|
||||
def test_type_required(self):
|
||||
"""
|
||||
Regression test for #23154: type must be reported as required by OPTIONS,
|
||||
and a POST omitting it must be rejected with a normal "required" validation
|
||||
error rather than a model-level "cannot be blank" error.
|
||||
"""
|
||||
self.add_permissions('vpn.add_l2vpn')
|
||||
|
||||
response = self.client.options(self._get_list_url(), **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_200_OK)
|
||||
self.assertTrue(response.data['actions']['POST']['type']['required'])
|
||||
|
||||
data = {
|
||||
'name': 'L2VPN Missing Type',
|
||||
'slug': 'l2vpn-missing-type',
|
||||
}
|
||||
response = self.client.post(self._get_list_url(), data, format='json', **self.header)
|
||||
self.assertHttpStatus(response, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertIn('type', response.data)
|
||||
|
||||
|
||||
class L2VPNTerminationTestCase(APIViewTestCases.APIViewTestCase):
|
||||
model = L2VPNTermination
|
||||
|
|
|
|||
Loading…
Reference in New Issue