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!