Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IA-3728 Create change requests from differences between two pyramids (backend model) #1836

Open
wants to merge 57 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
c4ab23b
Fix `Dumper.dump_as_json()` for objects without a `wkt` attribute and…
kemar Nov 28, 2024
82f9d11
Include `geom` and `groups` in the JSON dump
kemar Dec 2, 2024
70441ce
Test that the full diff works as expected
kemar Dec 2, 2024
f6823e8
Test full diff
kemar Dec 3, 2024
b7b2fed
Update tests
kemar Dec 4, 2024
f570601
Add the `DataSourceSynchronization` model
kemar Dec 4, 2024
2af0980
Fix migrations
kemar Dec 5, 2024
09a4f12
Test `Dumper.as_json()` for a new org unit in DHIS2 that doesn't exis…
kemar Dec 6, 2024
b58991e
Fix flacky test
kemar Dec 6, 2024
d8f1006
Implement `DataSourceSynchronization.create_json_diff()`
kemar Dec 6, 2024
bbbc15f
Do not re-order params
kemar Dec 6, 2024
ae5fbf6
Rename fields
kemar Dec 9, 2024
0f75804
Store stats about the number of change requests to be created
kemar Dec 9, 2024
5963fb9
Rename `json_diff_config` to `diff_config` and fix it
kemar Dec 9, 2024
c9e4083
Fix tests
kemar Dec 10, 2024
276f660
Fix tests
kemar Dec 10, 2024
c7a965b
Fix tests
kemar Dec 10, 2024
51da7f1
Fix tests
kemar Dec 10, 2024
5caae01
Rename variables in tests to make it easier to understand
kemar Dec 11, 2024
360ad27
Bulk creation of change requests
kemar Dec 12, 2024
9cc360e
Keep track of the internal ID
kemar Dec 16, 2024
c0d20ae
Fix tests
kemar Dec 16, 2024
ea0dddb
Bulk update old and new groups (m2m)
kemar Dec 16, 2024
773b33a
Split `bulk_create_from_data_source_sync`
kemar Dec 16, 2024
17acea9
Handle change requests for new org units
kemar Dec 17, 2024
d655ce8
Fix migrations
kemar Dec 17, 2024
3184d5a
Fix tests
kemar Dec 18, 2024
8e3f4af
Bulk creation of org units
kemar Dec 18, 2024
6c2ea42
Improve readability
kemar Dec 18, 2024
e1e2026
Bulk creation of groups
kemar Dec 18, 2024
614f929
Improve group tests
kemar Dec 19, 2024
8f26d15
Ensure uniqueness of new groups
kemar Dec 19, 2024
3b9cc15
Ensure the synchronization works with geo fields
kemar Dec 19, 2024
9955427
Fix tests
kemar Dec 19, 2024
e1f7766
Fix group synchronization for existing org units
kemar Dec 20, 2024
abdd196
Fix `Differ.diff` for groups
kemar Dec 20, 2024
24df5b4
Fix tests
kemar Dec 20, 2024
cca988c
Handle groups for change requests related to new org units
kemar Dec 23, 2024
32c8610
Rename `DataSourceSynchronizer` and add tests
kemar Dec 23, 2024
e9fe960
Add logs
kemar Dec 23, 2024
7ad5991
Create groups in batch
kemar Dec 24, 2024
9f83e92
Renaming
kemar Dec 24, 2024
158c80d
Add `DataSourceVersionsSynchronization.clean_data_source_versions()`
kemar Dec 24, 2024
25d3df3
Add validation
kemar Dec 24, 2024
29f00e3
Split tests
kemar Dec 30, 2024
bcc4bd6
Rename module
kemar Dec 30, 2024
7d63b72
Use a serializer to serialize JSON
kemar Dec 30, 2024
b423237
Improve admin and store a smaller JSON diff
kemar Dec 30, 2024
9dedc8c
Add a comment
kemar Dec 30, 2024
2a6d078
Fix comment
kemar Dec 30, 2024
0637bb2
Use `json.dumps` instead of `JSONRenderer` to work with an `str` rath…
kemar Dec 31, 2024
7c294c0
Handle missing parents
kemar Jan 2, 2025
92379fc
Don't rely on the string representations of objects to keep track of …
kemar Jan 3, 2025
aaddba4
Add comments
kemar Jan 3, 2025
3a4c585
Add the `json_diff_task` field to `DataSourceVersionsSynchronization`
kemar Jan 6, 2025
26bf64e
Update admin
kemar Jan 6, 2025
276213a
Remove `DataSourceVersionsSynchronization.json_diff_task`
kemar Jan 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions iaso/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
AlgorithmRun,
BulkCreateUserCsvFile,
DataSource,
DataSourceVersionsSynchronization,
Device,
DeviceOwnership,
DevicePosition,
Expand Down Expand Up @@ -508,6 +509,7 @@ class TaskAdmin(admin.ModelAdmin):
list_filter = ("account", "status", "name")
readonly_fields = ("stacktrace", "created_at", "result")
formfield_overrides = {models.JSONField: {"widget": IasoJSONEditorWidget}}
search_fields = ("name",)

