Opened 44 hours ago

Last modified 40 hours ago

#36431 new Bug

values() query on ForeignObject discards additional columns

Reported by: Jacob Walls Owned by:
Component: Database layer (models, ORM) Version: 4.2
Severity: Normal Keywords: composite primary key
Cc: Simon Charette Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

For a multi-column field using ForeignObject as modeled here, .values("user") will discard columns after the first, and values("user", "integer") will select the second column of user into the integer namespace.

Here's a failing test for tests/composite_pk/test_values.py:

    def test_foreign_object_values(self):
        Comment.objects.create(id=1, user=self.user_1, integer=42)
        values = list(Comment.objects.values("user", "integer"))
        self.assertEqual(values[0]["integer"], 42)
======================================================================
FAIL: test_foreign_object_values (composite_pk.test_values.CompositePKValuesTests.test_foreign_object_values)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/.../django/tests/composite_pk/test_values.py", line 217, in test_foreign_object_values
    self.assertEqual(values[0]["integer"], 42)
    ~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1 != 42

And a similar rough test for foreign_objects/tests.py that fails on Django 4.2, to remove the composite primary key from discussion. (If this model had more fields, this values query would be more realistic):

class ForeignObjectValues(TestCase):
    def test_foreign_object_values(self):
        from .models import Customer
        customer_1 = Customer.objects.create(customer_id=1, company="a")
        customer_2 = Customer.objects.create(customer_id=1, company="b")
        company_a_customers = Customer.objects.filter(company="a").values("address", "company")
        self.assertSequenceEqual(
            Customer.objects.exclude(company__in=[cust["company"] for cust in company_a_customers]),
            [customer_2],  # Fails, all companies included
        )

Change History (2)

comment:1 by Simon Charette, 42 hours ago

Triage Stage: UnreviewedAccepted

comment:2 by Simon Charette, 40 hours ago

Cc: Simon Charette added

On interesting dilemma we'll have to figure out here is what exactly to return when a foreign object composed of more than one from_fields is selected through values and values_list.

With composite primary keys we've made usages of values("pk") turned into values(Tuple(*pk_field_attnames)) and not values(*pk_field_attnames) so we should be coherent here IMO which means that we likely want something like

  • tests/composite_pk/test_values.py

    diff --git a/tests/composite_pk/test_values.py b/tests/composite_pk/test_values.py
    index 03a9a85496..cc45db40bc 100644
    a b  
    33
    44from django.test import TestCase
    55
    6 from .models import Post, Tenant, User
     6from .models import Comment, Post, Tenant, User
    77
    88
    99class CompositePKValuesTests(TestCase):
    def test_values(self):  
    210210                    {"pk": self.user_3.pk, "id": self.user_3.id},
    211211                ),
    212212            )
     213
     214    def test_foreign_object_values(self):
     215        Comment.objects.create(id=1, user=self.user_1, integer=42)
     216        values = list(Comment.objects.values("user", "integer"))
     217        self.assertEqual(values[0]["user"], (self.user_1.tenant_id, self.user_1.id))
     218        self.assertEqual(values[0]["integer"], 42)

In other words I suggest we double down on making sure references to composite fields (the only two we support today is composite pk and foreign objects with multiple fields) in values and values_list return tuples instead of flattening them as we've taken this approach for CompositePrimaryKey (which is now part of the public API) and that ForeignObject is still private and has been broken since its introduction in this regard.

Note: See TracTickets for help on using tickets.
Back to Top