Skip to content

API reference: Entity#

Entity and related models

The entity concept might feel a bit abstract, so it might be useful to reason about them using a concrete example (beneficiaries):

  • Entities are used to track beneficiaries (=people who will benefit from the help an organization provides). Those beneficiaries can be of different types (E.g.: Children under 5, Pregnant or lactating women, etc.).
  • Those beneficiaries are visited multiple times, so multiple submissions/instances (that we call "records") are attached to them via the entity_id foreign key of Instance.
  • In addition to those records, we also want to track some core metadata about the beneficiary, such as their name, age,... Because entities can be of very different natures, we avoid hardcoding those fields in the Entity model, and also reuse the form mechanism: each EntityType has a foreign key to a reference form, and each entity has a foreign key (attributes) to an instance/submission of that form.

Entity #

Bases: SoftDeletableModel

An entity represents a physical object or person with a known Entity Type

Contrary to forms, they are not linked to a specific OrgUnit. The core attributes that define this entity are not stored as fields in the Entity model, but in an Instance / submission

Source code in iaso/models/entity.py
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
class Entity(SoftDeletableModel):
    """An entity represents a physical object or person with a known Entity Type

    Contrary to forms, they are not linked to a specific OrgUnit.
    The core attributes that define this entity are not stored as fields in the Entity model, but in an Instance /
    submission
    """

    name = models.CharField(max_length=255, blank=True)  # this field is not used, name value is taken from attributes
    uuid = models.UUIDField(default=uuid.uuid4, editable=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    entity_type = models.ForeignKey(EntityType, blank=True, on_delete=models.PROTECT)
    attributes = models.OneToOneField(
        Instance, on_delete=models.PROTECT, help_text="instance", related_name="attributes", blank=True, null=True
    )
    account = models.ForeignKey(Account, on_delete=models.PROTECT)
    merged_to = models.ForeignKey("self", null=True, blank=True, on_delete=models.PROTECT)

    objects = DefaultSoftDeletableManager.from_queryset(EntityQuerySet)()

    objects_only_deleted = OnlyDeletedSoftDeletableManager.from_queryset(EntityQuerySet)()

    objects_include_deleted = IncludeDeletedSoftDeletableManager.from_queryset(EntityQuerySet)()

    class Meta:
        verbose_name_plural = "Entities"

    def __str__(self):
        return f"{self.name}"

    def as_small_dict(self):
        return {
            "id": self.pk,
            "uuid": self.uuid,
            "name": self.name,
            "created_at": self.created_at,
            "updated_at": self.updated_at,
            "entity_type": self.entity_type_id,
            "entity_type_name": self.entity_type and self.entity_type.name,
            "attributes": self.attributes and self.attributes.as_dict(),
        }

    def as_dict(self):
        instances = dict()

        for i in self.instances.all():
            instances["uuid"] = i.uuid
            instances["file_name"]: i.file_name
            instances[str(i.name)] = i.name

        return {
            "id": self.pk,
            "uuid": self.uuid,
            "created_at": self.created_at,
            "updated_at": self.updated_at,
            "entity_type": self.entity_type.as_dict(),
            "attributes": self.attributes.as_dict(),
            "instances": instances,
            "account": self.account.as_dict(),
        }

    def soft_delete_with_instances_and_pending_duplicates(self, audit_source, user):
        """
        This method does a proper soft-deletion of the entity:
        - soft delete the entity
        - soft delete its attached form instances
        - delete relevant pending EntityDuplicate pairs
        """
        from iaso.models.deduplication import ValidationStatus

        original = copy(self)
        self.delete()  # soft delete
        log_modification(original, self, audit_source, user=user)

        for instance in set([self.attributes] + list(self.instances.all())):
            original = copy(instance)
            instance.soft_delete()
            log_modification(original, instance, audit_source, user=user)

        self.duplicates1.filter(validation_status=ValidationStatus.PENDING).delete()
        self.duplicates2.filter(validation_status=ValidationStatus.PENDING).delete()

        return self

soft_delete_with_instances_and_pending_duplicates(audit_source, user) #

This method does a proper soft-deletion of the entity: - soft delete the entity - soft delete its attached form instances - delete relevant pending EntityDuplicate pairs

Source code in iaso/models/entity.py
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
def soft_delete_with_instances_and_pending_duplicates(self, audit_source, user):
    """
    This method does a proper soft-deletion of the entity:
    - soft delete the entity
    - soft delete its attached form instances
    - delete relevant pending EntityDuplicate pairs
    """
    from iaso.models.deduplication import ValidationStatus

    original = copy(self)
    self.delete()  # soft delete
    log_modification(original, self, audit_source, user=user)

    for instance in set([self.attributes] + list(self.instances.all())):
        original = copy(instance)
        instance.soft_delete()
        log_modification(original, instance, audit_source, user=user)

    self.duplicates1.filter(validation_status=ValidationStatus.PENDING).delete()
    self.duplicates2.filter(validation_status=ValidationStatus.PENDING).delete()

    return self

EntityType #

Bases: models.Model

Its reference_form describes the core attributes/metadata about the entity type (in case it refers to a person: name, age, ...)

Source code in iaso/models/entity.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
class EntityType(models.Model):
    """Its `reference_form` describes the core attributes/metadata about the entity type (in case it refers to a person: name, age, ...)"""

    name = models.CharField(max_length=255)  # Example: "Child under 5"
    code = models.CharField(
        max_length=255, null=True, blank=True
    )  # As the name could change over the time, this field will never change once the entity type created and ETL script will rely on that
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    # Link to the reference form that contains the core attribute/metadata specific to this entity type
    reference_form = models.ForeignKey(Form, blank=True, null=True, on_delete=models.PROTECT)
    account = models.ForeignKey(Account, on_delete=models.PROTECT, blank=True, null=True)
    is_active = models.BooleanField(default=False)
    # Fields (subset of the fields from the reference form) that will be shown in the UI - entity list view
    fields_list_view = ArrayField(
        models.CharField(max_length=255, blank=True, db_collation="case_insensitive"), size=100, null=True, blank=True
    )
    # Fields (subset of the fields from the reference form) that will be shown in the UI - entity detail view
    fields_detail_info_view = ArrayField(
        models.CharField(max_length=255, blank=True, db_collation="case_insensitive"), size=100, null=True, blank=True
    )
    # Fields (subset of the fields from the reference form) that will be used to search for duplicate entities
    fields_duplicate_search = ArrayField(
        models.CharField(max_length=255, blank=True, db_collation="case_insensitive"), size=100, null=True, blank=True
    )
    prevent_add_if_duplicate_found = models.BooleanField(
        default=False,
    )

    class Meta:
        unique_together = ["name", "account"]

    def __str__(self):
        return f"{self.name}"

    def as_dict(self):
        return {
            "name": self.name,
            "created_at": self.created_at,
            "updated_at": self.updated_at,
            "reference_form": self.reference_form.as_dict(),
            "account": self.account.as_dict(),
        }