diff --git a/django/db/models/base.py b/django/db/models/base.py index 518cfc44a2..358a744675 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -1357,7 +1357,7 @@ class Model(AltersData, metaclass=ModelBase): meta = meta or self._meta field_map = {} generated_fields = [] - for field in meta.local_concrete_fields: + for field in meta.local_fields: if field.name in exclude: continue if field.generated: @@ -1368,7 +1368,19 @@ class Model(AltersData, metaclass=ModelBase): continue generated_fields.append(field) continue - value = getattr(self, field.attname) + if ( + isinstance(field.remote_field, ForeignObjectRel) + and field not in meta.local_concrete_fields + ): + value = tuple( + getattr(self, from_field) for from_field in field.from_fields + ) + if len(value) == 1: + value = value[0] + elif field.concrete: + value = getattr(self, field.attname) + else: + continue if not value or not hasattr(value, "resolve_expression"): value = Value(value, field) field_map[field.name] = value diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py index 436d9583c4..09fb47e771 100644 --- a/tests/foreign_object/tests.py +++ b/tests/foreign_object/tests.py @@ -3,10 +3,10 @@ import datetime import pickle from operator import attrgetter -from django.core.exceptions import FieldError +from django.core.exceptions import FieldError, ValidationError from django.db import connection, models from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature -from django.test.utils import isolate_apps +from django.test.utils import CaptureQueriesContext, isolate_apps from django.utils import translation from .models import ( @@ -771,7 +771,30 @@ class TestCachedPathInfo(TestCase): class ForeignObjectModelValidationTests(TestCase): + @skipUnlessDBFeature("supports_table_check_constraints") + def test_validate_constraints_with_foreign_object(self): + customer_tab = CustomerTab(customer_id=1500) + with self.assertRaisesMessage(ValidationError, "customer_id_limit"): + customer_tab.validate_constraints() + + @skipUnlessDBFeature("supports_table_check_constraints") + def test_validate_constraints_success_case_single_query(self): + customer_tab = CustomerTab(customer_id=500) + with CaptureQueriesContext(connection) as ctx: + customer_tab.validate_constraints() + select_queries = [ + query["sql"] + for query in ctx.captured_queries + if "select" in query["sql"].lower() + ] + self.assertEqual(len(select_queries), 1) + @skipUnlessDBFeature("supports_table_check_constraints") def test_validate_constraints_excluding_foreign_object(self): customer_tab = CustomerTab(customer_id=150) customer_tab.validate_constraints(exclude={"customer"}) + + @skipUnlessDBFeature("supports_table_check_constraints") + def test_validate_constraints_excluding_foreign_object_member(self): + customer_tab = CustomerTab(customer_id=150) + customer_tab.validate_constraints(exclude={"customer_id"})