2010-02-07 5 views
24

Dire che ho modelli:Il modo giusto per restituire un'istanza del modello proxy da un'istanza del modello di base in Django?

class Animal(models.Model): 
    type = models.CharField(max_length=255) 

class Dog(Animal): 
    def make_sound(self): 
     print "Woof!" 
    class Meta: 
     proxy = True 

class Cat(Animal): 
    def make_sound(self): 
     print "Meow!" 
    class Meta: 
     proxy = True 

Diciamo che voglio fare:

animals = Animal.objects.all() 
for animal in animals: 
    animal.make_sound() 

Voglio tornare una serie di trame e Meows. Chiaramente, potrei semplicemente definire un make_sound nel modello originale che si biforca basato su animal_type, ma poi ogni volta che aggiungo un nuovo tipo di animale (immaginiamo che siano in diverse app), dovrei entrare e modificare la funzione make_sound . Preferirei semplicemente definire i modelli proxy e fargli definire il comportamento da soli. Da quello che posso dire, non c'è modo di restituire istanze miste di Cat o Dog, ma ho pensato che potrei definire un metodo "get_proxy_model" nella classe principale che restituisce un modello di gatto o di cane.

Sicuramente si potrebbe fare questo, e passare qualcosa come la chiave primaria e quindi solo fare Cat.objects.get (pk = passato_in_primario_key). Ma vorrebbe dire fare una query in più per i dati che hai già, che sembra ridondante. C'è un modo per trasformare un animale in un'istanza di gatto o di cane in modo efficiente? Qual è il modo giusto per fare ciò che voglio raggiungere?

+0

È possibile applicare make_sound al modello Animal e aggiungere anche sound = models.charField(). – monkut

+2

Il mio esempio è molto semplice - quello che sto cercando di fare richiede un sacco di lavoro che dipende dal tipo e non può essere memorizzato con il modello. – sotangochips

risposta

1

È possibile realizzare i modelli Django polimorfici utilizzando l'approccio descritto here. Questo codice è nelle prime fasi di sviluppo, credo, ma vale la pena indagare.

4

l'unico modo noto al genere umano è di utilizzare la programmazione di Metaclass.

Ecco risposta breve:

from django.db.models.base import ModelBase 

class InheritanceMetaclass(ModelBase): 
    def __call__(cls, *args, **kwargs): 
     obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs) 
     return obj.get_object() 

class Animal(models.Model): 
    __metaclass__ = InheritanceMetaclass 
    type = models.CharField(max_length=255) 
    object_class = models.CharField(max_length=20) 

    def save(self, *args, **kwargs): 
     if not self.object_class: 
      self.object_class = self._meta.module_name 
     super(Animal, self).save(*args, **kwargs) 
    def get_object(self): 
     if not self.object_class or self._meta.module_name == self.object_class: 
      return self 
     else: 
      return getattr(self, self.object_class) 

class Dog(Animal): 
    def make_sound(self): 
     print "Woof!" 


class Cat(Animal): 
    def make_sound(self): 
     print "Meow!" 

e il risultato desiderato:

shell$ ./manage.py shell_plus 
From 'models' autoload: Animal, Dog, Cat 
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2 
Type "help", "copyright", "credits" or "license" for more information. 
(InteractiveConsole) 
>>> dog1=Dog(type="Ozzie").save() 
>>> cat1=Cat(type="Kitty").save() 
>>> dog2=Dog(type="Dozzie").save() 
>>> cat2=Cat(type="Kinnie").save() 
>>> Animal.objects.all() 
[<Dog: Dog object>, <Cat: Cat object>, <Dog: Dog object>, <Cat: Cat object>] 
>>> for a in Animal.objects.all(): 
... print a.type, a.make_sound() 
... 
Ozzie Woof! 
None 
Kitty Meow! 
None 
Dozzie Woof! 
None 
Kinnie Meow! 
None 
>>> 

