Opened 13 years ago

Closed 2 years ago

#11707 closed Bug (fixed)

limit_choices_to on a ForeignKey can render duplicate options in formfield

Reported by: Chris.Wesseling@… Owned by: Crowley Shaita
Component: Forms Version: dev
Severity: Normal Keywords: ForeingKey limit_choices_to, dceu2011
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

If you pass a Q object as limit_choices_to on a ForeignKey field involving a join, you may end up with duplicate options in your form.

See regressiontest in patch for a clear view on the problem.

Attachments (7)

limit_ForeignKey.patch (3.0 KB) - added by Chris.Wesseling@… 13 years ago.
limit_ForeignKey.2.patch (3.1 KB) - added by Chris.Wesseling@… 13 years ago.
limit_ForeignKey.3.patch (3.1 KB) - added by Chris.Wesseling@… 13 years ago.
update resolving conflict
limit_ForeignKey.4.patch (5.6 KB) - added by Chris Wesseling 12 years ago.
just removed a the previous fix from the comments
limit_ForeignKey.1.2.X.patch (5.2 KB) - added by Chris Wesseling 11 years ago.
backported to 1.2.X and refactored to reduce complexity
limit_ForeignKey.trunk.patch (5.2 KB) - added by Chris Wesseling 11 years ago.
Refactored less complex against trunk
limit_ForeignKey.1.3.X.patch (5.2 KB) - added by Chris Wesseling 11 years ago.
against 1.3.X branch

Download all attachments as: .zip

Change History (44)

Changed 13 years ago by Chris.Wesseling@…

Attachment: limit_ForeignKey.patch added

comment:1 Changed 13 years ago by Chris Beaven

Triage Stage: UnreviewedReady for checkin

Changed 13 years ago by Chris.Wesseling@…

Attachment: limit_ForeignKey.2.patch added

comment:2 in reply to:  1 Changed 13 years ago by anonymous

Replying to SmileyChris:

I've updated the patch to resolve the conflicts I've had since you flagged this one as "Ready for checkin".
No real change.

Changed 13 years ago by Chris.Wesseling@…

Attachment: limit_ForeignKey.3.patch added

update resolving conflict

comment:3 Changed 13 years ago by Chris.Wesseling@…

Is there something I can do to get this checked in? I re-read the Triage docs. As far as I can see "A developer checks in the fix" is the only step left.

comment:4 Changed 13 years ago by Chris Beaven

The 1.2 roadmap shows that we're in a feature freeze. I'd suggest bringing this up on the django-dev google group a week or so after 1.2 final is released.

comment:5 Changed 12 years ago by Luke Plant

Resolution: fixed
Status: newclosed

In [15607]:

Fixed #11707 - limit_choices_to on a ForeignKey can render duplicate options in formfield

Thanks to Chris Wesseling for the report and patch.

comment:6 Changed 12 years ago by Luke Plant

In [15610]:

[1.2.X] Fixed #11707 - limit_choices_to on a ForeignKey can render duplicate options in formfield

Thanks to Chris Wesseling for the report and patch.

Backport of [15607] from trunk.

comment:7 Changed 12 years ago by Luke Plant

In [15791]:

Fixed #15559 - distinct queries introduced by [15607] cause errors with some custom model fields

This patch just reverts [15607] until a more satisfying solution can be
found.

Refs #11707

comment:8 Changed 12 years ago by Luke Plant

In [15792]:

[1.2.X] Fixed #15559 - distinct queries introduced by [15607] cause errors with some custom model fields

This patch just reverts [15607] until a more satisfying solution can be
found.

Refs #11707

Backport of [15791] from trunk.

comment:9 Changed 12 years ago by Luke Plant

Resolution: fixed
Status: closedreopened

Re-opened due to the fix being reverted, as above. For future reference, a possible alternative solution might be to do filtering of duplicates in Python, at the point of rendering the form field, rather than in the database.

comment:10 Changed 12 years ago by Russell Keith-Magee

Patch needs improvement: set
Triage Stage: Ready for checkinAccepted

