views:

195

answers:

2

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?

+2  A: 

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.

f4nt
+1: Simply override `save` is almost always the right thing to do.
S.Lott
That's close, and may prove to be acceptable, but is not quite what I need. Right now I create some objects on the server then serialize them to a client for use in an AJAXy app. The client can modify attributes and then save the objects. With your setup they wouldn't get `uuid`, `objmodule`, and `objclass` assigned until they are saved. As I use `uuid` on the client, I would probably need to add some additional logic to the server, or have the client assign it. So close, but I'd like to find a way without invoking `save` first. I can deal if this is the right way, however.
Pridkett
+3  A: 

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.

Carl Meyer
That's exactly what I was looking for. Thanks for reminding me about `@classmethod`. It's not something I use very often.
Pridkett