v3.7: serializer's field 'source' attribute different behaviour · Issue #5488 · encode/django-rest-framework (original) (raw)

Checklist

Steps to reproduce

I have ModelSerializer for User objects. This serializer used by list endpoint (not for modifying data).

class ColleagueUserSerializer(BaseModelSerializer):
    ...
    city = serializers.IntegerField(source='profile.contact.address.city')
    ...

But if profile's contact has no address error occurs

Expected behavior

return None

Actual behavior

AttributeError: Got AttributeError when attempting to get a value for field `city` on serializer `ColleagueUserSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `User` instance.
Original exception text was: 'NoneType' object has no attribute 'address'.

Notes

Problem with version 3.7.0

Prior to version 3.7.0, no error occurred. The problem in changing the behavior of the module 'fields.py'

def get_attribute(instance, attrs):
    """
    Similar to Python's built in `getattr(instance, attr)`,
    but takes a list of nested attributes, instead of a single attribute.

    Also accepts either attribute lookup on objects or dictionary lookups.
    """
    for attr in attrs:
        # this code was removed in v3.7.0
    if instance is None:
            # Break out early if we get `None` at any point in a nested lookup.
       	    return None
    # ---
        try:		
            if isinstance(instance, collections.Mapping):
                instance = instance[attr]
            else:
                instance = getattr(instance, attr)
        except ObjectDoesNotExist:
            return None
        if is_simple_callable(instance):
            try:
                instance = instance()
            except (AttributeError, KeyError) as exc:
                # If we raised an Attribute or KeyError here it'd get treated
                # as an omitted field in `Field.get_attribute()`. Instead we
                # raise a ValueError to ensure the exception is not masked.
                raise ValueError('Exception raised in callable attribute "{0}"; original exception was: {1}'.format(attr, exc))

    return instance