comment:11 in reply to:  7 ; Changed 12 years ago by Chris Wesseling

Replying to lukeplant:

(The changeset message doesn't reference this ticket)

Can someone point me to an example of such a custom model field or, even better, a test showing the breakage?

Replying to lukeplant:

For future reference, a possible alternative solution might be to do filtering of duplicates in Python, at the point of rendering the form field, rather than in the database.

Assuming 'limit_choices_to' is only used by Forms...

comment:12 in reply to:  11 Changed 12 years ago by anonymous

Replying to charstring:

Replying to lukeplant:

(The changeset message doesn't reference this ticket)

Can someone point me to an example of such a custom model field or, even better, a test showing the breakage?

The discussion linked from the description of the other ticket has an example. It's in dpaste so may not be long-lived. Copying here for reference:

class PointField(models.Field):
    description = _("A geometric point")

    __metaclass__ = models.SubfieldBase

    pattern = re.compile('^\(([\d\.]+),([\d\.]+)\)$')

    def db_type(self, connection):
        if connection.settings_dict['ENGINE'] is not 'django.db.backends.postgresql_psycopg2':
            return None

        return 'point'

    def to_python(self, value):
        if isinstance(value, tuple):
            return (float(value[0]), float(value[1]))

        if not value:
            return (0, 0)

        match = self.pattern.findall(value)[0]
        return (float(match[0]), float(match[1]))

    def get_prep_value(self, value):
        return self.to_python(value)

    def get_db_prep_value(self, value, connection, prepared=False):
        # Casts dates into the format expected by the backend
        if not prepared:
            value = self.get_prep_value(value)
        return '({0}, {1})'.format(value[0], value[1])

    def get_prep_lookup(self, lookup_type, value):
        raise TypeError('Lookup type %r not supported.' % lookup_type)

    def value_to_string(self, obj):
        value = self._get_val_from_obj(obj)
        return self.get_db_prep_value(value)

comment:13 Changed 12 years ago by Julien Phalip

Severity: Normal
Type: Bug

comment:14 Changed 12 years ago by Simon Meers

Easy pickings: unset

This is nasty because it not only renders duplicates but also blows up when .get() is called on the queryset if you select one of the duplicates (MultipleObjectsReturned).

comment:15 Changed 12 years ago by Chris Wesseling

Owner: changed from nobody to Chris Wesseling
Status: reopenednew
UI/UX: unset

comment:16 Changed 12 years ago by Chris Wesseling

Keywords: dceu2011 added
Patch needs improvement: unset

Talked to Russ. Picked one of the unclean solutions:

filter in python before displaying and checking again before getting the choice.

Thanks to Jonas and Roald!

Changed 12 years ago by Chris Wesseling

Attachment: limit_ForeignKey.4.patch added

just removed a the previous fix from the comments

comment:17 Changed 12 years ago by Simon Litchfield

This issue also breaks ModelChoiceField - MultipleObjectsReturned error

comment:18 in reply to:  17 Changed 12 years ago by Chris Wesseling

Replying to simon29:

This issue also breaks ModelChoiceField - MultipleObjectsReturned error

By "this issue also breaks", do you mean, you've tried the patch and it needs improvement?

If it does work, please set it to "ready for checkin".

Changed 11 years ago by Chris Wesseling

backported to 1.2.X and refactored to reduce complexity

Changed 11 years ago by Chris Wesseling

Refactored less complex against trunk

Changed 11 years ago by Chris Wesseling

against 1.3.X branch

comment:19 Changed 11 years ago by Jacob

Discussion from IRC:

[02:24am] I don't see a test case here that emulates the failures seen when the previous (committed then reverted) approach. Am I just missing it?
[09:26am] jacobkm: I also can't say I'm particularly happy with the patch, particularly iterating over the qs in distinct_choices().
[09:26am] chars:It's pretty hard to test for me. It's a case where Postgres can't compare the values.
[09:26am] chars: So it can't test for uniqueness
[09:26am] jacobkm: It also needs additions to documentation to mention that Q() objects are acceptable in limit_choices_to.

comment:20 in reply to:  19 Changed 11 years ago by anonymous

Replying to jacob:

Discussion from IRC:

[09:26am] jacobkm: It also needs additions to documentation to mention that Q() objects are acceptable in limit_choices_to.

Documentation on ForeignKey.limit_choices_to already mentions: "Instead of a dictionary this can also be a Q object for more complex queries."

Further discussion:

17:00 < chars> jacobkm: The only known case that broke the original .distinct() 
               solution was in Postgres. So maybe if #6422 gets accepted, we 
               could test for the distinct_on_fields feature and then distinct 
               on the pk, which is unique by definition.
17:00 < jacobkm> chars: see now *that* makes me a lot happier.
17:00 < chars> And fallback to the vanilla .distinct() if the backend doesn't 
               support it.

That's #6422.

comment:21 Changed 11 years ago by Chris Wesseling

DISTINCT is just a special GROUP BY...

So an empty .annotate() does the trick too, since it groups by the pk. And the DBMS should by definition be able to compare pk's.
I'll try to put up a patch tonight.

comment:22 in reply to:  21 ; Changed 9 years ago by Chris Wesseling

Replying to charstring:

DISTINCT is just a special GROUP BY...

So an empty .annotate() does the trick too, since it groups by the pk. And the DBMS should by definition be able to compare pk's.
I'll try to put up a patch tonight.

Well, that was a long night. ;)
I got implemented the .annotate() solution in here https://github.com/CharString/django/tree/ticket-11707

Is the PointField mentioned in 12 the same as the one that now lives in django.contrib.gis?

comment:23 Changed 9 years ago by Tim Graham

Patch needs improvement: set

I think the PointField in comment 12 is a custom field that's different from the one in contrib.gis. It's difficult for me to tell from the comments what the issue was. In any case, I'm going to mark this as "Patch needs improvement" since it appears it needs additional tests.

comment:24 in reply to:  22 Changed 9 years ago by Chris Wesseling

Replying to charstring:

Is the PointField mentioned in 12 the same as the one that now lives in django.contrib.gis?

No, it isn't. I've installed postgis for this bug. postgis points *can* be tested on equality.. the PointField in 12 uses the builtin postgres point type, *not* the postgis point type that django.crontib.gis does.

comment:25 Changed 2 years ago by Crowley Shaita

Owner: changed from Chris Wesseling to Crowley Shaita
Status: newassigned

comment:26 Changed 2 years ago by Crowley Shaita

Triage Stage: AcceptedReady for checkin

comment:27 Changed 2 years ago by Crowley Shaita

comment:28 Changed 2 years ago by Carlton Gibson

Patch needs improvement: unset
Triage Stage: Ready for checkinAccepted

Unchecking Patch needs improvement so that this goes back into the review queue.

comment:29 Changed 2 years ago by Crowley Shaita

Resolution: fixed
Status: assignedclosed

comment:30 Changed 2 years ago by Mariusz Felisiak

Resolution: fixed
Status: closednew

Patch is not merged.

comment:31 Changed 2 years ago by Crowley Shaita

Triage Stage: AcceptedUnreviewed

comment:32 Changed 2 years ago by Mariusz Felisiak

Triage Stage: UnreviewedAccepted

Crowley, please stop changing a triage stage without any reason.

comment:33 in reply to:  32 Changed 2 years ago by Crowley Shaita

Replying to felixxm:

Crowley, please stop changing a triage stage without any reason.

Sorry, I thought that's how I would get it to be reviewed.

comment:34 Changed 2 years ago by Mariusz Felisiak

Patch needs improvement: set

comment:35 Changed 2 years ago by Crowley Shaita

Patch needs improvement: unset

comment:36 Changed 2 years ago by Mariusz Felisiak

Triage Stage: AcceptedReady for checkin

comment:37 Changed 2 years ago by Mariusz Felisiak <felisiak.mariusz@…>

Resolution: fixed
Status: newclosed

In 556fa4bb:

Fixed #1891, Fixed #11707 -- Prevented duplicates with limit_choices_to on multi-value relations.

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