Pridkett
Pridkett

Reputation: 5523

Inheritance and factory functions in Python and Django

I'm creating a Django app that uses some inheritance in it's model, mainly because I need to assign everything a UUID and a reference so I know what class it was. Here's a simplified version of the base class:

class BaseElement(models.Model):
    uuid = models.CharField(max_length=64, editable=False, blank=True, default=lambda:unicode(uuid4()))
    objmodule = models.CharField(max_length=255, editable=False, blank=False)
    objclass = models.CharField(max_length=255, editable=False, blank=False)

class ChildElement(BaseElement):
    somefield = models.CharField(max_length=255)

I'd like to make sure that objmodule, objclass, and uuid are set automatically. I've learned from this post that it's a bad idea to do that by writing my own constructor, and that I'm better off writing a factory function. So now my BaseElement and ChildElement looks like this:

class BaseElement(models.Model):
    uuid = models.CharField(max_length=64, editable=False, blank=True, default=lambda:unicode(uuid4()))
    objmodule = models.CharField(max_length=255, editable=False, blank=False)
    objclass = models.CharField(max_length=255, editable=False, blank=False)

    def set_defaults(self):
        self.objmodule = unicode(self.__class__.__module__)
        self.objclass = unicode(self.__class__.__name__)
        self.uuid = unicode(uuid4())

class ChildElement(BaseElement):
    somefield = models.CharField(max_length=255)

    @staticmethod
    def create(*args, **kwargs):
        ce = ChildElement(*args, **kwargs)
        ce.set_defaults()
        return ce

This works. I can call ChildElement.create(somefield="foo") and I will get an appropriate object with uuid, objmodule, and objclass fields set correct. However, as I go through and create more classes like ChildElement2 and ChildElement3, I'm finding that I'm inserting the exact same static factory function. This grates on me because code duplication is bad.

With normal methods I'd just insert the create factory function in BaseElement, however, I can't do that here because I don't have a handle to self (because it hasn't been created yet) to get information about the class of the object that invoked the method.

Is there a way that I can migrate this factory into the BaseElement class so I don't have to duplicate this code everywhere and still have it so it automatically sets the values of uuid, objmodule, and objclass?

Upvotes: 5

Views: 2080

Answers (2)

Carl Meyer
Carl Meyer

Reputation: 126611

If you make create() a @classmethod instead of @staticmethod, you'll have access to the class object, which you can use instead of referring to it by name:

@classmethod
def create(cls, *args, **kwargs):
    obj = cls(*args, **kwargs)
    obj.set_defaults()
    return obj

This is now generic and can go on the base class instead of each child class.

Upvotes: 7

f4nt
f4nt

Reputation: 2671

I think you might have been better off overriding save in your BaseElement instead. Then on save you could set those fields. It'd be something like:

class MyBase(models.Model):
    uuid = models.CharField(max_length=64, editable=False, blank=True,
        default=lambda:unicode(uuid4()))
    objmodule = models.CharField(max_length=255, editable=False, blank=False)
    objclass = models.CharField(max_length=255, editable=False, blank=False)

    def save(self):
        if not self.id:
            self.objmodule = unicode(self.__class__.__module__)
            self.objclass = unicode(self.__class__.__name__)
            self.uuid = unicode(uuid4())
        super(self.__class__.__base__, self).save()

class InheritedFromBase(MyBase):
    new_field = models.CharField(max_length=100)

I tested with that and it seemed to do what you're looking for. I was able to create an "InheritedFromBase" object that had the fields you needed, without a lot of code duplication.

Upvotes: 2

Related Questions