When using Q objects to make some complex queries, the resulting queryset does not contain all expected objects. Here is an example case:
Models:
class ExtendedUser(User):
"""
This class inherits (MTI-style) from auth.models.User to add extra fields, and is the parent class
for the SupportStaff and Client classes to keep things DRY.
"""
parent = models.OneToOneField(User, parent_link=True)
middle_initial = models.CharField(max_length=10, blank=True)
title = models.CharField(max_length=50, blank=True)
office_building = models.ForeignKey("Building", null=True, blank=True)
office_number = models.CharField(max_length=15, blank=True)
phone = models.PhoneNumberField(blank=True)
twitter = models.CharField(max_length=50, blank=True)
pownce = models.CharField(max_length=50, blank=True)
aim = models.CharField(max_length=50, blank=True)
msn = models.CharField(max_length=50, blank=True)
yahoo = models.CharField(max_length=50, blank=True)
def get_full_name(self):
if self.middle_initial:
return u"%s %s %s" % (self.first_name, self.middle_initial, self.last_name)
else:
return super(ExtendedUser, self).get_full_name()
def __unicode__(self):
return u"%s" % self.get_full_name()
class Meta:
ordering = ('last_name', 'first_name')
verbose_name = u'Extended User'
verbose_name_plural = u'Extended Users'
class SupportStaff(ExtendedUser):
"""
This class inherits from the model class ExtendedUser (itself a subclass of auth User model).
This should only be used for people who are going to provide end-user support services.
"""
parent = models.OneToOneField(ExtendedUser, parent_link=True)
coverage = models.ManyToManyField("OrgUnit", related_name="support_staff_coverage", null=True, blank=True)
expertise = models.ManyToManyField("SpecialSkill", related_name="support_staff_expertise", null=True, blank=True)
def __unicode__(self):
return u"%s" % self.get_full_name()
class SupportTicket(models.Model):
call_source = models.ForeignKey("CallSource", null=True, blank=True)
client = models.ManyToManyField(Client)
call_description = models.TextField("Call Description", max_length=500)
call_type = models.ManyToManyField("CallType")
equipment_tag = models.CharField(max_length=50, null=True, blank=True)
priority = models.CharField(choices=PRIORITY_LEVELS, default=3, max_length=2)
target_date = models.DateTimeField("Target Date and Time", null=True, blank=True)
call_status = models.ForeignKey("CallStatus")
cause = models.ManyToManyField("Cause", null=True, blank=True)
solution = models.TextField(max_length=500, blank=True)
date_created = models.DateTimeField(default=datetime.datetime.now, editable=False)
created_by = models.ForeignKey(SupportStaff, related_name='ticket_created')
date_modified = models.DateTimeField(null=True, blank=True, editable=False)
last_modified_by = models.ForeignKey(SupportStaff, related_name='ticket_modified_by', null=True, blank=True)
date_closed = models.DateTimeField(null=True, blank=True)
closed_by = models.ForeignKey(User, related_name='ticket_closed_by', null=True, blank=True)
class Assignment(models.Model):
support_ticket = models.ForeignKey(SupportTicket, edit_inline=models.STACKED, num_in_admin=1, related_name='assignment_made')
group_assigned = models.ForeignKey(Group, core=True)
group_notified = models.NullBooleanField(editable=False)
support_staff_assigned = models.ForeignKey(SupportStaff, related_name='staff_assignment', null=True, blank=True)
support_staff_notified = models.NullBooleanField(editable=False)
notes = models.TextField(max_length=500, blank=True)
acknowledge = models.BooleanField(default=False)
date_created = models.DateTimeField(default=datetime.datetime.now, editable=False)
created_by = models.ForeignKey(SupportStaff, related_name='assigned_by')
date_modified = models.DateTimeField(default=datetime.datetime.now, editable=False)
last_modified_by = models.ForeignKey(SupportStaff, related_name='assignment_modified_by', null=True, blank=True)
date_acknowledged = models.DateTimeField(null=True, blank=True, editable=False)
Queries:
>>> t4 = SupportTicket.objects.filter(assignment_made__group_assigned__user=u)
>>> t5 = SupportTicket.objects.filter(created_by__parent__parent=u)
>>> t6 = SupportTicket.objects.filter(Q(assignment_made__group_assigned__user=u)|Q(created_by__parent__parent=u)).distinct()
>>> t4
[<SupportTicket: 3>, <SupportTicket: 2>, <SupportTicket: 1>]
>>> t5
[<SupportTicket: 5>, <SupportTicket: 3>, <SupportTicket: 2>, <SupportTicket: 1>]
>>> t6
[<SupportTicket: 3>, <SupportTicket: 2>, <SupportTicket: 1>]
>>> str(SupportTicket.objects.filter(Q(assignment_made__group_assigned__user=u)|Q(created_by__parent__parent=u)).distinct().query)
'SELECT DISTINCT "tickets_supportticket"."id", "tickets_supportticket"."call_source_id", "tickets_supportticket"."call_description", "tickets_supportticket"."equipment_tag", "tickets_supportticket"."priority", "tickets_supportticket"."target_date", "tickets_supportticket"."call_status_id", "tickets_supportticket"."solution", "tickets_supportticket"."date_created", "tickets_supportticket"."created_by_id", "tickets_supportticket"."date_modified", "tickets_supportticket"."last_modified_by_id", "tickets_supportticket"."date_closed", "tickets_supportticket"."closed_by_id" FROM "tickets_supportticket" LEFT OUTER JOIN "tickets_assignment" ON ("tickets_supportticket"."id" = "tickets_assignment"."support_ticket_id") INNER JOIN "auth_group" ON ("tickets_assignment"."group_assigned_id" = "auth_group"."id") LEFT OUTER JOIN "auth_user_groups" ON ("auth_group"."id" = "auth_user_groups"."group_id") WHERE ("auth_user_groups"."user_id" = 6 OR "tickets_supportticket"."created_by_id" = 6 ) ORDER BY "tickets_supportticket"."date_created" DESC'