Fix update_nickname runs on ever save of userprofile, fix nickname not updated after main's name changes

This commit is contained in:
ErikKalkoken 2020-06-28 21:38:25 +02:00
parent 64b72d0b06
commit 5c7478fa39
5 changed files with 93 additions and 93 deletions

View File

@ -68,7 +68,11 @@ class DiscordService(ServicesHook):
logger.debug('Syncing %s nickname for user %s', self.name, user) logger.debug('Syncing %s nickname for user %s', self.name, user)
if self.user_has_account(user): if self.user_has_account(user):
tasks.update_nickname.apply_async( tasks.update_nickname.apply_async(
kwargs={'user_pk': user.pk}, priority=SINGLE_TASK_PRIORITY kwargs={
'user_pk': user.pk,
'nickname': DiscordUser.objects.user_formatted_nick(user)
},
priority=SINGLE_TASK_PRIORITY
) )
def sync_nicknames_bulk(self, users: list): def sync_nicknames_bulk(self, users: list):

View File

@ -67,21 +67,25 @@ class DiscordUser(models.Model):
def __repr__(self): def __repr__(self):
return f'{type(self).__name__}(user=\'{self.user}\', uid={self.uid})' return f'{type(self).__name__}(user=\'{self.user}\', uid={self.uid})'
def update_nickname(self) -> bool: def update_nickname(self, nickname: str = None) -> bool:
"""Update nickname with formatted name of main character """Update nickname with formatted name of main character
Params:
- nickname: optional nickname to be used instead of user's main
Returns: Returns:
- True on success - True on success
- None if user is no longer a member of the Discord server - None if user is no longer a member of the Discord server
- False on error or raises exception - False on error or raises exception
""" """
requested_nick = DiscordUser.objects.user_formatted_nick(self.user) if not nickname:
if requested_nick: nickname = DiscordUser.objects.user_formatted_nick(self.user)
if nickname:
client = DiscordUser.objects._bot_client() client = DiscordUser.objects._bot_client()
success = client.modify_guild_member( success = client.modify_guild_member(
guild_id=DISCORD_GUILD_ID, guild_id=DISCORD_GUILD_ID,
user_id=self.uid, user_id=self.uid,
nick=requested_nick nick=nickname
) )
if success: if success:
logger.info('Nickname for %s has been updated', self.user) logger.info('Nickname for %s has been updated', self.user)

View File

