usar update_fields trigger signal example español como python django django-signals

python - update_fields - ¿Cómo usar la herencia del modelo Django con señales?



trigger django (7)

Esta solución resuelve el problema cuando no todos los módulos se importan en la 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

Tengo algunos niveles de herencia modelo en Django:

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

Hay muchos modelos diferentes heredados de WorkAttachmentFileBased y WorkAttachment . Quiero crear una señal, que actualice un campo attachment_count para el trabajo principal, cuando se crean los archivos adjuntos. Sería lógico pensar que la señal hecha para el remitente principal ( WorkAttachment ) también se ejecutará para todos los modelos heredados, pero no es así. Aquí está mi código:

@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()

¿Hay alguna manera de hacer que esta señal funcione para todos los modelos heredados de WorkAttachment ?

Python 2.7, Django 1.4 pre-alpha

PD. He probado una de las soluciones que encontré en la red , pero no funcionó para mí.


La solución de Michael Herrmann es definitivamente la manera más Django de hacer esto. Y sí, funciona para todas las subclases, ya que se cargan en la llamada lista ().

Me gustaría contribuir con las referencias de documentación:

En la práctica, los manejadores de señales generalmente se definen en un submódulo de señales de la aplicación a la que se refieren. Los receptores de señal están conectados en el método ready () de la clase de configuración de la aplicación. Si está utilizando el decorador de receptor (), simplemente importe el submódulo de señales dentro de listo ().

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

Y agrega una advertencia:

El método ready () se puede ejecutar más de una vez durante la prueba, por lo que es posible que desee proteger sus señales de la duplicación, especialmente si planea enviarlas dentro de las pruebas.

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

Por lo tanto, es posible que desee evitar las señales duplicadas con un parámetro dispatch_uid en la función de conexión.

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

En este contexto, haré:

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


La solución más simple es no restringir en el sender , sino controlar el manejador de señal si la instancia respectiva es una subclase:

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

Sin embargo, esto puede incurrir en una sobrecarga de rendimiento significativa ya que cada vez que se guarda un modelo, se llama a la función anterior.

Creo que he encontrado la forma más Django de hacer esto: las versiones recientes de Django sugieren conectar manejadores de señal en un archivo llamado signals.py . Aquí está el código de cableado necesario:

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)

Creo que esto funciona para todas las subclases, ya que todas se YourAppConfig.ready cuando se YourAppConfig.ready (y por lo tanto las signals se importen).


Podrías probar algo como:

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

(Descargo de responsabilidad: no he probado el anterior)


Puede registrar el controlador de conexión sin el sender especificado. Y filtra los modelos necesarios dentro de él.

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

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


También es posible usar tipos de contenido para descubrir subclases, suponiendo que tenga la clase base y las subclases empaquetadas en la misma aplicación. Algo como esto funcionaría:

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)


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)

¡que tengas un buen día!