Repository: django/django
File: tests/foreign_object/tests.py (Python)

Code before:
        self.assertIn("path_infos", foreign_object_rel.__dict__)
        foreign_object_rel_restored = pickle.loads(pickle.dumps(foreign_object_rel))
        self.assertNotIn("path_infos", foreign_object_rel_restored.__dict__)

    def test_pickling_foreignobject(self):
        """
        Pickling a ForeignObject does not remove the cached PathInfo values.

        ForeignObject will always keep the path_infos and reverse_path_infos
        attributes within the same process, because of the way
        Field.__reduce__() is used for restoring values.
        """
        foreign_object = Membership._meta.get_field("person")
        # Trigger storage of cached_property into ForeignObjectRel's __dict__
        foreign_object.path_infos
        foreign_object.reverse_path_infos
        self.assertIn("path_infos", foreign_object.__dict__)
        self.assertIn("reverse_path_infos", foreign_object.__dict__)
        foreign_object_restored = pickle.loads(pickle.dumps(foreign_object))
        self.assertIn("path_infos", foreign_object_restored.__dict__)
        self.assertIn("reverse_path_infos", foreign_object_restored.__dict__)


class ForeignObjectConstraintTests(TestCase):
    @skipUnlessDBFeature("supports_table_check_constraints")
    def test_validate_constraints_with_foreign_key_id(self):
        patron_tab = PatronTab(patron_id=None)
        with self.assertRaises(ValidationError) as cm:
            patron_tab.validate_constraints()
        self.assertIn("patron_not_null", str(cm.exception))


Review comment (bug):
Could you add a test case showing that `exclude={"patron_id"}` still avoids validating a failing constraint after this change? (I wouldn't worry about supporting `exclude={"patron"}`, which we've doc'd as [not working yet](https://docs.djangoproject.com/en/5.2/topics/composite-primary-key/#composite-primary-keys-in-model-validation), but I just want to ensure no regression here for the other case.

If you find your changes end up _fixing_ `exclude={"patron"}` e.g. through interrogating `local_related_fields`, then let's remove the doc'd limitation about specifying each local related field separately.

Code after:
        self.assertIn("path_infos", foreign_object_rel.__dict__)
        foreign_object_rel_restored = pickle.loads(pickle.dumps(foreign_object_rel))
        self.assertNotIn("path_infos", foreign_object_rel_restored.__dict__)

    def test_pickling_foreignobject(self):
        """
        Pickling a ForeignObject does not remove the cached PathInfo values.

        ForeignObject will always keep the path_infos and reverse_path_infos
        attributes within the same process, because of the way
        Field.__reduce__() is used for restoring values.
        """
        foreign_object = Membership._meta.get_field("person")
        # Trigger storage of cached_property into ForeignObjectRel's __dict__
        foreign_object.path_infos
        foreign_object.reverse_path_infos
        self.assertIn("path_infos", foreign_object.__dict__)
        self.assertIn("reverse_path_infos", foreign_object.__dict__)
        foreign_object_restored = pickle.loads(pickle.dumps(foreign_object))
        self.assertIn("path_infos", foreign_object_restored.__dict__)
        self.assertIn("reverse_path_infos", foreign_object_restored.__dict__)


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"})