Why can't I use __getattr__ with Django models?
Question: I've seen examples online of people using `__getattr__` with Django models,
but whenever I try I get errors. (Django 1.2.3)

I don't have any problems when I am using `__getattr__` on normal objects. For
example:

    
    
    class Post(object):
         def __getattr__(self, name):
             return 42
    

Works just fine...

>
>      >>> from blog.models import Post
>      >>> p = Post()
>      >>> p.random
>      42
>  

Now when I try it with a Django model:

    
    
    from django.db import models
    class Post(models.Model):
         def __getattr__(self, name):
             return 42
    

And test it on on the interpreter:

>
>      >>> from blog.models import Post
>      >>> p = Post()
>      ERROR: An unexpected error occurred while tokenizing input The
>  
>
> following traceback may be corrupted or invalid The error message is: ('EOF
> in multi-line statement', (6, 0))
>
> \---------------------------------------------------------------------------
> TypeError  
>  Traceback (most recent call last)
>
> /Users/josh/project/ in ()
>
> /Users/josh/project/lib/python2.6/site-packages/django/db/models/base.pyc in
> **init**(self, *args, **kwargs) 338 if kwargs: 339 raise TypeError("'%s' is
> an invalid keyword argument for this function" % kwargs.keys()[0]) \--> 340
> signals.post_init.send(sender=self.**class** , instance=self) 341 342 def
> **repr**(self):
>
> /Users/josh/project/lib/python2.6/site-
> packages/django/dispatch/dispatcher.pyc in send(self, sender, **named) 160
> 161 for receiver in self._live_receivers(_make_id(sender)): \--> 162
> response = receiver(signal=self, sender=sender, **named) 163
> responses.append((receiver, response)) 164 return responses
>
> /Users/josh/project/python2.6/site-packages/photologue/models.pyc in
> add_methods(sender, instance, signal, *args, **kwargs) 728 """ 729 if
> hasattr(instance, 'add_accessor_methods'): \--> 730
> instance.add_accessor_methods() 731 732 # connect the add_accessor_methods
> function to the post_init signal
>
> TypeError: 'int' object is not callable

Can someone explain what is going on?

* * *

EDIT: I may have been too abstract in the examples, here is some code that is
closer to what I actually would use on the website:

    
    
    class Post(models.Model):
        title = models.CharField(max_length=255)
        slug = models.SlugField()
        date_published = models.DateTimeField()
        content = RichTextField('Content', blank=True, null=True)
        # Etc...
    
    Class CuratedPost(models.Model):
        post = models.ForeignKey('Post')
        position = models.PositiveSmallIntegerField()
    
        def __getattr__(self, name):
            ''' If the user tries to access a property of the CuratedPost, return the property of the Post instead...  '''
            return self.post.name
    
        # Etc...
    

While I _could_ create a property for each attribute of the Post class, that
would lead to a lot of code duplication. Further more, that would mean anytime
I add or edit a attribute of the Post class I would have to remember to make
the same change to the CuratedPost class, which seems like a recipe for code
rot.


Answer: One must be careful using __getattr__ . Only intercept what you know, and let
the base class handle what you do not.

The first step is, can you use a property instead? If you want a "random"
attribute which return 42 then this is much safer:

    
    
    class Post(...):
      @property
      def random(self):
        return 42
    

If you want "random_*" (like "random_1", "random_34", etc) to do something
then you'll have to use __getattr__ like this:

    
    
    class Post(...):
      def __getattr__(self, name):
        if name.startswith("random_"):
          return name[7:]
        return super(Post, self).__getattr__(name)
    

