Question

This may be long, but I really really need your help before I use this in my production. I have a class Asset, which is the base class of other classes (like Photo, Question, Video etc.) Basically its a Multiple Table Inheritance. I need this to get all the post or all the objects of the user in the template. And it does what I want. But I noticed, many were against Multiple Table Inheritance, and a few discouraged Multiple Table Inheritance. So, I really need your generalized opinions, on using Multiple Table Inheritance. And also, what other option do I have? Or is there any other way to achieve to get all the objects (assets) of a User? Please help me decide what to do. I am using django and postresql. If I am not being clear, please ask me. I will really be thankful and appreciate if anyone would guide me through this.

Thank you.

This are my models:

class Asset(models.Model):
    user = models.ForeignKey(User, related_name = "user_objects")
    likes = models.ManyToManyField(User, through="Like", related_name="Liked_user")
    comments = models.ManyToManyField(User, through="Comment", related_name="Commented_user")
    timestamp = models.DateTimeField(auto_now = True, auto_now_add= False)
    updated = models.DateTimeField(auto_now = False, auto_now_add = True)

    class Meta:
        ordering = ['-timestamp']

    def __unicode__(self):
        return self.__class__.__name__

class Like(models.Model):
    asset = models.ForeignKey(Asset)
    liked_by = models.ForeignKey(User)
    liked_time = models.DateTimeField(auto_now = True, auto_now_add = False)

    def __unicode__(self):
        return "%s likes %s" % (self.liked_by, self.asset)

class Comment(models.Model):
    asset = models.ForeignKey(Asset)
    comment_by = models.ForeignKey(User)
    liked_time = models.DateTimeField(auto_now = True, auto_now_add = False)

    def __unicode__(self):
        return "%s likes %s" % (self.comment_by, self.asset)

def get_upload_file_name(instance, filename):
    return "uploaded_files/%s_%s" %(str(time()).replace('.','_'), filename)


class Album(Asset):
    title = models.CharField(max_length=200)
    description = models.TextField()

    def __unicode__(self):
        return self.__class__.__name__


class Picture(Asset):
    description = models.TextField()
    image = models.ImageField(upload_to=get_upload_file_name)
    album = models.ForeignKey(Album, null=True, blank=True, default = None)

    def __unicode__(self):
        return self.__class__.__name__

class BackgroundImage(Picture):
    pass

class ProfilePicture(Picture):
    pass


class Tag(models.Model):
    title = models.CharField(max_length=40)
    description = models.TextField()

    def __unicode__(self):
        return self.title

class Question(Asset):
    title = models.CharField(max_length=200)
    description = models.TextField()
    tags = models.ManyToManyField(Tag)

    def __unicode__(self):
        return self.title

class Answer(Asset):
    description = models.TextField()
    question = models.ForeignKey(Question)

    def __unicode__(self):
        return self.description 
Was it helpful?

Solution

Now that I've thought about it some more I'd like to suggest another solution using the Django contenttypes framework for generic relationships. I had not noticed before that you have 2 levels of inheritance (i.e. Asset->Picture->BackgroundImage). My previous answer would still work, but you'd have to add explicit links to the child models. This would allow you to cleanly have Asset link to all your asset types and vice versa.

class Asset(models.Model):
    user = models.ForeignKey(User, related_name = "user_objects")
    likes = models.ManyToManyField(User, through="Like", related_name="Liked_user")
    comments = models.ManyToManyField(User, through="Comment", related_name="Commented_user")
    timestamp = models.DateTimeField(auto_now = True, auto_now_add= False)
    updated = models.DateTimeField(auto_now = False, auto_now_add = True)

    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    class Meta:
        ordering = ['-timestamp']

    def __unicode__(self):
        return self.__class__.__name__

class Like(models.Model):
    asset = models.ForeignKey(Asset)
    liked_by = models.ForeignKey(User)
    liked_time = models.DateTimeField(auto_now = True, auto_now_add = False)

    def __unicode__(self):
        return "%s likes %s" % (self.liked_by, self.asset)

class Comment(models.Model):
    asset = models.ForeignKey(Asset)
    comment_by = models.ForeignKey(User)
    liked_time = models.DateTimeField(auto_now = True, auto_now_add = False)

    def __unicode__(self):
        return "%s likes %s" % (self.comment_by, self.asset)

def get_upload_file_name(instance, filename):
    return "uploaded_files/%s_%s" %(str(time()).replace('.','_'), filename)


class AlbumAsset(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()
    asset = generic.GenericRelation(Asset)

    def __unicode__(self):
        return self.__class__.__name__

class PictureAssetBase(models.Model):
    description = models.TextField()
    image = models.ImageField(upload_to=get_upload_file_name)
    album = models.ForeignKey(Album, null=True, blank=True, default = None)
    asset = generic.GenericRelation(Asset)

    def __unicode__(self):
        return self.__class__.__name__

    class Meta:
        abstract = True

class PictureAsset(PictureAssetBase):
    pass

class BackgroundImageAsset(PictureAssetBase):
    pass

class ProfilePictureAsset(PictureAssetBase):
    pass


class Tag(models.Model):
    title = models.CharField(max_length=40)
    description = models.TextField()

    def __unicode__(self):
        return self.title

class QuestionAsset(models.Model):
    title = models.CharField(max_length=200)
    description = models.TextField()
    tags = models.ManyToManyField(Tag)
    asset = generic.GenericRelation(Asset)

    def __unicode__(self):
        return self.title

class AnswerAsset(models.Model):
    description = models.TextField()
    question = models.ForeignKey(Question)
    asset = generic.GenericRelation(Asset)

    def __unicode__(self):
        return self.description 

Notice the content_object field in Asset. I also suggest that you make an ABC for pictures PictureAssetBase (notice abstract=True as explain in the docs here) and have PictureAsset, BackgroundImageAsset & ProfilePictureAsset inherit from that.

All asset type models have a reverse relation to Asset via GenericRelation. This way you're free to add new asset types with minimal impact to other models. In your Views etc. you will obviously need to provide special handling for each type, but you can determine the linked asset type via the content_type field in Asset, or add an explicit type field and handle it yourself.

OTHER TIPS

So I gather that you are doing some sort of timeline application. My suggestion would be to have your Asset model with OneToOneField relationships to your different asset types. So your Asset model would look like:

class Asset(models.Model):
    user = models.ForeignKey(User, related_name = "user_objects")
    likes = models.ManyToManyField(User, through="Like", related_name="Liked_user")
    comments = models.ManyToManyField(User, through="Comment", related_name="Commented_user")
    timestamp = models.DateTimeField(auto_now = True, auto_now_add= False)
    updated = models.DateTimeField(auto_now = False, auto_now_add = True)

    album = models.OneToOneField(Album, null=True, blank=True)
    picture = models.OneToOneField(Picture, null=True, blank=True)
    question = models.OneToOneField(Question, null=True, blank=True)
    answer = models.OneToOneField(Answer, null=True, blank=True)
    # etc...

    class Meta:
        ordering = ['-timestamp']

    def __unicode__(self):
        return self.__class__.__name__

Album, Picture, Question & Answer would no longer inherit from Asset, but would be standard models.

This way you can have assets in a common collection and sort/display them however you want. Then depending on what field is not null, that will be the "type" of an individual asset. It will take some manual checking to determine type and make sure an asset is not empty, but they are different enough that they would require custom handling anyway. I've done this type of thing several times before and it worked fine for my use cases.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top