def result_message(self, task):
return task.result and task.result.get("message", "")
Expand All @@ -526,7 +528,7 @@ def get_queryset(self, request):
@admin_attr_decorator
class SourceVersionAdmin(admin.ModelAdmin):
readonly_fields = ("created_at",)
list_display = ["__str__", "data_source", "number", "created_at", "updated_at"]
list_display = ["id", "data_source", "number", "created_at", "updated_at"]
list_filter = ["data_source", "created_at", "updated_at"]
search_fields = ["data_source__name", "number", "description"]
autocomplete_fields = ["data_source"]
Expand Down Expand Up @@ -824,7 +826,7 @@ class EntityDuplicateAnalyzisAdmin(admin.ModelAdmin):
class OrgUnitChangeRequestAdmin(admin.ModelAdmin):
list_display = ("pk", "org_unit", "created_at", "status")
list_display_links = ("pk", "org_unit")
list_filter = ("status", "kind")
list_filter = ("status", "kind", "data_source_synchronization")
readonly_fields = (
"uuid",
"created_at",
Expand All @@ -849,6 +851,7 @@ class OrgUnitChangeRequestAdmin(admin.ModelAdmin):
"new_reference_instances",
"payment",
"potential_payment",
"data_source_synchronization",
)
fieldsets = (
(
Expand Down Expand Up @@ -898,6 +901,7 @@ class OrgUnitChangeRequestAdmin(admin.ModelAdmin):
"updated_at",
"updated_by",
"rejection_comment",
"data_source_synchronization",
)
},
),
Expand All @@ -919,7 +923,7 @@ class OrgUnitChangeRequestAdmin(admin.ModelAdmin):
)

def get_queryset(self, request):
return super().get_queryset(request).select_related("org_unit__org_unit_type")
return super().get_queryset(request).select_related("org_unit__org_unit_type", "data_source_synchronization")


@admin.register(Config)
Expand Down Expand Up @@ -1077,6 +1081,40 @@ class Media:
}


@admin.register(DataSourceVersionsSynchronization)
class DataSourceVersionsSynchronizationAdmin(admin.ModelAdmin):
list_display = (
"pk",
"name",
"account",
"created_by",
"count_create",
"count_update",
)
list_display_links = ("pk", "name")
autocomplete_fields = ("account", "created_by", "source_version_to_update", "source_version_to_compare_with")
readonly_fields = (
"json_diff",
"count_create",
"count_update",
"created_at",
"updated_at",
"sync_task",
)

def get_queryset(self, request):
kemar marked this conversation as resolved.
Show resolved Hide resolved
return (
super()
.get_queryset(request)
.select_related(
"source_version_to_update__data_source",
"source_version_to_compare_with__data_source",
"account",
"created_by",
)
)


admin.site.register(AccountFeatureFlag)
admin.site.register(Device)
admin.site.register(DeviceOwnership)
Expand Down
1 change: 1 addition & 0 deletions iaso/diffing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
from .differ import Differ
from .dumper import Dumper
from .exporter import Exporter
from .synchronizer import DataSourceVersionsSynchronizer, diffs_to_json
2 changes: 1 addition & 1 deletion iaso/diffing/comparisons.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def access(self, org_unit):
groups = []
for group in org_unit.groups.all():
if group.source_ref == self.group_ref:
groups.append({"id": group.source_ref, "name": group.name})
groups.append({"id": group.source_ref, "name": group.name, "iaso_id": group.pk})

