views:

45

answers:

4

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.

A: 

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.

no
+1  A: 

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.

Gintautas Miliauskas
+3  A: 

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;
kRON
Your second example seems to work fine, which leaves me wondering what the reason is for Django's abstract classes... seems unnecessary if normal inheritance works. I think the mistake I made when trying this before was extending model.Model with my base class instead of extending nothing.
no
I think the answer lies between the gory details of the Django model metaclass. I'm pretty sure because of the kung fu that happens there for the `_meta` framework (remember how model fields get changed during class initialization), inheritance constraints and meta options you'd need to have `Models` as the base class for everything to sing properly--in which case if it's not abstract a db schema definition will get created. Haven't ever tried it myself, but I bet something quirky should happen if you inherit from a class with model fields that doesn't have a `Model` base.
kRON
Something quirky does happen... I just discovered that. Methods seem to work fine, but properties model.* properties do not.
no
+1  A: 

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.

Satoru.Logic
but there is no guarantee that an `ImageField` has a `photo` property either, is there? This is an interesting solution though, thanks.
no
@no, you can use `getattr` to get the field you declare, and you don't have to name the field `photo` everywhere.
Satoru.Logic
This would add a thumbnail method to photo field, not to the model class itself, correct? Doesn't the method need to belong to the model class for it to show up in the admin list?
no
@no, No, it won't be added to the photo field itself. In the context where `contribute_to_class` is called, `cls` refers to the model class, so the method will be added to the model class. Please read this article for detail or just read the source code of `django.db.models.base.ModelBase.add_to_class` :http://lazypython.blogspot.com/2008/11/django-models-digging-little-deeper.html
Satoru.Logic