Skip to content
This repository has been archived by the owner on Jun 1, 2022. It is now read-only.

Tool for un-merging incorrectly merged locations #700

Open
simonw opened this issue Jun 28, 2021 · 3 comments
Open

Tool for un-merging incorrectly merged locations #700

simonw opened this issue Jun 28, 2021 · 3 comments
Labels
django-admin and tools Anything involving the /admin/ tools

Comments

@simonw
Copy link
Collaborator

simonw commented Jun 28, 2021

https://discord.com/channels/799147121357881364/824443781990187008/859157576117780520 - it turns out we have some locations that have been incorrectly merged and we need a tool to fix them.

@simonw simonw added the django-admin and tools Anything involving the /admin/ tools label Jun 28, 2021
@simonw
Copy link
Collaborator Author

simonw commented Jun 28, 2021

For the moment I'll build this as a non-API tool - it will live at https://vial.calltheshots.us/admin/unmerge-locations/ to mirror the existing https://vial.calltheshots.us/admin/merge-locations/ tool.

@simonw
Copy link
Collaborator Author

simonw commented Jun 28, 2021

Current merge locations tool implementation:

@login_required
@user_passes_test(lambda user: user.has_perm("core.merge_locations"))
def merge_locations_view(request):
if request.method == "POST":
winner, loser = get_winner_loser(request.POST)
if (
winner
and loser
and (winner.pk != loser.pk)
and not (winner.soft_deleted or loser.soft_deleted)
):
# Merge them
merge_locations(winner, loser, request.user)
messages.success(request, "Locations merged")
return HttpResponseRedirect(
f"/admin/core/location/{winner.pk}/change/#location-h2"
)
else:
winner, loser = get_winner_loser(request.GET)
return render(
request,
"admin/merge_locations.html",
{
"winner": winner,
"loser": loser,
},
)

Which calls this function:

def merge_locations(winner: Location, loser: Location, user: User):
with reversion.create_revision():
# Record details of prior state before merge for these things,
# because they will not be automatically captured in the
# reversion history for the Location records
winner_concordances = [str(c) for c in winner.concordances.all()]
loser_concordances = [str(c) for c in loser.concordances.all()]
details = {
"winner_report_ids": list(winner.reports.values_list("pk", flat=True)),
"loser_report_ids": list(loser.reports.values_list("pk", flat=True)),
"winner_matched_source_location_ids": list(
winner.matched_source_locations.values_list("pk", flat=True)
),
"loser_matched_source_location_ids": list(
loser.matched_source_locations.values_list("pk", flat=True)
),
"winner_concordances": winner_concordances,
"loser_concordances": loser_concordances,
}
loser.reports.update(location=winner.pk)
loser.matched_source_locations.update(matched_location=winner.pk)
loser.soft_deleted = True
loser.soft_deleted_because = "Merged into location {}".format(winner.public_id)
loser.duplicate_of = winner
loser.save()
# Copy concordances from loser to winner
missing_concordances = [
c for c in loser_concordances if c not in winner_concordances
]
for concordance in missing_concordances:
winner.concordances.add(ConcordanceIdentifier.for_idref(concordance))
CompletedLocationMerge.objects.create(
winner_location=winner,
loser_location=loser,
created_by=user,
details=details,
)
reversion.set_user(user)
reversion.set_comment(
"Merged locations, winner = {}, loser = {}".format(
winner.public_id, loser.public_id
)
)

The trickiest thing to unwind here is the reports and source locations that were re-pointed at the winner. Thankfully we record details of what happened here:

details = {
    "winner_report_ids": list(winner.reports.values_list("pk", flat=True)),
    "loser_report_ids": list(loser.reports.values_list("pk", flat=True)),
    "winner_matched_source_location_ids": list(
        winner.matched_source_locations.values_list("pk", flat=True)
    ),
    "loser_matched_source_location_ids": list(
        loser.matched_source_locations.values_list("pk", flat=True)
    ),
    "winner_concordances": winner_concordances,
    "loser_concordances": loser_concordances,
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
django-admin and tools Anything involving the /admin/ tools
Projects
None yet
Development

No branches or pull requests

1 participant