Python program to create lists and import settings from Mailman 2 to Mailman 3
Hi friends. A couple of weeks ago I asked for help with calling Mailman functions from within a Python program. This was to avoid using the web interface or shell commands to create a new list from a legacy Mailman 2.x list, and instead do it from within a Python program.
Here is the program I ended up using, and I thought it would be worthwhile to get into the Mailman users list archives for future folks. Note that this is for the (recommended) venv installation of Mailman. Some things might be different for other installations.
I'll paste the program and also attach it:
#!/opt/mailman/venv/bin/python3
# Note: The line above should be the Python3 interpreter for your # installed version of Mailman3. It's the same as the first line in # your 'mailman' command, which might be named manage.py or something # else in your environment.
# The venv version of Mailman used to develop this program is: # GNU Mailman 3.3.9 (Tom Sawyer) # Python 3.12.3 (main, Apr 10 2024, 05:33:47) [GCC 13.2.0]
# About: # The multiple steps of creating a Mailman 3 list based on a legacy # Mailman 2.1 list.
# gbn July 28 2024. This little program is granted to the public domain.
######################################################################## # Adjust these variables for your list, domain, MM2 and MM3 locations: lists_to_import = [ # listname, domainname ('testlist','example.org'), ('testlist2','example.org') # Add more lists as needed ]
######################################################################## # Load prerequisite functionality: from mailman.config import config # Needed for transaction.commit from mailman.core.initialize import initialize # For initialize() from mailman.app.lifecycle import create_list # For create_list import pickle # Pickle file handling from contextlib import ExitStack # Pickle file handling from mailman.commands.cli_import import _Mailman, _Bouncer # "" from mailman.utilities.modules import hacked_sys_modules # "" import subprocess # Subprocess for import21
######################################################################## # Step 0: Initialize the environment: # Without arguments, initialize() looks in the standard locations for your .cfg def do_init(): initialize() # Alternative: Load an explicit configuration file, such as: # initialize('/etc/mailman3/mailman.cfg')
######################################################################## # Step 1: Create the list # Note: This fails silently if the list already exists or something goes wrong def do_create(new_list): try: # The list might exist or otherwise not be creatable l = create_list(new_list) except: # I did not find useful return values from create_list to diagnose print('create_list('+new_list+') failed. Trying to proceed anyway')
# This saves your new list:
transaction = config.db
transaction.commit()
######################################################################## # Step 2: Rewrite the pickle file from the legacy list. def do_pickle(old_pickle_file, new_pickle_file):
# These definitions are so the old pickle file can be parsed:
with ExitStack() as resources:
resources.enter_context(hacked_sys_modules('Mailman', _Mailman))
resources.enter_context(
hacked_sys_modules('Mailman.Bouncer', _Bouncer))
# Input the old pickle file from MM 2.1
with open(old_pickle_file, 'rb') as fp:
data = pickle.load(fp)
# ban_list was a problematic value from my MM2.1 lists:
# print(data['ban_list'])
data['ban_list'] = []
# Any other settings you want to change? Add them here. These
# variables may be seen with MM2's 'config_list' command
data['require_explicit_destination']=1 # yes
data['member_moderation_action']=1 # reject
data['generic_nonmember_action']=2 # reject
data['max_num_recipients']=2 # block cc/bcc to list
fp.seek(0) # probably not needed
fp.close() # ditto
# Output: Rewrite the file in the current MM3 format
with open(new_pickle_file,'wb') as ofp:
pickle.dump(data, ofp)
ofp.close()
######################################################################## # Step 3: Import the legacy settings to the newly created list def do_import(new_list, new_pickle_file):
# I did not see how to call import21 directly from Python, so am using
# a subprocess which basically uses the CLI syntax.
#
# CLI syntax: mailman import21 [OPTIONS] LISTSPEC PICKLE_FILE
def import_list(listname, pickle_file):
command = [
'mailman', 'import21',
listname, pickle_file
]
subprocess.run(command, check=True)
# print(f"List {listname} imported successfully.")
# Import the list via a subprocess:
import_list(new_list, new_pickle_file)
# This saves your changes:
transaction = config.db
transaction.commit()
######################################################################## # Main process: do_init()
# Loop for all lists we want to create & import: for new_listname, new_domainname in lists_to_import: new_list = new_listname+'@'+new_domainname # Adjust for wherever your old MM2 lists are: old_pickle_file = '/var/lib/mailman/lists/'+new_listname+'/config.pck'
print('Starting list '+new_list)
# The new pickle_file doesn't need to be saved after this program exits
new_pickle_file = '/opt/mailman/'+new_listname+'-new.pck'
do_create(new_list)
do_pickle(old_pickle_file, new_pickle_file)
do_import(new_list, new_pickle_file)
print('Done with list '+new_list)
# Don't forget to import and then index your list for full-text # searching. We're not doing that here because it takes awhile # (depending on the size of the archive). Use a command from the bash # shell such as: # mailman-web hyperkitty_import --since=1971-01-01 -l listname@domainnamed /var/lib/mailman/archives/private/listname.mbox/listname.mbox # followed by: # mailman-web update_index_one_list listname@domainname
Mailman's content filtering has removed the following MIME parts from this message.
Replaced multipart/alternative part with first alternative.
On 8/9/24 15:52, Greg Newby wrote:
######################################################################## # Step 3: Import the legacy settings to the newly created list def do_import(new_list, new_pickle_file):
# I did not see how to call import21 directly from Python, so am using # a subprocess which basically uses the CLI syntax.
from mailman.utilities.importer import import_config_pck
import_config_pck(l, data)
# l is from the result of l = create_list(new_list) above
# data is the massaged dictionary from above
# No need to save it in a new pickle and subprocess `mailman import21`
-- Mark Sapiro <mark@msapiro.net> The highway is for gamblers, San Francisco Bay Area, California better use your sense - B. Dylan
participants (2)
-
Greg Newby
-
Mark Sapiro