简体   繁体   中英

django heterogeneous queryset proxy models

I am trying to figure out how to use proxy classes in Django. I want to receive a queryset where each object belongs to a proxy class of a common super class so that I can run custom sub-classed methods with the same name and my controller logic doesn't need to know or care about which kind of Proxy model it is working with. One thing I don't want to do is to store the information in multiple tables because I want to have unified identifiers for easier reference/management.

I am pretty new to django/python so I would be happy to hear alternative ways to accomplish what I am trying to do.

Here is what I have:

TYPES = (
    ('aol','AOL'),
    ('yhoo','Yahoo'),
)

class SuperConnect(models.Model):
  name = models.CharField(max_length=90)
  type = models.CharField(max_length=45, choices = TYPES)
  connection_string = models.TextField(null=True)

class ConnectAOL(SuperConnect):
  class Meta:
    proxy = True

  def connect(self):
     conn_options = self.deconstruct_constring()
     # do special stuff to connect to AOL

  def deconstruct_constring(self):
     return pickle.loads(self.connection_string)

class ConnectYahoo(SuperConnect):
  class Meta:
    proxy = True

  def connect(self):
     conn_options = self.deconstruct_constring()
     # do special stuff to connect to Yahoo

  def deconstruct_constring(self):
     return pickle.loads(self.connection_string)

Now what I want to do is this:

connections = SuperConnect.objects.all()

for connection in connections:
  connection.connect()
  connection.dostuff

I've looked around and found some hacks but they look questionable and may require me to go to the database for each item in order to retrieve data I probably already have...

Somebody please rescue me :) or I am going to go with this hack:

class MixedQuerySet(QuerySet):
    def __getitem__(self, k):
        item = super(MixedQuerySet, self).__getitem__(k)
        if item.atype == 'aol':
            yield(ConnectAOL.objects.get(id=item.id))
        elif item.atype == 'yhoo':
            yield(ConnectYahoo.objects.get(id=item.id))
        else:
            raise NotImplementedError

    def __iter__(self):
        for item in super(MixedQuerySet, self).__iter__():
            if item.atype == 'aol':
                yield(ConnectAOL.objects.get(id=item.id))
            elif item.atype == 'yhoo':
                yield(ConnectYahoo.objects.get(id=item.id))
            else:
                raise NotImplementedError

class MixManager(models.Manager):
    def get_query_set(self):
        return MixedQuerySet(self.model)

TYPES = (
    ('aol','AOL'),
    ('yhoo','Yahoo'),
)

class SuperConnect(models.Model):
  name = models.CharField(max_length=90)
  atype = models.CharField(max_length=45, choices = TYPES)
  connection_string = models.TextField(null=True)
  objects = MixManager()

class ConnectAOL(SuperConnect):
  class Meta:
    proxy = True

  def connect(self):
     conn_options = self.deconstruct_constring()
     # do special stuff to connect to AOL

  def deconstruct_constring(self):
     return pickle.loads(self.connection_string)

class ConnectYahoo(SuperConnect):
  class Meta:
    proxy = True

  def connect(self):
     conn_options = self.deconstruct_constring()
     # do special stuff to connect to Yahoo

  def deconstruct_constring(self):
     return pickle.loads(self.connection_string)

As you mentioned in your question, the problem with your solution is that it generates a SQL query for every object instead of using one SQL in = (id1, id2) query. Proxy models cannot contain additional database fields, so there is no need for extra SQL queries.

Instead, you can convert a SuperConnect object to the appropriate type in SuperConnect.__init__ , using the __class__ attribute:

class SuperConnect(models.Model):
    name = models.CharField(max_length=90)
    type = models.CharField(max_length=45, choices = TYPES)
    connection_string = models.TextField(null=True)

    def __init__(self, *args, **kwargs):
        super(SuperConnect, self).__init__(*args, **kwargs)
        if self.type == 'aol':
            self.__class__ = ConnectAOL
        elif self.type == 'yahoo':
            self.__class__ = ConnectYahoo

There is no need for custom managers or querysets, the correct type is set when the SuperConnect object is initialized.

How about putting all the logic in one class. Something like this:

def connect(self):
    return getattr(self, "connect_%s" % self.type)()

def connect_aol(self):
    pass # AOL stuff

def connect_yahoo(self):
    pass # Yahoo! stuff

In the end you have your type field and you should be able to do most (if not all) things that you can do with seperate proxy classes.

If this approach doesn't solve your specific use cases, please clarify.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM