﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
11267	ForeignKey/OneToOneField attribute names should be valid kwargs in queries	dstora	nobody	"Currently, only the field names are available as kwargs for queries.[[BR]]
However, it makes (even more) sense that attribute names are supported too for !ForeignKey/OneToOneField fields.

----

Let's take the following example:

- I initially have a table ""Payment"" with a ""ccy_id"" field designating a 3-letter currency code.
My model definition looks like:
{{{
    from django.db import model
    class Payment(models.Model):
        # ...
        ccy_id = models.CharField(max_length=3)

}}}
In my python code I can retrieve payments by currency id as follows:
{{{
    payment_list = list(Payment.objects.filter(ccy_id=""USD""))
}}}

- Later, I decide to actually create a ""Currency"" table to hold some information about currencies.
And I also decide to define a foreign key constraint from ""Payment"" to ""Currency"".
My model now looks like:
{{{
    from django.db import model
    class Currency(models.Model):
        ccy_id = models.CharField(max_length=3, primary_key=True)
        #...
    class Payment(models.Model):
        # ...
        ccy = models.ForeignKey(Currency, to_field=""ccy_id"", db_column=""ccy_id"")

}}}
The problem here is that my existing Python code is broken, because ""ccy_id"" is not accepted anymore as kwarg for ""Payment"" queries.[[BR]]
Django now only accepts ""ccy"" instead.

----

Based on the principle that defining things like foreign keys should only add functionality (and not remove any) it seems quite important that on top of the field names queries also support attribute name kwargs.[[BR]]
[[BR]]
Looking at the code, I can see two ways of achieving this.[[BR]]
In both cases, the implementation of this feature is pretty small and fully backward-compatible:[[BR]]
[[BR]]
- The first way is to register fields in options.Option against both their name and attname if these differ (SVN diff against trunk below)
{{{
Index: django/db/models/options.py
===================================================================
--- django/db/models/options.py (revision 10924)
+++ django/db/models/options.py (working copy)
@@ -329,6 +329,8 @@
             cache[f.name] = (f, model, True, True)
         for f, model in self.get_fields_with_model():
             cache[f.name] = (f, model, True, False)
+            if f.attname != f.name:
+                cache[f.attname] = (f, model, True, False)
         if self.order_with_respect_to:
             cache['_order'] = OrderWrt(), None, True, False
         if app_cache_ready():
}}}
[[BR]]
- The second way is at query level to always enable a logic currently marked as a hack that looks at attname if no field is found with the requested name (SVN diff against trunk below)
{{{
Index: django/db/models/sql/query.py
===================================================================
--- django/db/models/sql/query.py   (revision 10924)
+++ django/db/models/sql/query.py   (working copy)
@@ -1678,8 +1678,7 @@
             self.used_aliases = used_aliases

     def setup_joins(self, names, opts, alias, dupe_multis, allow_many=True,
-            allow_explicit_fk=False, can_reuse=None, negate=False,
-            process_extras=True):
+            can_reuse=None, negate=False, process_extras=True):
         """"""
         Compute the necessary table joins for the passage through the fields
         given in 'names'. 'opts' is the Options class for the current model
@@ -1715,10 +1714,7 @@
                 field, model, direct, m2m = opts.get_field_by_name(name)
             except FieldDoesNotExist:
                 for f in opts.fields:
-                    if allow_explicit_fk and name == f.attname:
-                        # XXX: A hack to allow foo_id to work in values() for
-                        # backwards compatibility purposes. If we dropped that
-                        # feature, this could be removed.
+                    if name == f.attname:
                         field, model, direct, m2m = opts.get_field_by_name(f.name)
                         break
                 else:
@@ -2026,8 +2022,7 @@
         try:
             for name in field_names:
                 field, target, u2, joins, u3, u4 = self.setup_joins(
-                        name.split(LOOKUP_SEP), opts, alias, False, allow_m2m,
-                        True)
+                        name.split(LOOKUP_SEP), opts, alias, False, allow_m2m)
                 final_alias = joins[-1]
                 col = target.column
                 if len(joins) > 1:
}}}
"		closed	Database layer (models, ORM)	1.0		wontfix	foreign key ForeignKey OneToOneField query filter		Design decision needed	1	0	0	0	0	0
