2011-10-17 18 views
33

Ho un paio di livelli modello di ereditarietà in Django:Come utilizzare l'ereditarietà del modello di Django con i segnali?

class WorkAttachment(models.Model): 
    """ Abstract class that holds all fields that are required in each attachment """ 
    work   = models.ForeignKey(Work) 
    added   = models.DateTimeField(default=datetime.datetime.now) 
    views   = models.IntegerField(default=0) 

    class Meta: 
     abstract = True 


class WorkAttachmentFileBased(WorkAttachment): 
    """ Another base class, but for file based attachments """ 
    description  = models.CharField(max_length=500, blank=True) 
    size   = models.IntegerField(verbose_name=_('size in bytes')) 

    class Meta: 
     abstract = True 


class WorkAttachmentPicture(WorkAttachmentFileBased): 
    """ Picture attached to work """ 
    image   = models.ImageField(upload_to='works/images', width_field='width', height_field='height') 
    width   = models.IntegerField() 
    height   = models.IntegerField() 

Ci sono molti modelli differenti ereditati da WorkAttachmentFileBased e WorkAttachment. Voglio creare un segnale, che aggiornerebbe un campo attachment_count per il lavoro principale, quando viene creato l'allegato. Sarebbe logico pensare che il segnale creato per il mittente genitore (WorkAttachment) verrebbe eseguito anche per tutti i modelli ereditati, ma non lo è. ci

@receiver(post_save, sender=WorkAttachment, dispatch_uid="att_post_save") 
def update_attachment_count_on_save(sender, instance, **kwargs): 
    """ Update file count for work when attachment was saved.""" 
    instance.work.attachment_count += 1 
    instance.work.save() 

è un modo per fare questo lavoro segnale per tutti i modelli ereditato da WorkAttachment: Ecco il mio codice?

Python 2.7, 1.4 Django pre-alpha

P.S. Ho provato one of the solutions I found on the net, ma non ha funzionato per me.

+2

collegamento Solution è rotto – Martin

+3

