13

I want to add custom attributes to instances of a Django model. These attributes should not be stored in the database. In any other class, the attributes would simply be initialized by the __init__ method.

I can already see three different ways to do it, and none of them are completely satisfying. I wonder if there is any better/more pythonic/djangoist way to do it?

  1. Override the __init__ method: the syntax is a bit convoluted, but it works.

    from django.db.models import Model
    
    class Foo(Model):
      def __init__(self, *args, **kwargs):
         super(Model, self).__init__(*args, **kwargs)
         self.bar = 1
    
  2. Use a Django post_init signal: this takes class code outside of the class definition, which is not very readable.

    from django.dispatch import receiver
    from django.db.models.signals import post_init
    @receiver(post_init, sender=Foo)
        def user_init(sender, instance, *args, **kwargs):
          instance.bar = 1
    
  3. Use an instance method instead of an attribute: having a general exception raised as the default behaviour is a bit disturbing.

    class Foo(Model):
      def bar(self):
        try:
          return self.bar
        except:
          self.bar = 1
        return self.bar
    

Of these three choices, the first looks like the least worst to me. What do you think? Any alternative?

Community
  • 1
  • 1
Régis B.
  • 10,092
  • 6
  • 54
  • 90

2 Answers2

9

I would use the property decorator available in python

class Foo(Model):
    @property
    def bar(self):
        if not hasattr(self, '_bar'):
            self._bar = 1

        return self._bar

Then you can access that just like a property instead of invoking a function with ()

You could even get a little more straight forward with this by having the fallback built in, instead of stored

class Foo(Model):
    @property
    def bar(self):
        return getattr(self, '_bar', 1)
Bryan
  • 6,682
  • 2
  • 17
  • 21
  • Interesting, I didn't know about the @property decorator. But don't you think this is terribly ugly? – Régis B. Jan 22 '13 at 16:40
  • Not if you want to have a built-in default instead of specifying getattr everywhere else in your code. This is just a standard way of creating accessor methods in python, so if it's ugly we'll just need to bring it up to Guido as an improvement for v3 :-P – Bryan Jan 22 '13 at 16:41
  • 2
    Just came back to this question with a couple more years of Python under my belt (2013! I was young and innocent then...). I think today I would go with your answer. The `@property` approach is quite pythonic. On the other hand, django model constructors are seldom overridden. – Régis B. Feb 27 '17 at 18:10
  • 1
    And how would you set the value of _bar? A understand that getattr will return 1 when you first asks for instance.bar, but it won't set _bar to 1 (or at least at the tests I made with python 3) – Diego Aragão Oct 31 '19 at 21:08
  • 1
    @DiegoAragão You can use the `@bar.setter` decorator on a method that sets the value of bar, as documented [here](https://docs.python.org/3/library/functions.html#property). – otterrisk Apr 28 '23 at 09:32
4

Overriding __init__ is the right way.

dreamcrash
  • 47,137
  • 25
  • 94
  • 117
Pawel Furmaniak
  • 4,648
  • 3
  • 29
  • 33
  • Probably, but *do* read the [note about overriding `Model.__init__`](https://docs.djangoproject.com/en/4.1/ref/models/instances/#django.db.models.Model) in the docs. – djvg Nov 15 '22 at 09:39