from collections import defaultdict from typing import Dict from zope import component from ..AbstractTransaction import AbstractTransaction from ceo_common.interfaces import ILDAPService, IConfig, IUser from ceo_common.errors import UserAlreadySubscribedError, UserNotSubscribedError from ceo_common.logger_factory import logger_factory logger = logger_factory(__name__) class UpdateMemberPositionsTransaction(AbstractTransaction): """Transaction to update the CSC's executive positions.""" operations = [ 'update_positions_ldap', 'update_exec_group_ldap', 'subscribe_to_mailing_lists', ] def __init__(self, positions_reversed: Dict[str, str]): # positions_reversed is position -> username super().__init__() self.ldap_srv = component.getUtility(ILDAPService) # Reverse the dict so it's easier to use (username -> positions) self.positions = defaultdict(list) for position, username in positions_reversed.items(): self.positions[username].append(position) # a cached Dict of the Users who need to be modified (username -> User) self.users: Dict[str, IUser] = {} # for rollback purposes self.old_positions = {} # username -> positions self.old_execs = [] def child_execute_iter(self): cfg = component.getUtility(IConfig) mailing_lists = cfg.get('auxiliary mailing lists_exec') # position -> username new_positions_reversed = {} # For returning result # retrieve User objects and cache them for username in self.positions: user = self.ldap_srv.get_user(username) self.users[user.uid] = user # Remove positions for old users for user in self.ldap_srv.get_users_with_positions(): if user.uid not in self.positions: self.positions[user.uid] = [] self.users[user.uid] = user # Update positions in LDAP for username, new_positions in self.positions.items(): user = self.users[username] old_positions = user.positions[:] user.set_positions(new_positions) self.old_positions[username] = old_positions for position in new_positions: new_positions_reversed[position] = username yield 'update_positions_ldap' # update exec group in LDAP exec_group = self.ldap_srv.get_group('exec') self.old_execs = exec_group.members[:] new_execs = [ username for username, new_positions in self.positions.items() if len(new_positions) > 0 ] exec_group.set_members(new_execs) yield 'update_exec_group_ldap' # Update mailing list subscriptions subscription_failed = False for username, new_positions in self.positions.items(): user = self.users[username] for mailing_list in mailing_lists: try: if len(new_positions) > 0: user.subscribe_to_mailing_list(mailing_list) else: user.unsubscribe_from_mailing_list(mailing_list) except (UserAlreadySubscribedError, UserNotSubscribedError): pass except Exception: logger.warning(f'Failed to update mailing list for {user.uid}') subscription_failed = True if subscription_failed: yield 'failed_to_subscribe_to_mailing_lists' else: yield 'subscribe_to_mailing_lists' self.finish(new_positions_reversed) def rollback(self): if 'update_exec_group_ldap' in self.finished_operations: exec_group = self.ldap_srv.get_group('exec') exec_group.set_members(self.old_execs) for username, positions in self.old_positions.items(): user = self.users[username] user.set_positions(positions)