2011-01-07 8 views
6

He visto ejemplos en línea de personas que usan __getattr__ con modelos de Django, pero cada vez que lo intento obtengo errores. (Django 1.2.3)¿Por qué no puedo usar __getattr__ con los modelos de Django?

No tengo ningún problema cuando estoy usando __getattr__ en objetos normales. Por ejemplo:

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

funciona bien ...

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

Ahora, cuando lo intento con un modelo de Django:

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

y probarlo en el intérprete:

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

¿Alguien puede explicar lo que está pasando? g on?


EDIT: Yo podría haber sido demasiado abstracto en los ejemplos, he aquí algo de código que está más cerca de lo que en realidad usaría en el sitio web:

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

Mientras podría crear una propiedad para cada atributo de la clase Post, que daría lugar a una gran cantidad de duplicación de código. Además, eso significaría que cada vez que agregue o edite un atributo de la clase Post, tendría que recordar hacer el mismo cambio en la clase CuratedPost, que parece una receta para la descomposición del código.

+2

¿es realmente "return self.post.name"? Debería ser "return getattr (self.post, name)" –

+0

De hecho, eso es lo que el código debería ser – Joshmaker

Respuesta

0

Django envía ciertas señales cuando los modelos se inicializan por primera vez (es decir, cargando el shell) - haciendo que las llamadas a __getattr siempre devuelvan un número entero, ha modificado el código de una manera que Django señaliza weren ' t esperando (y por lo tanto, están rompiendo).

Si quieres hacer esto, tal vez tratar de esta manera:

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

Esto no funciona, ya que hasattr (self, attr) termina llamando a \ _ \ _ getattr \ _ \ _ recursivamente. En su lugar, quiere "attr en self .__ dict__". –

+1

Y en realidad, eso tampoco funcionará, ya que \ _ \ _ getattr \ _ \ _ solo se llama si attr no está en \ _ \ _ dict \ _ \ _ –

+1

@Andrew: Parece que se puede hacer eso con '__getattribute__', aunque – Cameron

5

Hay que tener cuidado usando __getattr__. Solo intercepte lo que sabe y deje que la clase base maneje lo que usted no sabe.

El primer paso es, ¿puedes usar una propiedad en su lugar? Si quieres un atributo "al azar" que devuelven 42, entonces esto es mucho más seguro:

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

si quieres "random_ *" (como "random_1", ​​"random_34", etc) para hacer algo, entonces se le tiene que usar __getattr__ como este:

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

Se suponía que el ejemplo que utilicé en mi publicación mostraba que incluso las aplicaciones muy simples de la propiedad __getattr__ se rompen cuando extiendo el objeto del modelo de Django. He actualizado mi publicación original para incluir algo más cercano a lo que usaría en realidad. – Joshmaker

+2

No creo que esto funcione con Django, porque models.Model no tiene '__getattr__', por lo que su' super (Post, self) .__ getattr __ (...) 'debería arrojar una excepción. – Cerin

+1

¿Lo has probado? Python proporciona un __getattr__ predeterminado, por lo que 'should' no debería ser un problema. –

Cuestiones relacionadas