2010-09-21 11 views
5

Estoy tratando de cambiar el comportamiento de un modelo de Django para permitirme acceder a las propiedades de una clave foránea directamente desde el padre, por ej.Cambiando el comportamiento de un modelo de Django con __getattr__

cache.part_number 
vs 
cache.product.part_number 

He intentado reemplazando el método __getattr__ de la siguiente manera, pero me da un error de recursividad cuando trato de acceder a las propiedades de la clave externa

class Product(models.Model): 
    part_number = models.CharField(max_length=10) 
    ... 

class Cache(models.Model): 
    product = models.ForeignKey(Product) 
    ... 

    def __getattr__(self, name): 
     value = getattr(self.product, name, None) 
     if value: 
      return value 
     else: 
      raise AttributeError 

¿Qué estoy haciendo mal?

+0

¿Por qué intenta reemplazar una función interna de pitón en lugar de utilizar un típico función o nombre del método? – FallenAngel

Respuesta

8

Considere el código dentro de su método de __getattr__:

value = getattr(self.product, name, None) 

Trate de adivinar lo que sucede cuando se invoca self.product. Te daré una pista: implica una llamada al __getattr__. El documentation tiene los detalles:

llama cuando una búsqueda de atributos no ha encontrado el atributo en los lugares habituales (es decir, no es una instancia atribuyen ni se puede encontrar en el árbol de clases para el auto). nombre es el nombre del atributo. Este método debe devolver el valor del atributo (calculado) o generar una excepción AttributeError.

Se ha preguntado cómo self.product resuelve para el correcto Product ejemplo, a pesar de que no está configurando en cualquier lugar?

Tenga en cuenta que si el atributo se encuentra a través del mecanismo normal, no se llama al __getattr__().

Django hace un poco de magia que implica interceptar, lo adivinaste, __getattr__. Por lo tanto, self automáticamente termina con un atributo product. Como está anulando el método __getattr__, la magia de Django deja de funcionar y se utiliza su versión. Como self.product no es un atributo de instancia, se llama de nuevo a __getattr__, y así sucesivamente, lo que lleva a un bucle infinito.

Será mejor que utilice un property para lograrlo.

class Cache(models.Model): 
    product = models.ForeignKey(Product) 
    ... 

    def _get_part_number(self): 
     part_number = self.product.part_number 
     if not part_number: 
      raise AttributeError 
     return part_number 
    part_number = property(_get_part_number) 
+0

ahora hay una respuesta que puedo +1. Aunque en lugar de una propiedad, OP podría usar 'super (Cache, self) .__ getattr __ ('product')', ¿o me falta algo? – aaronasterling

+0

@ Aaron: Sí, puedes. ¿Quiere agregar el fragmento en un método, digamos 'def part_number (self)'? –

+0

Quise añadirlo a la función '__getattr__' de OP. Añadiré una respuesta para aclarar. – aaronasterling

0

¿Qué tal algo como:

class Product(models.Model): 
    part_number = models.CharField(max_length=10) 
    ... 

class Cache(models.Model): 
    product = models.ForeignKey(Product) 
    ... 

    def __getattr__(self, name): 
     prefix = 'product_' 

     # Only deal with get_ calls 
     if not name.startswith(prefix): 
      raise AttributeError 
     else: 
      name = name.replace(prefix,'') 
      value = getattr(self.product, name, None) 
      if value: 
       return value 
      else: 
       raise AttributeError 

A continuación, puede llamar a:

cache.product_part_number 
1

que tenía un problema similar al publicado aquí y sólo recibió la respuesta cuando miré en la forma en que Python accede a los atributos de un objeto.

Como lo entiendo cuando se llama getattr() Python llama primero a getattribute(), si ese atributo no se encuentra por getattribute, entonces python usará su función getattr.

me trató de mantenerse lejos de usar getattr dentro de mi función, ya que causa cambios repetidos infinitas ver: https://stackoverflow.com/a/3278104/2319915

manera:

class Product(models.Model): 
    part_number = models.CharField(max_length=10) 


class Cache(models.Model): 
    product = models.ForeignKey(Product) 

    def __getattr__(self, name): 
     try: 
     return getattribute(self, name) 
     except AttributeError: 
     try: 
      return Product.objects.get(part_no=self.product.part_no) 
     except ObjectDoesNotExist: 
      raise AttributeError 
Cuestiones relacionadas