1# Copyright (C) 2018-2020 by the Free Software Foundation, Inc. 2# 3# This file is part of GNU Mailman. 4# 5# GNU Mailman is free software: you can redistribute it and/or modify it under 6# the terms of the GNU General Public License as published by the Free 7# Software Foundation, either version 3 of the License, or (at your option) 8# any later version. 9# 10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT 11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 13# more details. 14# 15# You should have received a copy of the GNU General Public License along with 16# GNU Mailman. If not, see <https://www.gnu.org/licenses/>. 17 18"""Command line fixes for some bugs.""" 19 20from mailman.config import config 21from mailman.model.address import Address 22from mailman.model.member import Member 23from public import public 24from sqlalchemy import func 25 26 27@public 28def remove_duplicate_addresses(): 29 """ 30 Remove duplicate Address records from the database and fix their 31 subscriptions so that a User doesn't loose their subscriptions. 32 33 This iterates over all the Address records, creates a list of Email 34 addresses that are duplicate. It then creates a list of memberships 35 for all those emails and subscribes a single address on all memberships. 36 37 It then deletes all but the first email address. 38 """ 39 # Get all the duplicated entries. 40 dup_addresses = config.db.store.query( 41 Address.email).group_by( 42 Address.email).having( 43 func.count(Address.email) > 1).all() 44 # Iterate over all the duplicate entries and check which one has 45 # subscriptions attached with it, then delete the other one. 46 for email in dup_addresses: 47 all_objs = config.db.store.query( 48 Address).filter( 49 Address.email == email.email).all() 50 # Due to #476, these duplicate records are created when a User tries to 51 # link an already existing Address. So, we get all the memberships 52 # linked to all duplicate addresses and subscribe one of the addresses 53 # to all memberships and just delete rest of the addresses 54 all_memberships = [] 55 for address in all_objs: 56 results = config.db.store.query( 57 Member).filter(Member._address == address).all() 58 if len(results): 59 all_memberships.extend(results) 60 # Delete all except first email object. 61 right_address = all_objs[0] 62 for address in all_objs[1:]: 63 config.db.store.delete(address) 64 # Update memberships. 65 for membership in all_memberships: 66 membership.address_id = right_address.id 67 config.db.store.commit() 68