From e3476f5bdbf95c20986eacd981bad0f447c119cb Mon Sep 17 00:00:00 2001
From: Robert Sparks <rjsparks@nostrum.com>
Date: Thu, 6 May 2021 16:03:32 +0000
Subject: [PATCH] wrap the repair and report utilities in management commands 
 - Legacy-Id: 18973

---
 .../find_meetingregistration_person_issues.py | 37 ++++++++++
 .../repair_meetingregistration_person.py      | 18 +++++
 ietf/stats/utils.py                           | 67 +++++++++++--------
 3 files changed, 93 insertions(+), 29 deletions(-)
 create mode 100644 ietf/stats/management/commands/find_meetingregistration_person_issues.py
 create mode 100644 ietf/stats/management/commands/repair_meetingregistration_person.py

diff --git a/ietf/stats/management/commands/find_meetingregistration_person_issues.py b/ietf/stats/management/commands/find_meetingregistration_person_issues.py
new file mode 100644
index 000000000..5dd6919e5
--- /dev/null
+++ b/ietf/stats/management/commands/find_meetingregistration_person_issues.py
@@ -0,0 +1,37 @@
+# Copyright The IETF Trust 2021, All Rights Reserved
+
+import debug # pyflakes:ignore
+
+from django.core.management.base import BaseCommand, CommandError
+
+from ietf.stats.utils import find_meetingregistration_person_issues
+
+class Command(BaseCommand):
+    help = "Find possible Person/Email objects to repair based on MeetingRegistration objects"
+
+    def add_arguments(self, parser):
+        parser.add_argument('--meeting',action='append')
+
+    def handle(self, *args, **options):
+        meetings = options['meeting'] or None
+        summary = find_meetingregistration_person_issues(meetings)
+
+        print(f'{summary.ok_records} records are OK')
+
+        for msg in summary.could_be_fixed:
+            print(msg)
+
+        for msg in summary.maybe_address:
+            print(msg)
+
+        for msg in summary.different_person:
+            print(msg)
+
+        for msg in summary.no_person:
+            print(msg)
+
+        for msg in summary.maybe_person:
+            print(msg)
+
+        for msg in summary.no_email:
+            print(msg)
diff --git a/ietf/stats/management/commands/repair_meetingregistration_person.py b/ietf/stats/management/commands/repair_meetingregistration_person.py
new file mode 100644
index 000000000..450054c09
--- /dev/null
+++ b/ietf/stats/management/commands/repair_meetingregistration_person.py
@@ -0,0 +1,18 @@
+# Copyright The IETF Trust 2021, All Rights Reserved
+
+import debug # pyflakes:ignore
+
+from django.core.management.base import BaseCommand, CommandError
+
+from ietf.stats.utils import repair_meetingregistration_person
+
+class Command(BaseCommand):
+    help = "Repair MeetingRegistration objects that have no person but an email matching a person"
+
+    def add_arguments(self, parser):
+        parser.add_argument('--meeting',action='append')
+
+    def handle(self, *args, **options):
+        meetings = options['meeting'] or None
+        repaired = repair_meetingregistration_person(meetings)
+        print(f'Repaired {repaired} MeetingRegistration objects')
\ No newline at end of file
diff --git a/ietf/stats/utils.py b/ietf/stats/utils.py
index e91a3d737..704e8a4ef 100644
--- a/ietf/stats/utils.py
+++ b/ietf/stats/utils.py
@@ -346,47 +346,56 @@ def get_meeting_registration_data(meeting):
         meeting.save()
     return num_created, num_processed, num_total
 
-# Might abandon this as too ambitious - if fixed things for 128 people in ietf 100 - 110 though, and that's probably worth doing.
-# Yeah - experiments say it _must_ be done. Probably need this as a management command.
 def repair_meetingregistration_person(meetings=None):
-    maybe_address = set()
-    different_person = set()
-    no_person = set()
-    maybe_person = set()
-    no_email = set()
-    ok_records = 0
-    skipped = 0
     repaired_records = 0
