tags:

views:

33

answers:

3

Suppose I have a few models representing real life objects: "Person", "Chair", "Room"

I also have a "Collection" model, which represents some collection of records of these models.

Each model can be a member of more than on collection - therefore, I have also created a "Membership" model, which represents an object is a member of a collection. It is defined as follows:

class Membership(models.Model):
   content_type   = models.ForeignKey(ContentType)
   object_id      = models.PositiveIntegerField()
   content_object = generic.GenericForeignKey('content_type', 'object_id')

   collection     = models.ForeignKey('Collection', related_name="members")

I want to be able to create a QuerySet, which given a collection, represents all its members of a given model. I know I can do it programmatically, but I need it in a QuerySet, which can be filtered, ordered etc.

EDIT:

Obviously this can be done using raw SQL:

   SELECT * FROM 
       ( modelx INNER JOIN membership ON modelx.id = membership.object_id) 
   WHERE 
       ( membership.collection_id=<my-collection-id> AND    
         membership.content_type_id=<modelx-type-id> )

But can it be represented using the Django query language?

A: 

No, this isn't possible. Querysets can only ever be of one single model type. So you can get a queryset of Membership objects and refer to each one's content_object property, which will give you the related object, but you can't get all the related objects directly in one queryset.

Daniel Roseman
I only want the queryset to hold records of one model...I could rephrase the question as: Can I query all records of model X, so that there exists a Membership which has the record X as its content object and a specific Collection in its "collection" field?
adamk
A: 

I implemented exactly this by way of a with_model method on a custom manager for the membership model:

class CollectionMemberManager(models.Manager):
    use_for_related_fields = True

    def with_model(self, model):
        return model._default_manager.filter(pk__in=self.filter(member_content_type=ContentType.objects.get_for_model(model)).values_list('member_object_id', flat=True))

CollectionMember is my equivalent to your Membership model. For more context, see the code in its entirety.

Joseph Spiros
adamk
A: 

It seems I have found the solution, by using QuerySet's extra method:

def members_of_model(collection,cls):
    cls_type = ContentType.objects.get_for_model(cls)
    cm_tablename = CollectionMembership._meta.db_table
    cls_tablename = cls._meta.db_table
    return cls.objects.all().extra(tables=[cm_tablename],
                                   where=[ '%s.content_type_id=%%s' % cm_tablename,
                                           '%s.collection_id=%%s' % cm_tablename,
                                           '%s.object_id=%s.id' % (cm_tablename, cls_tablename) ],
                                   params=[cls_type.id,collection.id] )

This returns a valid QuerySet of a specific model, which holds all records which are members of a specific collection.

adamk