2011-01-07 8 views
6

Ho visto esempi online di persone che usano __getattr__ con modelli Django, ma ogni volta che provo ottengo errori. (Django 1.2.3)Perché non posso usare __getattr__ con i modelli Django?

Non ho alcun problema quando sto usando __getattr__ su oggetti normali. Per esempio:

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

funziona bene ...

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

Ora in cui provo con un modello di Django:

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

e testarlo sul sul interprete:

>>> 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

Qualcuno può spiegare cosa sta succedendo g on?


EDIT: io sia stato troppo astratta negli esempi, qui è un codice che è più vicino a quello che in realtà avrei usato sul sito:

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... 

Mentre io potrebbe creare un proprietà per ogni attributo della classe Post, che porterebbe a un sacco di duplicazione del codice. Inoltre, ciò significherebbe ogni volta che aggiungo o modifico un attributo della classe Post che dovrei ricordare per apportare la stessa modifica alla classe CuratedPost, che sembra una ricetta per il codice marc.

+2

è davvero "return self.post.name"? Dovrebbe essere "return getattr (self.post, name)" –

+0

In effetti, questo è ciò che il codice dovrebbe essere – Joshmaker

risposta

0

Django invia determinati segnali quando i modelli vengono inizializzati prima (cioè, caricando il guscio) - facendo in modo che le chiamate verso __getattr restituire sempre un numero intero, è stato modificato il codice in modo che i segnali Django weren' aspettarsi (e quindi si stanno spezzando).

Se si vuole fare questo, magari provare in questo modo:

def __getattr__(self, attr): 
    if hasattr(self, attr): 
    return super(MyModel, self).__getattr__(attr) 
    return 42 
+0

Questo non funziona poiché hasattr (self, attr) finisce per chiamare \ _ \ _ getattr \ _ \ _ in modo ricorsivo. Vuoi invece "attr in self .__ dict__". –

+1

E in realtà, non funzionerà neanche da quando \ _ \ _ getattr \ _ \ _ viene chiamato solo se attr non è in \ _ \ _ dict \ _ \ _ –

+1

@Andrew: Sembra che tu possa farlo con '__getattribute__', anche se – Cameron

5

Bisogna stare attenti con __getattr__. Intercetta solo ciò che sai e lascia che la classe base gestisca ciò che non fai.

Il primo passo è, è possibile utilizzare una proprietà invece? Se si desidera che un attributo di "random" che restituiscono 42 allora questo è molto più sicuro:

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

Se si vuole "random_ *" (come "random_1", ​​"random_34", ecc) per fare qualcosa? Allora ti devo usare __getattr__ in questo modo:

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

L'esempio che ho usato nel mio post doveva mostrare che anche le applicazioni molto semplici della proprietà __getattr__ si interrompono quando estendo l'oggetto modello Django. Ho aggiornato il mio post originale per includere qualcosa di più vicino a ciò che effettivamente utilizzerei. – Joshmaker

+2

Non credo che funzionerà con Django, perché model.Model non ha '__getattr__', quindi il tuo super (Post, self) .__ getattr __ (...)' dovrebbe generare un'eccezione. – Cerin

+1

Hai provato? Python fornisce un __getattr__ predefinito, quindi "dovrebbe" non essere un problema. –