views:

42

answers:

2

I've got

class Supplier(Model) : 
   pass

class Customer(Model) : 
   pass

class Dock(Model) : 
   pass

class SupplierDockAccess(Model) : 
   supplier = ForeignKey(Supplier)
   dock = ForeignKey(Dock)

class SupplierCustomerAccess(Model):
   supplier = ForeignKey(Supplier)
   customer = ForeignKey(Customer)

I have an instance of Customer, and I'd like to get all Docks that the customer has access to. Customers have access to Suppliers via SupplierCustomerAccess, and Suppliers have access to Docks via SupplierDockAccess. I can do it like so:

# get the suppliers the customer has access to
supplier_customer_accesses = SupplierCustomerAccess.objects.filter(customer=customer)
suppliers = [s.supplier for s in supplier_customer_accesses]

# get the docks those suppliers have access to
supplier_dock_accesses = SupplierDockAccess.objects.filter(supplier__in=suppliers)
docks = [s.dock for s in supplier_dock_accesses]

... but then the resulting list of docks contains duplicates, and I really think it oughtta be possible to do it in one go. Anyone feel like demonstrating some mighty django-fu?

+2  A: 

Alright, I figured it out. One of those things where talking about it seems to do the trick:

docks = Dock.objects.filter(supplierdockaccess__supplier__suppliercustomeraccess__customer=customer).distinct()

...and looking at the sql, it does indeed do it in one big join. Nice. Sorry about answering my own question.

Colin
Answering your own question is perfectly fine. Go ahead and accept the answer so that those who come after will know what helped you.
Manoj Govindan
+2  A: 

Easiest way I can think of to do this is a combination of ManyToManyFields and a custom QuerySet/Manager.

from django.db import models
class CustomQuerySetManager(models.Manager):
    """
        Class for making QuerySet methods available on result set
        or through the objects manager.
    """
    def get_query_set(self):   
        return self.model.QuerySet(self.model) 
    def __getattr__(self, attr, *args): 
        try:                   
             return getattr(self.__class__, attr, *args)
        except AttributeError:
             return getattr(self.get_query_set(), attr, *args)

class Customer(models.Model):
   suppliers = models.ManyToManyField(through=SupplierCustomerAccess)
   objects = CustomQuerySetManager()
   class QuerySet(QuerySet):
       def docks(self):
           return Dock.objects.filter(
                   supplierdockaccess__supplier__in=self.suppliers
               ).distinct()
   ...
class Supplier(models.Model):
   docks = models.ManyToManyField(through=SupplierDockAccess)
   ...

You should see only one or two hits to the database (depending on if you used select_related when you got your customer), and your code is insanely clean:

docks = customer.docks()
suppliers = customer.suppliers.all()
...
Jack M.
This also limits your JOIN to two tables, instead of four like the OP's answer.
Jack M.