Repository: django/django
File: django/core/serializers/python.py (Python)

Code before:

    def get_dump_object(self, obj):
        data = {"model": str(obj._meta)}

        pk_included = True

        if self.use_natural_primary_keys:
            natural_key_func = getattr(obj, "natural_key", None)

            if callable(natural_key_func):
                natural_key_value = None
                try:
                    natural_key_value = natural_key_func()
                except Exception:
                    pass

                is_opt_out = (
                    natural_key_value is None
                    or not natural_key_value
                    or natural_key_value == (obj.pk,)
                    or not isinstance(natural_key_value, tuple)
                )

                if not is_opt_out:
                    pk_included = False

        if pk_included:
            data["pk"] = self._value_from_field(obj, obj._meta.pk)

        data["fields"] = self._current
        return data

    def _value_from_field(self, obj, field):
        if isinstance(field, CompositePrimaryKey):
            return [self._value_from_field(obj, f) for f in field]
        value = field.value_from_object(obj)
        # Protected types (i.e., primitives like None, numbers, dates,
        # and Decimals) are passed through as is. All other values are
        # converted to string first.
        return value if is_protected_type(value) else field.value_to_string(obj)

    def handle_field(self, obj, field):
        self._current[field.name] = self._value_from_field(obj, field)

    def handle_fk_field(self, obj, field):
        if self.use_natural_foreign_keys and hasattr(
            field.remote_field.model, "natural_key"
        ):
            related = getattr(obj, field.name)
            if related:
                natural_key_value = related.natural_key()

Review comment (suggestion):
There's a 3rd location in this file with a similar need (`handle_m2m_field`).

Code after:

    def get_dump_object(self, obj):
        data = {"model": str(obj._meta)}
        if not self.use_natural_primary_keys or not self._resolve_natural_key(obj):
            data["pk"] = self._value_from_field(obj, obj._meta.pk)
        data["fields"] = self._current
        return data

    def _value_from_field(self, obj, field):
        if isinstance(field, CompositePrimaryKey):
            return [self._value_from_field(obj, f) for f in field]
        value = field.value_from_object(obj)
        # Protected types (i.e., primitives like None, numbers, dates,
        # and Decimals) are passed through as is. All other values are
        # converted to string first.
        return value if is_protected_type(value) else field.value_to_string(obj)

    def handle_field(self, obj, field):
        self._current[field.name] = self._value_from_field(obj, field)

    def handle_fk_field(self, obj, field):
        if self.use_natural_foreign_keys and (
            natural_key_value := self._resolve_fk_natural_key(obj, field)
        ):
            value = natural_key_value
        else:
            value = self._value_from_field(obj, field)
        self._current[field.name] = value

    def handle_m2m_field(self, obj, field):
        if field.remote_field.through._meta.auto_created:
            if self.use_natural_foreign_keys and self._model_supports_natural_key(
                field.remote_field.model
            ):

                def m2m_value(value):
                    if natural := value.natural_key():
                        return natural
                    else:
                        return self._value_from_field(value, value._meta.pk)

                def queryset_iterator(obj, field):
                    attr = getattr(obj, field.name)
                    chunk_size = (
                        2000 if getattr(attr, "prefetch_cache_name", None) else None
                    )
                    return attr.iterator(chunk_size)

            else:

                def m2m_value(value):