Come funziona?

  1. Memorizzare le informazioni sulla classe nome dell'animale - usiamo object_class per quella
  2. Rimuovi "proxy" meta attributo - abbiamo bisogno di relazione inversa a Django (il cattivo lato di questo creiamo in più DB tavolo per ogni modello bambino e dei rifiuti, ha colpito DB per che, il lato buono possiamo aggiungere qualche bambino modello campi dipendenti)
  3. Personalizza save() per Animal per salvare la classe nome in object_class dell'oggetto che richiama il salvataggio.
  4. Il metodo get_object è necessario per referenziare attraverso la relazione inversa in Django al modello con nome memorizzato nella classe oggetto .
  5. fare questo .get_object() "fusione" automaticamente ogni volta animali è istanziare da ridefinendo metaclasse di Animal modello. Metaclass è qualcosa come un modello per una classe (proprio come una classe è un modello per un oggetto).

Maggiori informazioni su metaclasse in Python: http://www.ibm.com/developerworks/linux/library/l-pymeta.html

0

Questa risposta può essere schivare la domanda un po ', perché non utilizza modelli proxy. Tuttavia, poiché la questione si chiede, essa lascia una scrivere le seguenti (e senza dover aggiornare la classe Animal se vengono aggiunti nuovi tipi) -

animals = Animal.objects.all() 
for animal in animals: 
    animal.make_sound() 

Per evitare programmazione metaclasse, si potrebbe usare composition over inheritance. Per example--

class Animal(models.Model): 

    type = models.CharField(max_length=255) 

    @property 
    def type_instance(self): 
     """Return a Dog or Cat object, etc.""" 
     return globals()[self.type]() 

    def make_sound(self): 
     return self.type_instance.make_sound() 

class Dog(object): 
    def make_sound(self): 
     print "Woof!" 

class Cat(object): 
    def make_sound(self): 
     print "Meow!" 

Se i Dog e Cat classi hanno bisogno di accedere all'istanza Animal, si potrebbe anche modificare il metodo type_instance() sopra per passare cosa ha bisogno per il costruttore di classe (ad esempio self).

7

L'approccio proposto da metaclasse thedk è davvero un modo molto potente per andare, però, ho dovuto combinare con una risposta alla domanda here di avere la query restituisce un procura un'istanza modello. La versione semplificata del codice adattato all'esempio precedente sarebbe:

from django.db.models.base import ModelBase 

class InheritanceMetaclass(ModelBase): 
    def __call__(cls, *args, **kwargs): 
     obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs) 
     return obj.get_object() 

class Animal(models.Model): 
    __metaclass__ = InheritanceMetaclass 
    type = models.CharField(max_length=255) 
    object_class = models.CharField(max_length=20) 

    def save(self, *args, **kwargs): 
     if not self.object_class: 
      self.object_class = self._meta.module_name 
     super(Animal, self).save(*args, **kwargs) 

    def get_object(self): 
     if self.object_class in SUBCLASSES_OF_ANIMAL: 
      self.__class__ = SUBCLASSES_OF_ANIMAL[self.object_class] 
     return self 

class Dog(Animal): 
    class Meta: 
     proxy = True 
    def make_sound(self): 
     print "Woof!" 


class Cat(Animal): 
    class Meta: 
     proxy = True 
    def make_sound(self): 
     print "Meow!" 


SUBCLASSES_OF_ANIMAL = dict([(cls.__name__, cls) for cls in ANIMAL.__subclasses__()]) 

Il vantaggio di questo approccio è che procura nessuna migrazione db è richiesto al momento della creazione di nuove sottoclassi. Lo svantaggio è che non è possibile aggiungere campi specifici alle sottoclassi.

Sarei felice di avere un feedback su questo approccio.

+0

Grande lavoro, mi chiedo se questo si oppone ai test di vita reale. – eugene

+1

Penso che sarebbe opportuno spostare la registrazione SUBCLASSES_OF_ANIMAL in metaclass '__init__' – eugene