Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating instances of Django proxy models from their base class

Tags:

python

django

I have a series of models that look like this:

class Analysis(models.Model):
    analysis_type = models.CharField(max_length=255)

    def important_method(self):
        ...do stuff...


class SpecialAnalysis(Analysis):
    class Meta:
        proxy = True

    def important_method(self):
        ...something different...

This is all pretty standard. However, what I'd like to do is automatically convert an Analysis model to a proxy model based on the value of the analysis_type field. For example, I'd like to be able to write code that looks like this:

>>> analysis = Analysis.objects.create(analysis_type="nothing_special")
>>> analysis.__class__
<class 'my_app.models.Analysis'>

>>> analysis = Analysis.objects.create(analysis_type="special")
>>> analysis.__class__
<class 'my_app.models.SpecialAnalysis'>

>>> analysis = Analysis.objects.get(pk=2)
>>> analysis.__class__
<class 'my_app.models.SpecialAnalysis'>

>>> # calls the ``important_method`` of the correct model
>>> for analysis in Analysis.objects.all():
...     analysis.important_method()

Is this even remotely possible? A similar question was asked here, which actually gives some code for the iteration example, but it still leaves me with the question of how to get or create an instance of a proxy class from its parent. I suppose I could just override a bunch of manager methods, but I feel like there must be a more elegant way to do it.

like image 564
Mark Micchelli Avatar asked Aug 27 '13 19:08

Mark Micchelli


2 Answers

This is a great approach and I don't particularly see it as a cheat. Here is IMHO some enhancements to the __init__ function so it doesn't has to change when you add more classes.

def __init__(self, *args, **kwargs):
    super(Analysis, self).__init__(*args, **kwargs)
    if not self.__type and type(self) == Analysis:
        raise Exception("We should never create a base Analysis object. Please create a child proxy class instead.")

    for _class in Analysis.__subclasses__():
        if self.check_type == _class.__name__:
            self.__class__ = _class
            break

def save(self, **kwargs):
    self.check_type = self.__class__.__name__
    super(Analysis, self).save(**kwargs)

Hope this helps!

like image 118
Hassek Avatar answered Sep 23 '22 04:09

Hassek


I haven't found a "clean" or "elegant" way to do this. When I ran into this problem I solved it by cheating Python a little bit.

class Check(models.Model):
    check_type = models.CharField(max_length=10, editable=False)
    type = models.CharField(max_length=10, null=True, choices=TYPES)
    method = models.CharField(max_length=25, choices=METHODS)
    'More fields.'

    def __init__(self, *args, **kwargs):
        super(Check, self).__init__(*args, **kwargs)
        if self.check_type:
            map = {'TypeA': Check_A,
                'TypeB': Check_B,
                'TypeC': Check_C}
            self.__class__ = map.get(self.check_type, Check)

    def run(self):
        'Do the normal stuff'
        pass


class Check_A(Check):
    class Meta:
        proxy = True

    def run(self):
        'Do something different'
        pass

class Check_B(Check):
    class Meta:
        proxy = True

    def run(self):
        'Do something different'
        pass


class Check_C(Check):
    class Meta:
        proxy = True

    def run(self):
        'Do something different'
        pass

It's not really clean but it was the easiest hack to find which solved my problem.

Maybe this is helps you, maybe it doesn't.

I'm also hoping someone else has a more pythonic solution to this problem since I'm counting the days till this method fails and comes back to haunt me..

like image 35
EWit Avatar answered Sep 20 '22 04:09

EWit