@ -38,13 +38,14 @@ def update_groups(self, user_pk: int) -> None:
@shared_task( @shared_task(
bind=True, name='discord.update_nickname', base=QueueOnce, max_retries=None bind=True, name='discord.update_nickname', base=QueueOnce, max_retries=None
) )
def update_nickname(self, user_pk: int) -> None: def update_nickname(self, user_pk: int, nickname: str = None) -> None:
"""Set nickname on Discord for given user to his main character name """Set nickname on Discord for given user to his main character name
Params: Params:
- user_pk: PK of given user - user_pk: PK of given user
- nickname: optional nickname to be used instead of user's main
""" """
_task_perform_user_action(self, user_pk, 'update_nickname') _task_perform_user_action(self, user_pk, 'update_nickname', nickname=nickname)
@shared_task( @shared_task(

View File

@ -114,7 +114,7 @@ class TestServiceFeatures(TransactionTestCase):
"""All tests: Given a user with member state, service permission and active Discord account""" """All tests: Given a user with member state, service permission and active Discord account"""
clear_cache() clear_cache()
reset_testdata() reset_testdata()
self.group_3 = Group.objects.create(name='charlie') self.group_charlie = Group.objects.create(name='charlie')
# States # States
self.member_state = AuthUtils.get_member_state() self.member_state = AuthUtils.get_member_state()
@ -144,8 +144,9 @@ class TestServiceFeatures(TransactionTestCase):
self.discord_user = DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID) self.discord_user = DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
self.assertTrue(DiscordUser.objects.user_has_account(self.user)) self.assertTrue(DiscordUser.objects.user_has_account(self.user))
def test_name_of_main_changes(self, requests_mocker): def test_when_name_of_main_changes_then_discord_nick_is_updated(
# modify_guild_member() self, requests_mocker
):
requests_mocker.patch(modify_guild_member_request.url, status_code=204) requests_mocker.patch(modify_guild_member_request.url, status_code=204)
# changing nick to trigger signals # changing nick to trigger signals
@ -153,22 +154,23 @@ class TestServiceFeatures(TransactionTestCase):
self.user.profile.main_character.character_name = new_nick self.user.profile.main_character.character_name = new_nick
self.user.profile.main_character.save() self.user.profile.main_character.save()
# Need to have called modify_guild_member two times only # verify Discord nick was updates
# Once for sync nickname nick_updated = False
# Once for change of main character
requests_made = list()
for r in requests_mocker.request_history: for r in requests_mocker.request_history:
requests_made.append(DiscordRequest(r.method, r.url)) my_request = DiscordRequest(r.method, r.url)
if my_request == modify_guild_member_request and "nick" in r.json():
nick_updated = True
self.assertEqual(r.json()["nick"], new_nick)
expected = [modify_guild_member_request] self.assertTrue(nick_updated)
self.assertListEqual(requests_made, expected) self.assertTrue(DiscordUser.objects.user_has_account(self.user))
def test_name_of_main_changes_but_user_deleted(self, requests_mocker): def test_when_name_of_main_changes_and_user_deleted_then_account_is_deleted(
# modify_guild_member() self, requests_mocker
):
requests_mocker.patch( requests_mocker.patch(
modify_guild_member_request.url, status_code=404, json={'code': 10007} modify_guild_member_request.url, status_code=404, json={'code': 10007}
) )
# remove_guild_member()
requests_mocker.delete(remove_guild_member_request.url, status_code=204) requests_mocker.delete(remove_guild_member_request.url, status_code=204)
# changing nick to trigger signals # changing nick to trigger signals
@ -176,24 +178,11 @@ class TestServiceFeatures(TransactionTestCase):
self.user.profile.main_character.character_name = new_nick self.user.profile.main_character.character_name = new_nick
self.user.profile.main_character.save() self.user.profile.main_character.save()
# Need to have called modify_guild_member two times only self.assertFalse(DiscordUser.objects.user_has_account(self.user))
# Once for sync nickname
# Once for change of main character
requests_made = list()
for r in requests_mocker.request_history:
requests_made.append(DiscordRequest(r.method, r.url))
expected = [ def test_when_name_of_main_changes_and_and_rate_limited_then_dont_call_api(
modify_guild_member_request,
remove_guild_member_request,
]
self.assertListEqual(requests_made, expected)
# self.assertFalse(DiscordUser.objects.user_has_account(self.user))
def test_name_of_main_changes_but_user_rate_limited(
self, requests_mocker self, requests_mocker
): ):
# modify_guild_member()
requests_mocker.patch(modify_guild_member_request.url, status_code=204) requests_mocker.patch(modify_guild_member_request.url, status_code=204)
# exhausting rate limit # exhausting rate limit
@ -210,12 +199,12 @@ class TestServiceFeatures(TransactionTestCase):
self.user.profile.main_character.save() self.user.profile.main_character.save()
# should not have called the API # should not have called the API
requests_made = list() requests_made = [
for r in requests_mocker.request_history:
requests_made.append(DiscordRequest(r.method, r.url)) requests_made.append(DiscordRequest(r.method, r.url))
for r in requests_mocker.request_history
]
expected = list() self.assertListEqual(requests_made, list())
self.assertListEqual(requests_made, expected)
def test_when_member_is_demoted_to_guest_then_his_account_is_deleted( def test_when_member_is_demoted_to_guest_then_his_account_is_deleted(
self, requests_mocker self, requests_mocker
@ -269,79 +258,75 @@ class TestServiceFeatures(TransactionTestCase):
if my_request == modify_guild_member_request and "roles" in r.json(): if my_request == modify_guild_member_request and "roles" in r.json():
roles_updated = True roles_updated = True
self.assertSetEqual(set(r.json()["roles"]), {13, 98}) self.assertSetEqual(set(r.json()["roles"]), {13, 98})
break
self.assertTrue(roles_updated) self.assertTrue(roles_updated)
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
def test_adding_group_to_user_role_exists(self, requests_mocker): def test_when_group_added_to_member_and_role_known_then_his_roles_are_updated(
# guild_member() self, requests_mocker
):
requests_mocker.get( requests_mocker.get(
guild_member_request.url, guild_member_request.url,
json={ json={
'user': create_user_info(), 'user': create_user_info(),
'roles': ['1', '13', '99'] 'roles': ['13', '99']
} }
) )
# guild_roles()
requests_mocker.get( requests_mocker.get(
guild_roles_request.url, guild_roles_request.url,
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE, ROLE_MEMBER] json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE, ROLE_MEMBER]
) )
# create_guild_role()
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE) requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
# modify_guild_member()
requests_mocker.patch(modify_guild_member_request.url, status_code=204) requests_mocker.patch(modify_guild_member_request.url, status_code=204)
# adding new group to trigger signals # adding new group to trigger signals
self.user.groups.add(self.group_3) self.user.groups.add(self.group_charlie)
self.user.refresh_from_db()
# compare the list of made requests with expected # verify roles for user where updated
requests_made = list() roles_updated = False
for r in requests_mocker.request_history: for r in requests_mocker.request_history:
requests_made.append(DiscordRequest(r.method, r.url)) my_request = DiscordRequest(r.method, r.url)
if my_request == modify_guild_member_request and "roles" in r.json():
roles_updated = True
self.assertSetEqual(set(r.json()["roles"]), {3, 13, 99})
break
expected = [ self.assertTrue(roles_updated)
guild_member_request, self.assertTrue(DiscordUser.objects.user_has_account(self.user))
guild_roles_request,
modify_guild_member_request
]
self.assertListEqual(requests_made, expected)
def test_adding_group_to_user_role_does_not_exist(self, requests_mocker): def test_when_group_added_to_member_and_role_unknown_then_his_roles_are_updated(
# guild_member() self, requests_mocker
):
requests_mocker.get( requests_mocker.get(
guild_member_request.url, guild_member_request.url,
json={ json={
'user': {'id': str(TEST_USER_ID), 'username': TEST_MAIN_NAME}, 'user': {'id': str(TEST_USER_ID), 'username': TEST_MAIN_NAME},
'roles': ['1', '13', '99'] 'roles': ['13', '99']
} }
) )
# guild_roles()
requests_mocker.get( requests_mocker.get(
guild_roles_request.url, guild_roles_request.url,
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER] json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER]
) )
# create_guild_role()
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE) requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
# modify_guild_member()
requests_mocker.patch(modify_guild_member_request.url, status_code=204) requests_mocker.patch(modify_guild_member_request.url, status_code=204)
# adding new group to trigger signals # adding new group to trigger signals
self.user.groups.add(self.group_3) self.user.groups.add(self.group_charlie)
self.user.refresh_from_db() self.user.refresh_from_db()
# compare the list of made requests with expected # verify roles for user where updated
requests_made = list() roles_updated = False
for r in requests_mocker.request_history: for r in requests_mocker.request_history:
requests_made.append(DiscordRequest(r.method, r.url)) my_request = DiscordRequest(r.method, r.url)
if my_request == modify_guild_member_request and "roles" in r.json():
roles_updated = True
self.assertSetEqual(set(r.json()["roles"]), {3, 13, 99})
break
expected = [ self.assertTrue(roles_updated)
guild_member_request, self.assertTrue(DiscordUser.objects.user_has_account(self.user))
guild_roles_request,
create_guild_role_request,
modify_guild_member_request
]
self.assertListEqual(requests_made, expected)
@override_settings(CELERY_ALWAYS_EAGER=True) @override_settings(CELERY_ALWAYS_EAGER=True)
@ -373,7 +358,10 @@ class StateTestCase(TestCase):
self.user = User.objects.get(pk=self.user.pk) self.user = User.objects.get(pk=self.user.pk)
def test_perm_changes_to_higher_priority_state_creation(self, requests_mocker): def test_perm_changes_to_higher_priority_state_creation(self, requests_mocker):
mock_url = DiscordRequest(method='DELETE',url=f'{DISCORD_API_BASE_URL}guilds/{TEST_GUILD_ID}/members/12345678910') mock_url = DiscordRequest(
method='DELETE',
url=f'{DISCORD_API_BASE_URL}guilds/{TEST_GUILD_ID}/members/12345678910'
)
requests_mocker.delete(mock_url.url, status_code=204) requests_mocker.delete(mock_url.url, status_code=204)
self._add_discord_user() self._add_discord_user()
self._refresh_user() self._refresh_user()
@ -394,7 +382,10 @@ class StateTestCase(TestCase):
self.user.discord self.user.discord
def test_perm_changes_to_lower_priority_state_creation(self, requests_mocker): def test_perm_changes_to_lower_priority_state_creation(self, requests_mocker):
mock_url = DiscordRequest(method='DELETE',url=f'{DISCORD_API_BASE_URL}guilds/{TEST_GUILD_ID}/members/12345678910') mock_url = DiscordRequest(
method='DELETE',
url=f'{DISCORD_API_BASE_URL}guilds/{TEST_GUILD_ID}/members/12345678910'
)
requests_mocker.delete(mock_url.url, status_code=204) requests_mocker.delete(mock_url.url, status_code=204)
self._add_discord_user() self._add_discord_user()
self._refresh_user() self._refresh_user()

View File

@ -167,7 +167,7 @@ def process_main_character_change(sender, instance, *args, **kwargs):
if old_instance.main_character and not instance.main_character: # lost main char disable services if old_instance.main_character and not instance.main_character: # lost main char disable services
logger.info("Disabling services due to loss of main character for user {0}".format(instance.user)) logger.info("Disabling services due to loss of main character for user {0}".format(instance.user))
disable_user(instance.user) disable_user(instance.user)
elif old_instance.main_character is not instance.main_character: # swapping/changing main character elif old_instance.main_character != instance.main_character: # swapping/changing main character
logger.info("Updating Names due to change of main character for user {0}".format(instance.user)) logger.info("Updating Names due to change of main character for user {0}".format(instance.user))
for svc in ServicesHook.get_services(): for svc in ServicesHook.get_services():
try: try: