Dagg Nabbit
Dagg Nabbit

Reputation: 76766

Resuable model members in django

I have a django model like this:

class Something(models.Model):    
    title = models.CharField(max_length=200, default=u'')
    text  = models.CharField(max_length=250, default=u'', blank=True)
    photo = models.ImageField(upload_to=u'something')
    def photo_thumb(self):
        if self.photo:
            return u'<img src="%s" />' % (settings.MEDIA_URL + '/thumbs/?h=64&w=80&c=50x0&p=' + self.photo.name)
        else: 
            return u'(no photo)'
    photo_thumb.short_description = u'Photo'
    photo_thumb.allow_tags = True
    photo_thumb.admin_order_field = 'photo' 
    def __unicode__(self):
        return self.title;

class SomethingElse(models.Model):    
    name = models.CharField(max_length=200, default=u'')
    foo  = models.CharField(max_length=250, default=u'', blank=True)
    photo = models.ImageField(upload_to=u'something_else')
    def photo_thumb(self):
        if self.photo:
            return u'<img src="%s" />' % (settings.MEDIA_URL + '/thumbs/?h=64&w=80&c=50x0&p=' + self.photo.name)
        else: 
            return u'(no photo)'
    photo_thumb.short_description = u'Photo'
    photo_thumb.allow_tags = True
    photo_thumb.admin_order_field = 'photo' 
    def __unicode__(self):
        return self.title;

I feel like this violates DRY, for obvious reasons. My question is, can I stick this somewhere else:

    # ...
    def photo_thumb(self):
        if self.photo:
            return u'<img src="%s" />' % (settings.MEDIA_URL + '/thumbs/?h=64&w=80&c=50x0&p=' + self.photo.name)
        else: 
            return u'(no photo)'
    photo_thumb.short_description = u'Photo'
    photo_thumb.allow_tags = True
    photo_thumb.admin_order_field = 'photo' 
    # ...

And then include it in relevant model classes with a single line of code? Or can photo_thumb be dynamically added to the appropriate classes somehow? I've tried classical and parasitic inheritance, but I may not be doing it right... I'm new to Django and fairly new to python also. Any help is appreciated.

Upvotes: 1

Views: 243

Answers (4)

satoru
satoru

Reputation: 33225

Another solution may be to create a subclass of ImageField and override the contribute_to_class method:

class ImageWithThumbnailField(ImageField):
    def contribute_to_class(self, cls, name):
        super(ImageWithThumbnailField, self).contribute_to_class(cls, name)


        def photo_thumb(self):
            photo = getattr(self, name, None)
            if photo:
               return u'<img src="%s" />' % (settings.MEDIA_URL + '/thumbs/?h=64&w=80&c=50x0&p=' + photo.name)
            else: 
               return u'(no photo)'
        photo_thumb.short_description = u'Photo'
        photo_thumb.allow_tags = True
        photo_thumb.admin_order_field = 'photo' 

        setattr(cls, 'photo_thumb', photo_thumb);

I think this is better because on calling the photo_thumb method you are expecting the existence of self.photo which is not guaranteed if you are using the other solution that use an abstract model.

EDIT: Note that you can use getattr(self, name) to dynamically access the field. So yes, it is guaranteed that we have some photo field.

Upvotes: 1

Filip Dupanović
Filip Dupanović

Reputation: 33680

I agree with @Gintautas. The general rule of thumb is to create an abstract model class if you need to reuse model fields and meta options; use a simple class if you only need to reuse other properties and methods.

In your case I'd go with the abstract class (because of the photo model field):

class PhotoModels(models.Model):

    photo = models.ImageField(upload_to=u'something')

    def photo_thumb(self):
        if self.photo:
            return u'<img src="%s" />' % (settings.MEDIA_URL +
                   '/thumbs/?h=64&w=80&c=50x0&p=' + self.photo.name)
        else: 
            return u'(no photo)'

    photo_thumb.short_description = u'Photo'
    photo_thumb.allow_tags = True
    photo_thumb.admin_order_field = 'photo'

    class meta:
        abstract = True

class Something(PhotoModels):    
    title = models.CharField(max_length=200, default=u'')
    text = models.CharField(max_length=250, default=u'', blank=True)

class SomethingElse(PhotoModels):    
    name = models.CharField(max_length=200, default=u'')
    foo = models.CharField(max_length=250, default=u'', blank=True)
    photo.upload_to = u'something_else'

    def __unicode__(self):
        return self.title;

... although this would be legal just as well:

class PhotoModels:

    def photo_thumb(self):
        if self.photo:
            return u'<img src="%s" />' % (settings.MEDIA_URL +
                   '/thumbs/?h=64&w=80&c=50x0&p=' + self.photo.name)
        else: 
            return u'(no photo)'

    photo_thumb.short_description = u'Photo'
    photo_thumb.allow_tags = True
    photo_thumb.admin_order_field = 'photo' 

class Something(models.Model, PhotoModels):    
    title = models.CharField(max_length=200, default=u'')
    text = models.CharField(max_length=250, default=u'', blank=True)
    photo = models.ImageField(upload_to=u'something')

class SomethingElse(models.Model, PhotoModels):    
    name = models.CharField(max_length=200, default=u'')
    foo = models.CharField(max_length=250, default=u'', blank=True)
    photo = models.ImageField(upload_to=u'something_else')

    def __unicode__(self):
        return self.title;

Upvotes: 3

Gintautas Miliauskas
Gintautas Miliauskas

Reputation: 7892

Sure you can reuse the code. Just factor it out into a base class, and make both your classes inherit from that base class. That should work just fine. Just don't forget that the base class either needs to inherit from models.Model itself (then I would suggest making it abstract), or you can put the reusable code in a mixin; that means that your both classes will be inheriting from both models.Model and the new mixin base class.

Upvotes: 1

Dagg Nabbit
Dagg Nabbit

Reputation: 76766

Maybe I asked too soon... I think abstract base classes may be the answer.

http://docs.djangoproject.com/en/dev/topics/db/models/#abstract-base-classes

I'll check it out and confirm.

Upvotes: 0

Related Questions