2

So I have models like this:

class Celebrity(models.Model):
    #30+ fields here ...

class HoneyBadger(models.Model):
    name = models.CharField(max_length=10)
    celebrity_owner = models.ForeignKey(Celebrity)

Now I want the admin interface for HoneyBadger to show the name of the creature plus the celebrity owner fields.

I know the standard advice is to do something like this:

class HoneyBadger(models.Model):
    name = models.CharField(max_length=10)
    celebrity_owner = models.ForeignKey(Celebrity)

    def owner_birth_date(self):
        return self.celebrity_owner.birth_date
    #And so on for every other field in celebrity_owner

And then reference those methods in the admin.

Instead I want a way to save myself all that typing!

Here's my first attempt at a lazy shortcut:

class HoneyBadger(models.Model):
    name = models.CharField(max_length=10)
    celebrity_owner = models.ForeignKey(Celebrity)

    def __getattr__(self, name):
        """Dynamically make derived fields for celebrity_owner fields so I don't have to type out"""
        field_lookup_prefix = 'celebrity_owner_field_'
        if name.startswith(field_lookup_prefix):
            field_name = name[len(field_lookup_prefix):]
            def wrapper(*args, **kwargs):
                return getattr(self.celebrity_owner, field_name)
            return wrapper
        else:
            raise AttributeError('%s not found' % name)

That does work when I run it in the shell, but the Django admin isn't liking it. I get this error:

ImproperlyConfigured at /admin/tracker/

HoneyBadgerAdmin.list_display[5], 'celebrity_owner_field_birth_date' is not a callable or an attribute of 'HoneyBadgerAdmin' or found in the model 'HoneyBadger'.

Does anyone know how I can make my code work with the admin or if there's another way to save typing out a method for every single celebrity field? (Perhaps some kind of runtime monkey patching?)

Greg
  • 45,306
  • 89
  • 231
  • 297

2 Answers2

2

There are two possible workarounds to achieve what you want.

  1. Provide a link to the Celebrity object in HoneyBadger Admin:

    def celebrity_link(self):
        return '<a href="%s">%s</a>' % (reverse('admin:appname_celebrity_change',args=(self.celebrity_owner.id,)), self.celebrity_owner.celeb_name)
    
    celebrity_link.allow_tags = True
    celebrity_link.short_description = u'Celebrity'
    
    class HoneyBadgerAdmin(admin.ModelAdmin):
        list_display = [..., celebrity_link, ...]
    
  2. Next solution which is a bit complex but will do exactly what we want is: Define your custom admin template. Basically copy the change form from the django source and add custom code to display the Celebrity information. See this documentation.

Sudipta
  • 4,773
  • 2
  • 27
  • 42
  • I tried it. I don't think inlines work for foreign keys, only manytomany. (Maybe for 1to1 too?) – Greg Jun 13 '13 at 17:29
  • @Greg Yes I realized this solution will not work as Inlines are designed to follow relationships backwards. So if you do exact opposite of the solution provided; i.e. make HoneyBadgerInline and CelebrityAdmin, then you can have all the linked HoneyBadgers showing up in the Celebrity admin page. But that's what we want here. The best substitute would be to provide a link to the Celebrity object. I'll edit the solution to show this. – Sudipta Jun 14 '13 at 09:40
1

It looks like you want to extend the Celebrity class with a name field? Why not inherit that class in the HoneyBadger class?

class HoneyBadger(Celebrity):
    name = models.CharField(max_length=10)

(You may have to delete the old HoneyBadger table from the database first for this to work.)

Brent Washburne
  • 12,904
  • 4
  • 60
  • 82
  • Doesn't inheritance make an implicit onetoone field linking the two? One celebrity can own many badgers so that wouldn't work. – Greg Jun 12 '13 at 16:21
  • No, inheritance makes a copy of all the fields in the parent class into the child class, so HoneyBadger will have all the Celebrity fields in its table. This is called an "is-a" relation, where HoneyBadger is-a Celebrity. – Brent Washburne Jun 12 '13 at 16:31
  • If you instead want a "has-a" relation, where Celebrity has-a HoneyBadger, then your original design will work. Just leave out the HoneyBadger methods and reference them with honey_badger.celebrity_owner.birthdate . Django will chase down the foreign keys for you. – Brent Washburne Jun 12 '13 at 16:34
  • But I'm trying to reference those fields from the admin so I can't just say honey_badger.celebrity_owner.birthdate. – Greg Jun 12 '13 at 17:43
  • Oh, this is for the Admin. You'd think that the celebrity_owner__birthdate notation would work, but you need this monkeypatch for your HoneyBadger Admin model: http://djangosnippets.org/snippets/2887/ – Brent Washburne Jun 12 '13 at 18:31