+    qs = MeetingRegistration.objects.all()
+    if meetings:
+        qs = qs.filter(meeting__number__in=meetings)
+    for mr in qs:
+        if mr.email and not mr.person:
+            email_person = Person.objects.filter(email__address=mr.email).first()
+            if email_person:
+                mr.person = email_person
+                mr.save()
+                repaired_records += 1            
+    return repaired_records
 
+class MeetingRegistrationIssuesSummary(object):
+    pass
 
-#    for mr in MeetingRegistration.objects.exclude(person__email__address=F('email')):
-    for mr in MeetingRegistration.objects.all():
-        if meetings and mr.meeting.number not in meetings:
-            skipped += 1
-            continue
+def find_meetingregistration_person_issues(meetings=None):
+    summary = MeetingRegistrationIssuesSummary()
+
+    summary.could_be_fixed = set()
+    summary.maybe_address = set()
+    summary.different_person = set()
+    summary.no_person = set()
+    summary.maybe_person = set()
+    summary.no_email = set()
+    summary.ok_records = 0
+
+    qs = MeetingRegistration.objects.all()
+    if meetings:
+        qs = qs.filter(meeting__number__in=meetings)
+    for mr in qs:
         if mr.person and mr.email and mr.email in mr.person.email_set.values_list('address',flat=True):
-            ok_records += 1
-            continue
-        if mr.email:
+            summary.ok_records += 1
+        elif mr.email:
             email_person = Person.objects.filter(email__address=mr.email).first()
             if mr.person:
                 if not email_person:
-                    maybe_address.add(f'{mr.email} is not present in any Email object. The MeetingRegistration object implies this is an address for {mr.person} ({mr.person.pk})')
+                    summary.maybe_address.add(f'{mr.email} is not present in any Email object. The MeetingRegistration object implies this is an address for {mr.person} ({mr.person.pk})')
                 elif email_person != mr.person:
-                    different_person.add(f'{mr} ({mr.pk}) has person {mr.person} ({mr.person.pk}) but an email {mr.email} attached to a different person {email_person} ({email_person.pk}).')
+                    summary.different_person.add(f'{mr} ({mr.pk}) has person {mr.person} ({mr.person.pk}) but an email {mr.email} attached to a different person {email_person} ({email_person.pk}).')
             elif email_person:
-                mr.person = email_person
-                mr.save()
-                repaired_records += 1
+                summary.could_be_fixed.add(f'{mr} ({mr.pk}) has no person, but email {mr.email} matches {email_person} ({email_person.pk})')
             else:
                 maybe_person_qs = Person.objects.filter(name__icontains=mr.last_name).filter(name__icontains=mr.first_name)
-#                if not maybe_person_qs.exists():
-#                    maybe_person_qs = Person.objects.filter(name__icontains=mr.last_name)
-                if maybe_person_qs:
-                    maybe_person.add(f'{mr} ({mr.pk}) has email address {mr.email} which cannot be associated with any Person. Consider these possible people {[(p,p.pk) for p in maybe_person_qs]}')
+                if maybe_person_qs.exists():
+                    summary.maybe_person.add(f'{mr} ({mr.pk}) has email address {mr.email} which cannot be associated with any Person. Consider these possible people {[(p,p.pk) for p in maybe_person_qs]}')
                 else:
-                    no_person.add(f'{mr} ({mr.pk}) has email address {mr.email} which cannot be associated with any Person')
+                    summary.no_person.add(f'{mr} ({mr.pk}) has email address {mr.email} which cannot be associated with any Person')
         else:
-            no_email.add(f'{mr} ({mr.pk}) provides no email address')
+            summary.no_email.add(f'{mr} ({mr.pk}) provides no email address')
 
-    return ok_records, repaired_records, skipped, maybe_address, different_person, maybe_person, no_person, no_email
+    return summary