ho trovato [pagina soluzione in archivi web] (http: // web. archive.org/web/20120715042306/http://codeblogging.net/blogs/1/14). C'è uno svantaggio della soluzione: dovresti dichiarare il segnale dopo tutte le sottoclassi, altrimenti non le troverà. – HighCat

risposta

17

Si potrebbe provare qualcosa di simile:

model_classes = [WorkAttachment, WorkAttachmentFileBased, WorkAttachmentPicture, ...] 

def update_attachment_count_on_save(sender, instance, **kwargs): 
    instance.work.attachment_count += 1 
    instance.work.save() 

for model_class in model_classes: 
    post_save.connect(update_attachment_count_on_save, 
         sender=model_class, 
         dispatch_uid="att_post_save_"+model_class.__name__) 

(Disclaimer: non ho testato il sopra)

+4

Grazie per la risposta. Mi ha ispirato per la soluzione, che ho descritto qui: http://codeblogging.net/blogs/1/14/ –

+0

Felice di poterti aiutare, bel post sul blog. – codeape

+2

Proprio per quello che il post sul blog di @SilverLight non è più un link valido. – Fitblip

0

E 'anche possibile utilizzare i tipi di contenuto per scoprire sottoclassi - ammesso che abbiate la classe base e sottoclassi confezionato nella stessa app. Qualcosa di simile potrebbe funzionare:

from django.contrib.contenttypes.models import ContentType 
content_types = ContentType.objects.filter(app_label="your_app") 
for content_type in content_types: 
    model = content_type.model_class() 
    post_save.connect(update_attachment_count_on_save, sender=model) 
39

è possibile registrare il gestore connessione senza sender specificato. E filtra i modelli necessari al suo interno.

from django.db.models.signals import post_save 
from django.dispatch import receiver 


@receiver(post_save) 
def my_handler(sender, **kwargs): 
    # Returns false if 'sender' is NOT a subclass of AbstractModel 
    if not issubclass(sender, AbstractModel): 
     return 
    ... 

Rif: https://groups.google.com/d/msg/django-users/E_u9pHIkiI0/YgzA1p8XaSMJ

+0

Questa è un'ottima soluzione. – Mikle

+5

Questo funziona ma ha un leggero inconveniente nel fatto che la funzione ricevitore viene chiamata per ogni modello che chiama 'save()'. – dhobbs

+1

Sì, sembra che ciò comporterebbe un significativo sovraccarico delle prestazioni. –

2

Questa soluzione risolve il problema quando non tutti i moduli importati nella memoria.

def inherited_receiver(signal, sender, **kwargs): 
    """ 
    Decorator connect receivers and all receiver's subclasses to signals. 

     @inherited_receiver(post_save, sender=MyModel) 
     def signal_receiver(sender, **kwargs): 
      ... 

    """ 
    parent_cls = sender 

    def wrapper(func): 
     def childs_receiver(sender, **kw): 
      """ 
      the receiver detect that func will execute for child 
      (and same parent) classes only. 
      """ 
      child_cls = sender 
      if issubclass(child_cls, parent_cls): 
       func(sender=child_cls, **kw) 

     signal.connect(childs_receiver, **kwargs) 
     return childs_receiver 
    return wrapper 
+0

questo è costruito su http://stackoverflow.com/a/17173716/433570 e senza i difetti menzionati da SilverLight nella risposta accettata. – eugene

6
post_save.connect(my_handler, ParentClass) 
# connect all subclasses of base content item too 
for subclass in ParentClass.__subclasses__(): 
    post_save.connect(my_handler, subclass) 

hanno una bella giornata!

+1

Questa è la migliore risposta. –

+0

Devi assicurarti che questo venga eseguito dopo che tutte le sottoclassi possibili sono state definite altrimenti verranno saltate (anche se, non ho provato quella affermazione, penso che sia quello che succederebbe). –

19

La soluzione più semplice è quella di non limitare il sender, ma controlla nel gestore segnale se la rispettiva istanza è una sottoclasse:

@receiver(post_save) 
def update_attachment_count_on_save(sender, instance, **kwargs): 
    if isinstance(instance, WorkAttachment): 
     ... 

Tuttavia, questo può comportare una significativa riduzione delle prestazioni come ogni tempo qualsiasi modello viene salvato, viene chiamata la funzione precedente.

Penso di aver trovato il modo più Django per farlo: le versioni recenti di Django suggeriscono di connettere i gestori di segnale in un file chiamato signals.py. Ecco il codice di cablaggio necessario:

your_app/__ init__.py:

default_app_config = 'your_app.apps.YourAppConfig' 

your_app/apps.py:

import django.apps 

class YourAppConfig(django.apps.AppConfig): 
    name = 'your_app' 
    def ready(self): 
     import your_app.signals 

your_app/signals.py:

def get_subclasses(cls): 
    result = [cls] 
    classes_to_inspect = [cls] 
    while classes_to_inspect: 
     class_to_inspect = classes_to_inspect.pop() 
     for subclass in class_to_inspect.__subclasses__(): 
      if subclass not in result: 
       result.append(subclass) 
       classes_to_inspect.append(subclass) 
    return result 

def update_attachment_count_on_save(sender, instance, **kwargs): 
    instance.work.attachment_count += 1 
    instance.work.save() 

for subclass in get_subclasses(WorkAttachment): 
    post_save.connect(update_attachment_count_on_save, subclass) 

ho che questo funziona per tutte le sottoclassi, perché saranno tutti caricati dal tempo YourAppConfig.ready si chiama (e quindi signals è importato).

+0

Questa dovrebbe essere la risposta migliore – spg

+0

buona risposta. Si noti che '' 'result''' in' '' get_subclassed''' contiene la classe genitore, che corrisponde a questa domanda. Se la tua classe genitore è un modello astratto, vorrai che '' 'risultato''' sia inizialmente una lista vuota. – biodiv

3

La soluzione di Michael Herrmann è sicuramente il modo più Django per farlo. E sì, funziona per tutte le sottoclassi poiché vengono caricate nella chiamata ready().

desidero contribuire con i riferimenti documentazione:

In pratica, i gestori di segnale sono solitamente definiti in un modulo segnali della domanda si riferiscono. I ricevitori di segnale sono collegati nel metodo ready() della classe di configurazione dell'applicazione. Se stai usando il decoratore del ricevitore(), importa semplicemente il sottomodulo dei segnali all'interno di ready().

https://docs.djangoproject.com/en/dev/topics/signals/#connecting-receiver-functions

E aggiungere un avvertimento:

Il metodo pronto() può essere eseguito più di una volta durante i test, quindi si consiglia di proteggere i segnali da duplicazioni, soprattutto se si Stai pianificando di inviarli nei test.

https://docs.djangoproject.com/en/dev/topics/signals/#connecting-receiver-functions

Così si potrebbe desiderare di evitare che i segnali duplicati con un parametro dispatch_uid sulla funzione di collegamento.

post_save.connect(my_callback, dispatch_uid="my_unique_identifier") 

In questo contesto lo farò:

for subclass in get_subclasses(WorkAttachment): 
    post_save.connect(update_attachment_count_on_save, subclass, dispatch_uid=subclass.__name__) 

https://docs.djangoproject.com/en/dev/topics/signals/#preventing-duplicate-signals

Problemi correlati