def datastream(cls): """ Class decorator to track model changes to datastream Usage @datastream class MyModel(Model): ... def iter_changed_datastream(self, changed_fields=None): yield <datastream name>, <object id> ... yield <datastream name>, <object id> """ if hasattr(cls, "iter_changed_datastream"): if is_document(cls): mongo_signals.post_save.connect(_on_document_change, sender=cls) mongo_signals.pre_delete.connect(_on_document_change, sender=cls) else: django_signals.post_save.connect(_on_model_change, sender=cls) django_signals.pre_delete.connect(_on_model_change, sender=cls) return cls
def model(cls, m_cls): """ Decorator to denote models with labels. Contains field validation and `effective_labels` generation. Usage: ``` @Label.model class MyModel(...) ``` Adds pre-save hook to check and process `.labels` fields. Raises ValueError if any of the labels is not exists. Target model may have `iter_effective_labels` method with following signature: ``` def iter_effective_labels(self) -> Iterable[List[str]] ``` which may yield a list of effective labels from related objects to form `effective_labels` field. :param m_cls: Target model class :return: """ def default_iter_effective_labels(instance) -> Iterable[List[str]]: yield instance.labels or [] def on_pre_save(sender, instance=None, document=None, *args, **kwargs): instance = instance or document # Clean up labels labels = Label.merge_labels( default_iter_effective_labels(instance)) instance.labels = labels # Build and clean up effective labels can_expose_label = getattr(sender, "can_expose_label", lambda x: True) labels_iter = getattr(sender, "iter_effective_labels", default_iter_effective_labels) instance.effective_labels = [ ll.name for ll in Label.objects.filter( name__in=Label.merge_labels(labels_iter(instance))) if can_expose_label(ll) ] # Validate all labels all_labels = set(instance.labels) | set(instance.effective_labels) can_set_label = getattr(sender, "can_set_label", lambda x: True) for label in Label.objects.filter(name__in=list(all_labels)): if not can_set_label(label): # Check can_set_label method raise ValueError(f"Invalid label: {label.name}") all_labels.discard(label.name) if all_labels: raise ValueError(f"Invalid labels: {', '.join(all_labels)}") # Install handlers if is_document(m_cls): from mongoengine import signals as mongo_signals mongo_signals.pre_save.connect(on_pre_save, sender=m_cls, weak=False) else: from django.db.models import signals as django_signals django_signals.pre_save.connect(on_pre_save, sender=m_cls, weak=False) return m_cls