summaryrefslogtreecommitdiff
path: root/src/mailman/utilities/importer.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/utilities/importer.py')
-rw-r--r--src/mailman/utilities/importer.py252
1 files changed, 250 insertions, 2 deletions
diff --git a/src/mailman/utilities/importer.py b/src/mailman/utilities/importer.py
index 3cc7259d7..50d2e7640 100644
--- a/src/mailman/utilities/importer.py
+++ b/src/mailman/utilities/importer.py
@@ -27,32 +27,103 @@ __all__ = [
import sys
import datetime
+import os
+from urllib2 import URLError
-from mailman.interfaces.action import FilterAction
+from mailman.config import config
+from mailman.interfaces.action import FilterAction, Action
from mailman.interfaces.autorespond import ResponseAction
from mailman.interfaces.digests import DigestFrequency
from mailman.interfaces.mailinglist import Personalization, ReplyToMunging
from mailman.interfaces.nntp import NewsgroupModeration
from mailman.interfaces.archiver import ArchivePolicy
+from mailman.interfaces.bans import IBanManager
+from mailman.interfaces.mailinglist import IAcceptableAliasSet
+from mailman.interfaces.bounce import UnrecognizedBounceDisposition
+from mailman.interfaces.usermanager import IUserManager
+from mailman.interfaces.member import DeliveryMode, DeliveryStatus, MemberRole
+from mailman.handlers.decorate import decorate, decorate_template
+from mailman.utilities.i18n import search
+from zope.component import getUtility
def seconds_to_delta(value):
return datetime.timedelta(seconds=value)
+
+def days_to_delta(value):
+ return datetime.timedelta(days=value)
+
+
+def list_members_to_unicode(value):
+ return [ unicode(item) for item in value ]
+
+
+def filter_action_mapping(value):
+ # The filter_action enum values have changed. In Mailman 2.1 the order was
+ # 'Discard', 'Reject', 'Forward to List Owner', 'Preserve'.
+ # In 3.0 it's 'hold', 'reject', 'discard', 'accept', 'defer', 'forward',
+ # 'preserve'
+ if value == 0:
+ return FilterAction.discard
+ elif value == 1:
+ return FilterAction.reject
+ elif value == 2:
+ return FilterAction.forward
+ elif value == 3:
+ return FilterAction.preserve
+ else:
+ raise ValueError("Unknown filter_action value: %s" % value)
+
+
+def member_action_mapping(value):
+ # The mlist.default_member_action and mlist.default_nonmember_action enum
+ # values are different in Mailman 2.1, because they have been merged into a
+ # single enum in Mailman 3
+ # For default_member_action, which used to be called
+ # member_moderation_action, the values were:
+ # 0==Hold, 1=Reject, 2==Discard
+ if value == 0:
+ return Action.hold
+ elif value == 1:
+ return Action.reject
+ elif value == 2:
+ return Action.discard
+def nonmember_action_mapping(value):
+ # For default_nonmember_action, which used to be called
+ # generic_nonmember_action, the values were:
+ # 0==Accept, 1==Hold, 2==Reject, 3==Discard
+ if value == 0:
+ return Action.accept
+ elif value == 1:
+ return Action.hold
+ elif value == 2:
+ return Action.reject
+ elif value == 3:
+ return Action.discard
+
# Attributes in Mailman 2 which have a different type in Mailman 3.
TYPES = dict(
autorespond_owner=ResponseAction,
autorespond_postings=ResponseAction,
autorespond_requests=ResponseAction,
+ autoresponse_grace_period=days_to_delta,
bounce_info_stale_after=seconds_to_delta,
bounce_you_are_disabled_warnings_interval=seconds_to_delta,
digest_volume_frequency=DigestFrequency,
- filter_action=FilterAction,
+ filter_action=filter_action_mapping,
newsgroup_moderation=NewsgroupModeration,
personalize=Personalization,
reply_goes_to_list=ReplyToMunging,
+ filter_types=list_members_to_unicode,
+ pass_types=list_members_to_unicode,
+ filter_extensions=list_members_to_unicode,
+ pass_extensions=list_members_to_unicode,
+ forward_unrecognized_bounces_to=UnrecognizedBounceDisposition,
+ default_member_action=member_action_mapping,
+ default_nonmember_action=nonmember_action_mapping,
)
@@ -61,6 +132,28 @@ NAME_MAPPINGS = dict(
host_name='mail_host',
include_list_post_header='allow_list_posts',
real_name='display_name',
+ last_post_time='last_post_at',
+ autoresponse_graceperiod='autoresponse_grace_period',
+ autorespond_admin='autorespond_owner',
+ autoresponse_admin_text='autoresponse_owner_text',
+ filter_mime_types='filter_types',
+ pass_mime_types='pass_types',
+ filter_filename_extensions='filter_extensions',
+ pass_filename_extensions='pass_extensions',
+ bounce_processing='process_bounces',
+ bounce_unrecognized_goes_to_list_owner='forward_unrecognized_bounces_to',
+ mod_password='moderator_password',
+ news_moderation='newsgroup_moderation',
+ news_prefix_subject_too='nntp_prefix_subject_too',
+ send_welcome_msg='send_welcome_message',
+ send_goodbye_msg='send_goodbye_message',
+ member_moderation_action='default_member_action',
+ generic_nonmember_action='default_nonmember_action',
+ )
+
+EXCLUDES = (
+ "members",
+ "digest_members",
)
@@ -74,6 +167,9 @@ def import_config_pck(mlist, config_dict):
:type config_dict: dict
"""
for key, value in config_dict.items():
+ # Some attributes must not be directly imported
+ if key in EXCLUDES:
+ continue
# Some attributes from Mailman 2 were renamed in Mailman 3.
key = NAME_MAPPINGS.get(key, key)
# Handle the simple case where the key is an attribute of the
@@ -99,3 +195,155 @@ def import_config_pck(mlist, config_dict):
mlist.archive_policy = ArchivePolicy.public
else:
mlist.archive_policy = ArchivePolicy.never
+ # Handle ban list
+ for addr in config_dict.get('ban_list', []):
+ IBanManager(mlist).ban(addr)
+ # Handle acceptable aliases
+ for addr in config_dict.get('acceptable_aliases', '').splitlines():
+ addr = addr.strip()
+ if not addr:
+ continue
+ IAcceptableAliasSet(mlist).add(addr)
+ # Handle conversion to URIs
+ convert_to_uri = {
+ "welcome_msg": "welcome_message_uri",
+ "goodbye_msg": "goodbye_message_uri",
+ "msg_header": "header_uri",
+ "msg_footer": "footer_uri",
+ "digest_header": "digest_header_uri",
+ "digest_footer": "digest_footer_uri",
+ }
+ convert_placeholders = { # only the most common ones
+ "%(real_name)s": "$display_name",
+ "%(real_name)s@%(host_name)s": "$fqdn_listname",
+ "%(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s": "$listinfo_uri",
+ }
+ for oldvar, newvar in convert_to_uri.iteritems():
+ if oldvar not in config_dict:
+ continue
+ text = config_dict[oldvar]
+ for oldph, newph in convert_placeholders.iteritems():
+ text = text.replace(oldph, newph)
+ default_value = getattr(mlist, newvar)
+ if not text and not default_value:
+ continue
+ # Check if the value changed from the default
+ try:
+ default_text = decorate(mlist, default_value)
+ expanded_text = decorate_template(mlist, text)
+ except (URLError, KeyError):
+ # Use case: importing the old a@ex.com into b@ex.com
+ # We can't check if it changed from the default
+ # -> don't import, we may do more harm than good and it's easy to
+ # change if needed
+ continue
+ if not text and not default_text:
+ continue # both are empty, leave it
+ if expanded_text.strip() == default_text.strip():
+ continue # keep the default
+ # Write the custom value to the right file
+ base_uri = "mailman:///$listname/$language/"
+ if default_value:
+ filename = default_value.rpartition("/")[2]
+ else:
+ filename = "%s.txt" % newvar[:-4]
+ if not default_value or not default_value.startswith(base_uri):
+ setattr(mlist, newvar, base_uri + filename)
+ filepath = list(search(filename, mlist))[0]
+ try:
+ os.makedirs(os.path.dirname(filepath))
+ except OSError, e:
+ if e.errno != 17: # Already exists
+ raise
+ with open(filepath, "w") as template:
+ template.write(text.encode('utf-8'))
+ # Import rosters
+ members = set(config_dict.get("members", {}).keys()
+ + config_dict.get("digest_members", {}).keys())
+ import_roster(mlist, config_dict, members, MemberRole.member)
+ import_roster(mlist, config_dict, config_dict.get("owner", []),
+ MemberRole.owner)
+ import_roster(mlist, config_dict, config_dict.get("moderator", []),
+ MemberRole.moderator)
+
+
+
+def import_roster(mlist, config_dict, members, role):
+ """
+ Import members lists from a config.pck configuration dictionary to a
+ mailing list.
+
+ :param mlist: The mailing list.
+ :type mlist: IMailingList
+ :param config_dict: The Mailman 2.1 configuration dictionary.
+ :type config_dict: dict
+ :param members: The members list to import
+ :type members: list
+ :param role: The MemberRole to import them as
+ :type role: MemberRole enum
+ """
+ usermanager = getUtility(IUserManager)
+ for email in members:
+ email = unicode(email)
+ roster = mlist.get_roster(role)
+ if roster.get_member(email) is not None:
+ print("%s is already imported with role %s" % (email, role),
+ file=sys.stderr)
+ continue
+ user = usermanager.get_user(email)
+ if user is None:
+ merged_members = {}
+ merged_members.update(config_dict.get("members", {}))
+ merged_members.update(config_dict.get("digest_members", {}))
+ if merged_members.get(email, 0) != 0:
+ original_email = merged_members[email]
+ else:
+ original_email = email
+ user = usermanager.create_user(original_email)
+ address = usermanager.get_address(email)
+ address.verified_on = datetime.datetime.now()
+ mlist.subscribe(address, role)
+ member = roster.get_member(email)
+ assert member is not None
+ prefs = config_dict.get("user_options", {}).get(email, 0)
+ if email in config_dict.get("members", {}):
+ member.preferences.delivery_mode = DeliveryMode.regular
+ elif email in config_dict.get("digest_members", {}):
+ if prefs & 8: # DisableMime
+ member.preferences.delivery_mode = DeliveryMode.plaintext_digests
+ else:
+ member.preferences.delivery_mode = DeliveryMode.mime_digests
+ else:
+ # probably not adding a member role here
+ pass
+ if email in config_dict.get("language", {}):
+ member.preferences.preferred_language = \
+ unicode(config_dict["language"][email])
+ # if the user already exists, display_name and password will be
+ # overwritten
+ if email in config_dict.get("usernames", {}):
+ address.display_name = unicode(config_dict["usernames"][email])
+ user.display_name = unicode(config_dict["usernames"][email])
+ if email in config_dict.get("passwords", {}):
+ user.password = config.password_context.encrypt(
+ config_dict["passwords"][email])
+ # delivery_status
+ oldds = config_dict.get("delivery_status", {}).get(email, (0, 0))[0]
+ if oldds == 0:
+ member.preferences.delivery_status = DeliveryStatus.enabled
+ elif oldds == 1:
+ member.preferences.delivery_status = DeliveryStatus.unknown
+ elif oldds == 2:
+ member.preferences.delivery_status = DeliveryStatus.by_user
+ elif oldds == 3:
+ member.preferences.delivery_status = DeliveryStatus.by_moderator
+ elif oldds == 4:
+ member.preferences.delivery_status = DeliveryStatus.by_bounces
+ # moderation
+ if prefs & 128:
+ member.moderation_action = Action.hold
+ # other preferences
+ member.preferences.acknowledge_posts = bool(prefs & 4) # AcknowledgePosts
+ member.preferences.hide_address = bool(prefs & 16) # ConcealSubscription
+ member.preferences.receive_own_postings = not bool(prefs & 2) # DontReceiveOwnPosts
+ member.preferences.receive_list_copy = not bool(prefs & 256) # DontReceiveDuplicates