views:

411

answers:

2

Hi! I have this models:

class Project(models.Model):
    users = models.ManyToManyField(User, through='Project_User')

class Project_User(models.Model):
    project = models.ForeignKey('Project')
    user = models.ForeignKey(User)
    property = models.BooleanField()

Not all Projects have own Project_User rows.

Thing, what I need is get queryset of all Projects where field "property" of current user != true or Project_User row of current user doesn't exists. Is here any way to do this using django's ORM? As result, I need Queryset object for applying some other filters to it.

Using custom SQL I can do it. Current user have id==XXXX:

SELECT * FROM "app_project" LEFT OUTER JOIN "app_project_user" 
ON ("app_project"."id" = "app_project_user"."project_id" 
    AND ("app_project_user"."user_id" = XXXX OR "app_project_user"."user_id" IS NULL)) 
WHERE ("app_project_user"."property" = false OR "app_project_user"."property" IS NULL);

I hope, it is possible, but I don't know how, yet..

Thanks for any help!

A: 

Something along the lines of

p = Projects.objects.filter( users=current_user )
p = p.exclude( project_user__user=current_user,
               project_user__property=True )

This should give you a queryset of all projects for the current user, excluding those for which there is a Project_User for the current user who's property value is True

more info: http://docs.djangoproject.com/en/dev/topics/db/queries/#lookups-that-span-relationships

spbogie
It's not good for me, because ORM generates subqueries. And it doesn't work, when I have several bool fields - one subquery for one field.
ramusus
+2  A: 

For complex lookups (and any lookup, essentially, involving OR), I recommend Django's Q operator.

In this case, the query might look like:

from django.db.models import Q

q = (Q(project_user=my_current_user) | Q(project_user=None)) & \
    (Q(project_user__property=False) | Q(project_user__property=None))
projects = Project.objects.filter(q)

In this case, don't forget to indicate that NULL is allowed for your property field:

class Project_User(models.Model):
    # ... as above, then:
    property= models.BooleanField(null=True)

Otherwise, django will issue CREATE TABLE sql for the property field that specifically indicates null is not allowed ("property" bool NOT NULL), which would contradict the use of Q(project_user__property=None).

Jarret Hardie
thanks it became more clear for me. One problem, that I see: Project.objects.filter(Q(project_user=my_current_user) | Q(project_user=None)).transforms to SELECT COUNT(*) FROM "app_project" LEFT OUTER JOIN "app_project_user" ON ("app_project"."id" = "app_project_user"."project_id") LEFT OUTER JOIN "auth_user" ON ("app_project_user"."user_id" = "auth_user"."id") WHERE ("app_project_user"."user_id" = 10 OR "auth_user"."id" IS NULL).I don't understand for what the second LEFT OUTER JOIN with auth_user table. It returns another result, then with 1 join. Is there any way to avoid it?
ramusus
Best solution for me is generating this query: SELECT COUNT(*) FROM "app_project" LEFT OUTER JOIN "app_project_user" ON ("app_project"."id" = "app_project_user"."project_id" AND "app_project_user"."user_id" = 10 OR "app_project_user"."user_id" IS NULL)But I don't know Is it possible with Q(project_user=None) clause.
ramusus
The second join with auth_user is likely because you are using (correctly so) request.user to determine the current user. request.user is an instance of django.auth.models.User, hence the join to the auth_user table. I wouldn't worry about babysitting each individual generated SQL. Build your app, and if the performance isn't what you hope, optimize.
Jarret Hardie
Good, detailed observation though!
Jarret Hardie