views:

547

answers:

2

I have

class Achievement(MyBaseModel):
    pass

class Alias(MyBaseModel):
    achievements = models.ManyToManyField('Achievement')

>>> ach = Achievement.objects.all()[1]

This works :

>>> Alias.objects.all().filter(achievements__pk__contains=ach.pk).count()
77L

But this doesn't :

>>> Alias.objects.all().filter(achievements__contains=ach).count()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/var/home/ptarjan/django/mysite/django/db/models/query.py", line 489, in filter
    return self._filter_or_exclude(False, *args, **kwargs)
  File "/var/home/ptarjan/django/mysite/django/db/models/query.py", line 507, in _filter_or_exclude
    clone.query.add_q(Q(*args, **kwargs))
  File "/var/home/ptarjan/django/mysite/django/db/models/sql/query.py", line 1258, in add_q
    can_reuse=used_aliases)
  File "/var/home/ptarjan/django/mysite/django/db/models/sql/query.py", line 1201, in add_filter
    self.where.add((alias, col, field, lookup_type, value), connector)
  File "/var/home/ptarjan/django/mysite/django/db/models/sql/where.py", line 48, in add
    params = field.get_db_prep_lookup(lookup_type, value)
  File "/var/home/ptarjan/django/mysite/django/db/models/fields/related.py", line 156, in get_db_prep_lookup
    raise TypeError, "Related Field has invalid lookup: %s" % lookup_type
TypeError: Related Field has invalid lookup: contains

Why? (Django 1.0.2)

Looking at the query log, it is doing something that I didn't expect! That query yielded :

>>> connection.queries[-1]
{'time': '0.027', 'sql': u'SELECT COUNT(*) FROM `yourock_alias` INNER JOIN `yourock_achiever` ON (`yourock_alias`.`id` = `yourock_achiever`.`alias_id`) WHERE `yourock_achiever`.`achievement_id` LIKE BINARY %j0xvw9% '}

But doing this

>>> Alias.objects.all().filter(achievements=ach).count()
77L

Gives this query

>>> connection.queries[-1]
{'time': '0.023', 'sql': u'SELECT COUNT(*) FROM `yourock_alias` INNER JOIN `yourock_achiever` ON (`yourock_alias`.`id` = `yourock_achiever`.`alias_id`) WHERE `yourock_achiever`.`achievement_id` = j0xvw9 '}

which is what I wanted, but the = seems to me to mean that it IS the one object. The query that django is doing actually is returning if the object is anywhere in the achievement list.

Is this correctly setup and is just very counter-intuitive or am I doing something wrong?

+1  A: 

I can't answer why the design decision was made to implement it this way, but most likely its to follow the Python philosophy that things should be specified explicitly and not implied.

The reason it doesn't work is because __contains expects a field to reference. The bit you passed it was a reference to a whole object.

Andre Miller
A: 

In the second case, you are comparing objects. And, from Django documentation, in order to compare an object you have to use the == operator.

On top of that: why don't you use ach.alias_set.objects.count(), as explained in the query section of the manual?

Roberto Liffredo
Is your query equivalent? Is it the recommended way? (I'm a django newbie)
Paul Tarjan
Yes, it is. I have added a link to the Django documentation, about how to follow a relationship "backward".
Roberto Liffredo