return groups

Expand Down
16 changes: 9 additions & 7 deletions iaso/diffing/differ.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from django.db.models import Q

from iaso.models import OrgUnit, GroupSet, Group
from .comparisons import as_field_types, Diff, Comparison

Expand All @@ -19,7 +21,7 @@ def __init__(self, logger):
self.iaso_logger = logger

def load_pyramid(self, version, validation_status=None, top_org_unit=None, org_unit_types=None):
self.iaso_logger.info("loading pyramid ", version.data_source, version, top_org_unit, org_unit_types)
self.iaso_logger.info(f"loading pyramid {version.data_source} {version} {top_org_unit} {org_unit_types}")
queryset = (
OrgUnit.objects.prefetch_related("groups")
.prefetch_related("groups__group_sets")
Expand Down Expand Up @@ -62,11 +64,13 @@ def diff(
field_names.append("groupset:" + group_set.source_ref + ":" + group_set.name)
for group in group_set.groups.all():
groups_with_with_groupset.append(group.id)
for group in Group.objects.filter(source_version=version):
for group in Group.objects.filter(Q(source_version=version) | Q(source_version=version_ref)).distinct(
"source_ref"
):
if group.id not in groups_with_with_groupset and group.source_ref:
field_names.append("group:" + group.source_ref + ":" + group.name)

self.iaso_logger.info("will compare the following fields ", field_names)
self.iaso_logger.info(f"will compare the following fields {field_names}")
field_types = as_field_types(field_names)

orgunits_dhis2 = self.load_pyramid(
Expand All @@ -78,9 +82,7 @@ def diff(
top_org_unit=top_org_unit_ref,
org_unit_types=org_unit_types_ref,
)
self.iaso_logger.info(
"comparing ", version_ref, "(", len(orgunits_dhis2), ")", " and ", version, "(", len(orgunit_refs), ")"
)
self.iaso_logger.info(f"comparing {version_ref} ({len(orgunits_dhis2)}) and {version} ({len(orgunit_refs)})")
# speed how to index_by(&:source_ref)
diffs = []
index = 0
Expand Down Expand Up @@ -146,7 +148,7 @@ def compare_fields(self, orgunit_dhis2, orgunit_ref, field_types):
else:
status = "modified"

if dhis2_value is None and ref_value is not None:
if not dhis2_value and ref_value:
status = "new"
if not same and dhis2_value is not None and (ref_value is None or ref_value == []):
status = "deleted"
Expand Down
24 changes: 17 additions & 7 deletions iaso/diffing/dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import json

from django.contrib.gis.geos import GEOSGeometry
from django.core.serializers.json import DjangoJSONEncoder
from django.forms import model_to_dict

from iaso.management.commands.command_logger import CommandLogger

Expand All @@ -18,14 +20,19 @@ def color(status):
return CommandLogger.END


class ShapelyJsonEncoder(json.JSONEncoder):
def __init__(self, **kwargs):
super(ShapelyJsonEncoder, self).__init__(**kwargs)

class DiffJSONEncoder(DjangoJSONEncoder):
def default(self, obj):
if hasattr(obj, "as_dict"):
if obj.__class__.__name__ in ["Diff", "Comparison"]:
return obj.as_dict()
return obj.wkt
if obj.__class__.__name__ == "OrgUnit":
return model_to_dict(obj)
if obj.__class__.__name__ == "PathValue":
# See django_ltree.fields
# https://github.com/mariocesar/django-ltree/blob/154c7e/django_ltree/fields.py#L27-L28
return str(obj)
if obj.__class__.__name__ == "MultiPolygon":
return obj.wkt
return super().default(obj)


class Dumper:
Expand Down Expand Up @@ -60,8 +67,11 @@ def dump_stats(self, diffs):
self.iaso_logger.info(json.dumps(stats, indent=4))
return stats

def as_json(self, diffs):
return json.dumps(diffs, indent=4, cls=DiffJSONEncoder)

def dump_as_json(self, diffs):
self.iaso_logger.info(json.dumps(diffs, indent=4, cls=ShapelyJsonEncoder))
self.iaso_logger.info(self.as_json(diffs))

def dump_as_csv(self, diffs, fields, csv_file, number_of_parents=5):
res = []
Expand Down
Loading
Loading