mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 18:08:03 +03:00 
			
		
		
		
	Handle unset fields with 'many=True' (#7574)
* Handle unset fields with 'many=True' The docs note: When serializing fields with dotted notation, it may be necessary to provide a `default` value if any object is not present or is empty during attribute traversal. However, this doesn't work for fields with 'many=True'. When using these, the default is simply ignored. The solution is simple: do in 'ManyRelatedField' what we were already doing for 'Field', namely, catch possible 'AttributeError' and 'KeyError' exceptions and return the default if there is one set. Signed-off-by: Stephen Finucane <stephen@that.guru> Closes: #7550 * Add test cases for #7550 Signed-off-by: Stephen Finucane <stephen@that.guru>
This commit is contained in:
		
							parent
							
								
									26830c3d2d
								
							
						
					
					
						commit
						5185cc9348
					
				| 
						 | 
				
			
			@ -10,7 +10,7 @@ from django.utils.encoding import smart_str, uri_to_iri
 | 
			
		|||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
from rest_framework.fields import (
 | 
			
		||||
    Field, empty, get_attribute, is_simple_callable, iter_options
 | 
			
		||||
    Field, SkipField, empty, get_attribute, is_simple_callable, iter_options
 | 
			
		||||
)
 | 
			
		||||
from rest_framework.reverse import reverse
 | 
			
		||||
from rest_framework.settings import api_settings
 | 
			
		||||
| 
						 | 
				
			
			@ -535,7 +535,30 @@ class ManyRelatedField(Field):
 | 
			
		|||
        if hasattr(instance, 'pk') and instance.pk is None:
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        relationship = get_attribute(instance, self.source_attrs)
 | 
			
		||||
        try:
 | 
			
		||||
            relationship = get_attribute(instance, self.source_attrs)
 | 
			
		||||
        except (KeyError, AttributeError) as exc:
 | 
			
		||||
            if self.default is not empty:
 | 
			
		||||
                return self.get_default()
 | 
			
		||||
            if self.allow_null:
 | 
			
		||||
                return None
 | 
			
		||||
            if not self.required:
 | 
			
		||||
                raise SkipField()
 | 
			
		||||
            msg = (
 | 
			
		||||
                'Got {exc_type} when attempting to get a value for field '
 | 
			
		||||
                '`{field}` on serializer `{serializer}`.\nThe serializer '
 | 
			
		||||
                'field might be named incorrectly and not match '
 | 
			
		||||
                'any attribute or key on the `{instance}` instance.\n'
 | 
			
		||||
                'Original exception text was: {exc}.'.format(
 | 
			
		||||
                    exc_type=type(exc).__name__,
 | 
			
		||||
                    field=self.field_name,
 | 
			
		||||
                    serializer=self.parent.__class__.__name__,
 | 
			
		||||
                    instance=instance.__class__.__name__,
 | 
			
		||||
                    exc=exc
 | 
			
		||||
                )
 | 
			
		||||
            )
 | 
			
		||||
            raise type(exc)(msg)
 | 
			
		||||
 | 
			
		||||
        return relationship.all() if hasattr(relationship, 'all') else relationship
 | 
			
		||||
 | 
			
		||||
    def to_representation(self, iterable):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1025,6 +1025,73 @@ class Issue2704TestCase(TestCase):
 | 
			
		|||
        assert serializer.data == expected
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Issue7550FooModel(models.Model):
 | 
			
		||||
    text = models.CharField(max_length=100)
 | 
			
		||||
    bar = models.ForeignKey(
 | 
			
		||||
        'Issue7550BarModel', null=True, blank=True, on_delete=models.SET_NULL,
 | 
			
		||||
        related_name='foos', related_query_name='foo')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Issue7550BarModel(models.Model):
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Issue7550TestCase(TestCase):
 | 
			
		||||
 | 
			
		||||
    def test_dotted_source(self):
 | 
			
		||||
 | 
			
		||||
        class _FooSerializer(serializers.ModelSerializer):
 | 
			
		||||
            class Meta:
 | 
			
		||||
                model = Issue7550FooModel
 | 
			
		||||
                fields = ('id', 'text')
 | 
			
		||||
 | 
			
		||||
        class FooSerializer(serializers.ModelSerializer):
 | 
			
		||||
            other_foos = _FooSerializer(source='bar.foos', many=True)
 | 
			
		||||
 | 
			
		||||
            class Meta:
 | 
			
		||||
                model = Issue7550BarModel
 | 
			
		||||
                fields = ('id', 'other_foos')
 | 
			
		||||
 | 
			
		||||
        bar = Issue7550BarModel.objects.create()
 | 
			
		||||
        foo_a = Issue7550FooModel.objects.create(bar=bar, text='abc')
 | 
			
		||||
        foo_b = Issue7550FooModel.objects.create(bar=bar, text='123')
 | 
			
		||||
 | 
			
		||||
        assert FooSerializer(foo_a).data == {
 | 
			
		||||
            'id': foo_a.id,
 | 
			
		||||
            'other_foos': [
 | 
			
		||||
                {
 | 
			
		||||
                    'id': foo_a.id,
 | 
			
		||||
                    'text': foo_a.text,
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    'id': foo_b.id,
 | 
			
		||||
                    'text': foo_b.text,
 | 
			
		||||
                },
 | 
			
		||||
            ],
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def test_dotted_source_with_default(self):
 | 
			
		||||
 | 
			
		||||
        class _FooSerializer(serializers.ModelSerializer):
 | 
			
		||||
            class Meta:
 | 
			
		||||
                model = Issue7550FooModel
 | 
			
		||||
                fields = ('id', 'text')
 | 
			
		||||
 | 
			
		||||
        class FooSerializer(serializers.ModelSerializer):
 | 
			
		||||
            other_foos = _FooSerializer(source='bar.foos', default=[], many=True)
 | 
			
		||||
 | 
			
		||||
            class Meta:
 | 
			
		||||
                model = Issue7550FooModel
 | 
			
		||||
                fields = ('id', 'other_foos')
 | 
			
		||||
 | 
			
		||||
        foo = Issue7550FooModel.objects.create(bar=None, text='abc')
 | 
			
		||||
 | 
			
		||||
        assert FooSerializer(foo).data == {
 | 
			
		||||
            'id': foo.id,
 | 
			
		||||
            'other_foos': [],
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DecimalFieldModel(models.Model):
 | 
			
		||||
    decimal_field = models.DecimalField(
 | 
			
		||||
        max_digits=3,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user