From d98afd2c0f89327165d2e4b672a66c57035ebfe1 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Thu, 14 Oct 2021 14:17:58 -0400 Subject: [PATCH] Invoke auth handler when deleting a user via importer plus some misc. tweaks i needed to cleanup some user accounts --- rattail/auth.py | 38 ++++++++++++++++++++++++++++++++++++ rattail/importing/model.py | 9 +++++++++ rattail/importing/rattail.py | 10 ++++++---- rattail/people.py | 5 +++++ 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/rattail/auth.py b/rattail/auth.py index 57df0c84..b56d086e 100644 --- a/rattail/auth.py +++ b/rattail/auth.py @@ -28,6 +28,8 @@ See also :doc:`rattail-manual:base/handlers/other/auth`. from __future__ import unicode_literals, absolute_import +import sqlalchemy_continuum as continuum + from rattail.app import GenericHandler @@ -189,3 +191,39 @@ class AuthHandler(GenericHandler): include_guest=include_guest, include_authenticated=include_authenticated) return permission in perms + + def delete_user(self, user, **kwargs): + """ + Delete the given user account. Use with caution! As this + generally cannot be undone. + + Default behavior here is of course to delete the account, but + it also must try to "remove" the user association from various + places, in particular the continuum transactions table. + Please note that this will leave certain record versions as + appearing to be "without an author". + """ + session = self.app.get_session(user) + + # disassociate user from transactions + self.remove_user_from_continuum_transactions(user) + + # finally, delete the user outright + session.delete(user) + return True + + def remove_user_from_continuum_transactions(self, user): + """ + Remove the given user from all Continuum transactions. + """ + session = self.app.get_session(user) + model = self.model + + # remove the user from any continuum transactions + # nb. we can use "any" model class here, to obtain Transaction + Transaction = continuum.transaction_class(model.User) + transactions = session.query(Transaction)\ + .filter(Transaction.user_id == user.uuid)\ + .all() + for txn in transactions: + txn.user_id = None diff --git a/rattail/importing/model.py b/rattail/importing/model.py index 50bc74ae..0b876253 100644 --- a/rattail/importing/model.py +++ b/rattail/importing/model.py @@ -543,6 +543,15 @@ class UserImporter(ToRattail): auth.set_user_password(user, data['plain_password']) return user + def delete_object(self, user): + """ + Override this to invoke the auth handler for user deletion, + since it may have extra smarts. + """ + auth = self.app.get_auth_handler() + auth.delete_user(user) + return True + class AdminUserImporter(UserImporter): """ diff --git a/rattail/importing/rattail.py b/rattail/importing/rattail.py index e88eb18d..fcacd7b5 100644 --- a/rattail/importing/rattail.py +++ b/rattail/importing/rattail.py @@ -349,15 +349,17 @@ class GlobalRoleImporter(RoleImporter): for new_user in new_users: if new_user not in old_users: user = self.session.query(model.User).get(new_user) - user.roles.append(role) - changed = True + if user: + user.roles.append(role) + changed = True # remove some users for old_user in old_users: if old_user not in new_users: user = self.session.query(model.User).get(old_user) - user.roles.remove(role) - changed = True + if user: + user.roles.remove(role) + changed = True if changed: # also record a change to the role, for datasync. diff --git a/rattail/people.py b/rattail/people.py index 43bb38f3..c5eb091c 100644 --- a/rattail/people.py +++ b/rattail/people.py @@ -314,6 +314,7 @@ class PeopleHandler(GenericHandler): F('last_name'), F('display_name'), F('usernames', additive=True), + F('employee_uuid'), F('member_uuids', additive=True), ] @@ -328,6 +329,7 @@ class PeopleHandler(GenericHandler): 'last_name': person.last_name, 'display_name': person.display_name, 'usernames': [u.username for u in person.users], + 'employee_uuid': person.employee.uuid if person.employee else None, 'member_uuids': [m.uuid for m in person.members], } @@ -379,6 +381,9 @@ class PeopleHandler(GenericHandler): merge happen. :returns: String indicating reason not to merge, or ``None``. """ + if removing.employee and keeping.employee: + if removing.employee is not keeping.employee: + return "Cannot merge 2 people who are distinct employees" def perform_merge(self, removing, keeping, **kwargs): """