Skip to content

Commit ce12778

Browse files
committed
Improve the Blackboard LTI 1.3 upgrade management command
In the case that it's run after some users have already launched upgraded LTI 1.3 links and so have new user accounts, it can merge them with the original user accounts created by the LTI 1.1 consumer. It takes the ID of the LTI 1.3 consumer as a required argument, to use in the LTI_13_UserAlias objects.
1 parent 7710722 commit ce12778

File tree

1 file changed

+32
-6
lines changed

1 file changed

+32
-6
lines changed

numbas_lti/management/commands/upgrade_blackboard_lti_13.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
However, Blackboard seems to give the same user IDs under both protocols, so we can create LTI_13_UserAlias objects based on the LTI_11_UserAlias objects.
99
"""
1010

11-
from numbas_lti.models import LTI_11_UserAlias, LTI_13_UserAlias
11+
from numbas_lti.models import LTI_11_UserAlias, LTI_13_UserAlias, LTIConsumer, User, Attempt
1212

1313

1414
from django.conf import settings
@@ -18,7 +18,9 @@ class Command(BaseCommand):
1818
help = 'Migrate LTI user aliases from LTI 1.1 to LTI 1.3. This should only be necessary for Blackboard connections.'
1919

2020
def add_arguments(self, parser):
21-
parser.add_argument('--consumer', help='Key of the consumer to upgrade. If not given, data for all LTI 1.1 consumers is upgraded.')
21+
parser.add_argument('to_consumer', help='ID of the LTI 1.3 consumer to upgrade to.')
22+
parser.add_argument('--merge-accounts', action='store_true', help='Merge any user accounts created through LTI 1.3 with accounts created through LTI 1.1.')
23+
parser.add_argument('--consumer', type=int, help='Key of the consumer to upgrade. If not given, data for all LTI 1.1 consumers is upgraded.')
2224

2325
def handle(self, *args, **options):
2426
lti_11_aliases = LTI_11_UserAlias.objects.all()
@@ -27,23 +29,47 @@ def handle(self, *args, **options):
2729
if consumer_key:
2830
lti_11_aliases = lti_11_aliases.filter(consumer__lti_11__key=consumer_key)
2931

32+
lti_13_consumer = LTIConsumer.objects.get(pk=options['to_consumer'])
33+
print(f"Upgrading to consumer {lti_13_consumer}")
34+
if options['merge_accounts']:
35+
users_to_merge = User.objects.filter(lti_13_aliases__consumer=lti_13_consumer, lti_11_aliases=None).filter(lti_13_aliases__sub__in=lti_11_aliases.values('consumer_user_id'))
36+
for u in users_to_merge:
37+
ou = User.objects.filter(lti_11_aliases__consumer_user_id__in=u.lti_13_aliases.values('sub')).first()
38+
self.merge_user(new_user=u, old_user=ou)
39+
3040
num_upgraded = 0
3141
for ua in lti_11_aliases:
3242
u = ua.user
3343
try:
34-
LTI_13_UserAlias.objects.get_or_create(
35-
consumer=ua.consumer,
44+
obj, created = LTI_13_UserAlias.objects.get_or_create(
45+
consumer=lti_13_consumer,
3646
user=u,
3747
defaults={
38-
'sub':ua.consumer_user_id,
48+
'sub': ua.consumer_user_id,
3949
'full_name': u.get_full_name(),
4050
'given_name': u.first_name,
4151
'family_name': u.last_name,
4252
'email': u.email
4353
}
4454
)
45-
num_upgraded += 1
55+
num_upgraded += 1 if created else 0
4656
except LTI_13_UserAlias.MultipleObjectsReturned:
4757
pass
4858

4959
print(f"Migrated {num_upgraded} user aliases.")
60+
61+
def merge_user(self, new_user, old_user):
62+
print(f"Merging {new_user.get_full_name()} ({new_user.pk}) with {old_user.get_full_name()} ({old_user.pk})")
63+
fields = [f for f in User._meta.get_fields() if f.auto_created and not f.concrete and not hasattr(f, 'through')]
64+
for f in fields:
65+
objects = f.related_model.objects.filter(**{f.field.name: new_user.pk})
66+
for o in objects:
67+
setattr(o, f.field.name, old_user)
68+
f.related_model.objects.bulk_update(objects, [f.field.name])
69+
70+
deleted_attempts = Attempt.objects.deleted().filter(user=new_user)
71+
for a in deleted_attempts:
72+
a.user = old_user
73+
Attempt.objects.deleted().bulk_update(deleted_attempts, ['user'])
74+
75+
new_user.delete()

0 commit comments

Comments
 (0)