summaryrefslogtreecommitdiff
path: root/src/mailman/commands/cli_lists.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/commands/cli_lists.py')
-rw-r--r--src/mailman/commands/cli_lists.py407
1 files changed, 189 insertions, 218 deletions
diff --git a/src/mailman/commands/cli_lists.py b/src/mailman/commands/cli_lists.py
index fdffce16d..aa1b49b32 100644
--- a/src/mailman/commands/cli_lists.py
+++ b/src/mailman/commands/cli_lists.py
@@ -17,10 +17,13 @@
"""The 'lists' subcommand."""
+import sys
+import click
+
from mailman.app.lifecycle import create_list, remove_list
from mailman.core.constants import system_preferences
from mailman.core.i18n import _
-from mailman.database.transaction import transaction, transactional
+from mailman.database.transaction import transaction
from mailman.email.message import UserNotification
from mailman.interfaces.address import (
IEmailValidator, InvalidEmailAddressError)
@@ -30,7 +33,9 @@ from mailman.interfaces.domain import (
from mailman.interfaces.languages import ILanguageManager
from mailman.interfaces.listmanager import IListManager, ListAlreadyExistsError
from mailman.interfaces.template import ITemplateLoader
+from mailman.utilities.options import I18nCommand
from mailman.utilities.string import expand, wrap
+from operator import attrgetter
from public import public
from zope.component import getUtility
from zope.interface import implementer
@@ -39,240 +44,206 @@ from zope.interface import implementer
COMMASPACE = ', '
+@click.command(
+ cls=I18nCommand,
+ help=_('List all mailing lists.'))
+@click.option(
+ '--advertised', '-a',
+ is_flag=True, default=False,
+ help=_('List only those mailing lists that are publicly advertised'))
+@click.option(
+ '--names/--no-names', '-n/-N',
+ is_flag=True, default=False,
+ help=_('Show also the list names'))
+@click.option(
+ '--descriptions/--no-descriptions', '-d/-D',
+ is_flag=True, default=False,
+ help=_('Show also the list descriptions'))
+@click.option(
+ '--quiet', '-q',
+ is_flag=True, default=False,
+ help=_('Less verbosity'))
+@click.option(
+ '--domain', 'domains',
+ multiple=True, metavar='DOMAIN',
+ help=_("""\
+ List only those mailing lists hosted on the given domain, which
+ must be the email host name. Multiple -d options may be given.
+ """))
+@click.pass_context
+def lists(ctx, advertised, names, descriptions, quiet, domains):
+ mailing_lists = set()
+ list_manager = getUtility(IListManager)
+ # Gather the matching mailing lists.
+ for mlist in list_manager.mailing_lists:
+ if advertised and not mlist.advertised:
+ continue
+ if len(domains) > 0 and mlist.mail_host not in domains:
+ continue
+ mailing_lists.add(mlist)
+ # Maybe no mailing lists matched.
+ if len(mailing_lists) == 0:
+ if not quiet:
+ print(_('No matching mailing lists found'))
+ ctx.exit()
+ count = len(mailing_lists) # noqa: F841
+ if not quiet:
+ print(_('$count matching mailing lists found:'))
+ # Calculate the longest identifier.
+ longest = 0
+ output = []
+ for mlist in sorted(mailing_lists, key=attrgetter('list_id')):
+ if names:
+ identifier = '{} [{}]'.format(
+ mlist.fqdn_listname, mlist.display_name)
+ else:
+ identifier = mlist.fqdn_listname
+ longest = max(len(identifier), longest)
+ output.append((identifier, mlist.description))
+ # Print it out.
+ if descriptions:
+ format_string = '{0:{2}} - {1:{3}}'
+ else:
+ format_string = '{0:{2}}'
+ for identifier, description in output:
+ print(format_string.format(
+ identifier, description, longest, 70 - longest))
+
+
@public
@implementer(ICLISubCommand)
class Lists:
- """List all mailing lists"""
-
name = 'lists'
+ command = lists
- def add(self, parser, command_parser):
- """See `ICLISubCommand`."""
- command_parser.add_argument(
- '-a', '--advertised',
- default=False, action='store_true',
- help=_(
- 'List only those mailing lists that are publicly advertised'))
- command_parser.add_argument(
- '-n', '--names',
- default=False, action='store_true',
- help=_('Show also the list names'))
- command_parser.add_argument(
- '-d', '--descriptions',
- default=False, action='store_true',
- help=_('Show also the list descriptions'))
- command_parser.add_argument(
- '-q', '--quiet',
- default=False, action='store_true',
- help=_('Less verbosity'))
- command_parser.add_argument(
- '--domain',
- action='append', help=_("""\
- List only those mailing lists hosted on the given domain, which
- must be the email host name. Multiple -d options may be given.
- """))
- def process(self, args):
- """See `ICLISubCommand`."""
- mailing_lists = []
- list_manager = getUtility(IListManager)
- # Gather the matching mailing lists.
- for fqdn_name in sorted(list_manager.names):
- mlist = list_manager.get(fqdn_name)
- if args.advertised and not mlist.advertised:
- continue
- domains = getattr(args, 'domain', None)
- if domains and mlist.mail_host not in domains:
- continue
- mailing_lists.append(mlist)
- # Maybe no mailing lists matched.
- if len(mailing_lists) == 0:
- if not args.quiet:
- print(_('No matching mailing lists found'))
- return
- count = len(mailing_lists) # noqa: F841
- if not args.quiet:
- print(_('$count matching mailing lists found:'))
- # Calculate the longest identifier.
- longest = 0
- output = []
- for mlist in mailing_lists:
- if args.names:
- identifier = '{} [{}]'.format(
- mlist.fqdn_listname, mlist.display_name)
- else:
- identifier = mlist.fqdn_listname
- longest = max(len(identifier), longest)
- output.append((identifier, mlist.description))
- # Print it out.
- if args.descriptions:
- format_string = '{0:{2}} - {1:{3}}'
- else:
- format_string = '{0:{2}}'
- for identifier, description in output:
- print(format_string.format(
- identifier, description, longest, 70 - longest))
+@click.command(
+ cls=I18nCommand,
+ help=_("""\
+ Create a mailing list.
+
+ The 'fully qualified list name', i.e. the posting address of the mailing
+ list is required. It must be a valid email address and the domain must be
+ registered with Mailman. List names are forced to lower case."""))
+@click.option(
+ '--language', metavar='CODE',
+ help=_("""\
+ Set the list's preferred language to CODE, which must be a registered two
+ letter language code."""))
+@click.option(
+ '--owner', '-o', 'owners',
+ multiple=True, metavar='OWNER',
+ help=_("""\
+ Specify a list owner email address. If the address is not currently
+ registered with Mailman, the address is registered and linked to a user.
+ Mailman will send a confirmation message to the address, but it will also
+ send a list creation notice to the address. More than one owner can be
+ specified."""))
+@click.option(
+ '--notify/-no-notify', '-n/-N',
+ default=False,
+ help=_("""\
+ Notify the list owner by email that their mailing list has been
+ created."""))
+@click.option(
+ '--quiet', '-q',
+ is_flag=True, default=False,
+ help=_('Print less output.'))
+@click.option(
+ '--domain/--no-domain', '-d/-D', 'create_domain',
+ default=True,
+ help=_("""\
+ Register the mailing list's domain if not yet registered. This is
+ the default behavior, but these options are provided for backward
+ compatibility. With -D do not register the mailing list's domain."""))
+@click.argument('fqdn_listname', metavar='LISTNAME')
+@click.pass_context
+def create(ctx, language, owners, notify, quiet, create_domain, fqdn_listname):
+ language_code = (language if language is not None
+ else system_preferences.preferred_language.code)
+ # Make sure that the selected language code is known.
+ if language_code not in getUtility(ILanguageManager).codes:
+ ctx.fail(_('Invalid language code: $language_code'))
+ # Check to see if the domain exists or not.
+ listname, at, domain = fqdn_listname.partition('@')
+ domain_manager = getUtility(IDomainManager)
+ if domain_manager.get(domain) is None and create_domain:
+ domain_manager.add(domain)
+ # Validate the owner email addresses. The problem with doing this check in
+ # create_list() is that you wouldn't be able to distinguish between an
+ # InvalidEmailAddressError for the list name or the owners. I suppose we
+ # could subclass that exception though.
+ if len(owners) > 0:
+ validator = getUtility(IEmailValidator)
+ invalid_owners = [owner for owner in owners
+ if not validator.is_valid(owner)]
+ if invalid_owners:
+ invalid = COMMASPACE.join(sorted(invalid_owners)) # noqa: F841
+ ctx.fail(_('Illegal owner addresses: $invalid'))
+ try:
+ mlist = create_list(fqdn_listname, owners)
+ except InvalidEmailAddressError:
+ ctx.fail(_('Illegal list name: $fqdn_listname'))
+ except ListAlreadyExistsError:
+ ctx.fail(_('List already exists: $fqdn_listname'))
+ except BadDomainSpecificationError as domain:
+ ctx.fail(_('Undefined domain: $domain'))
+ # Find the language associated with the code, then set the mailing list's
+ # preferred language to that.
+ language_manager = getUtility(ILanguageManager)
+ with transaction():
+ mlist.preferred_language = language_manager[language_code]
+ # Do the notification.
+ if not quiet:
+ print(_('Created mailing list: $mlist.fqdn_listname'))
+ if notify:
+ template = getUtility(ITemplateLoader).get(
+ 'domain:admin:notice:new-list', mlist)
+ text = wrap(expand(template, mlist, dict(
+ # For backward compatibility.
+ requestaddr=mlist.request_address,
+ siteowner=mlist.no_reply_address,
+ )))
+ # Set the I18N language to the list's preferred language so the header
+ # will match the template language. Stashing and restoring the old
+ # translation context is just (healthy? :) paranoia.
+ with _.using(mlist.preferred_language.code):
+ msg = UserNotification(
+ owners, mlist.no_reply_address,
+ _('Your new mailing list: $fqdn_listname'),
+ text, mlist.preferred_language)
+ msg.send(mlist)
@public
@implementer(ICLISubCommand)
class Create:
- """Create a mailing list."""
-
name = 'create'
+ command = create
- def add(self, parser, command_parser):
- """See `ICLISubCommand`."""
- self.parser = parser
- command_parser.add_argument(
- '--language',
- metavar='CODE', help=_("""\
- Set the list's preferred language to CODE, which must be a
- registered two letter language code."""))
- command_parser.add_argument(
- '-o', '--owner',
- action='append', default=[],
- dest='owners', metavar='OWNER', help=_("""\
- Specify a listowner email address. If the address is not
- currently registered with Mailman, the address is registered and
- linked to a user. Mailman will send a confirmation message to the
- address, but it will also send a list creation notice to the
- address. More than one owner can be specified."""))
- command_parser.add_argument(
- '-n', '--notify',
- default=False, action='store_true',
- help=_("""\
- Notify the list owner by email that their mailing list has been
- created."""))
- command_parser.add_argument(
- '-q', '--quiet',
- default=False, action='store_true',
- help=_('Print less output.'))
- domain_options = command_parser.add_mutually_exclusive_group()
- domain_options.add_argument(
- '-d', '--domain',
- dest='domain',
- default=True, action='store_true',
- help=_("""\
- Register the mailing list's domain if not yet registered. This is
- the default behavior, but these options are provided for backward
- compatibility."""))
- domain_options.add_argument(
- '-D', '--no-domain',
- dest='domain',
- default=False, action='store_false',
- help=_("""\
- Do not register the mailing list's domain if not already
- registered."""))
- # Required positional argument.
- command_parser.add_argument(
- 'listname', metavar='LISTNAME', nargs=1,
- help=_("""\
- The 'fully qualified list name', i.e. the posting address of the
- mailing list. It must be a valid email address and the domain
- must be registered with Mailman. List names are forced to lower
- case."""))
- def process(self, args):
- """See `ICLISubCommand`."""
- language_code = (args.language
- if args.language is not None
- else system_preferences.preferred_language.code)
- # Make sure that the selected language code is known.
- if language_code not in getUtility(ILanguageManager).codes:
- self.parser.error(_('Invalid language code: $language_code'))
- return
- assert len(args.listname) == 1, (
- 'Unexpected positional arguments: %s' % args.listname)
- # Check to see if the domain exists or not.
- fqdn_listname = args.listname[0]
- listname, at, domain = fqdn_listname.partition('@')
- domain_manager = getUtility(IDomainManager)
- if domain_manager.get(domain) is None and args.domain:
- domain_manager.add(domain)
- # Validate the owner email addresses. The problem with doing this
- # check in create_list() is that you wouldn't be able to distinguish
- # between an InvalidEmailAddressError for the list name or the
- # owners. I suppose we could subclass that exception though.
- if args.owners:
- validator = getUtility(IEmailValidator)
- invalid_owners = [owner for owner in args.owners
- if not validator.is_valid(owner)]
- if invalid_owners:
- invalid = COMMASPACE.join(sorted(invalid_owners)) # noqa: F841
- self.parser.error(_('Illegal owner addresses: $invalid'))
- return
- try:
- mlist = create_list(fqdn_listname, args.owners)
- except InvalidEmailAddressError:
- self.parser.error(_('Illegal list name: $fqdn_listname'))
- return
- except ListAlreadyExistsError:
- self.parser.error(_('List already exists: $fqdn_listname'))
- return
- except BadDomainSpecificationError as domain:
- self.parser.error(_('Undefined domain: $domain'))
- return
- # Find the language associated with the code, then set the mailing
- # list's preferred language to that.
- language_manager = getUtility(ILanguageManager)
- with transaction():
- mlist.preferred_language = language_manager[language_code]
- # Do the notification.
- if not args.quiet:
- print(_('Created mailing list: $mlist.fqdn_listname'))
- if args.notify:
- template = getUtility(ITemplateLoader).get(
- 'domain:admin:notice:new-list', mlist)
- text = wrap(expand(template, mlist, dict(
- # For backward compatibility.
- requestaddr=mlist.request_address,
- siteowner=mlist.no_reply_address,
- )))
- # Set the I18N language to the list's preferred language so the
- # header will match the template language. Stashing and restoring
- # the old translation context is just (healthy? :) paranoia.
- with _.using(mlist.preferred_language.code):
- msg = UserNotification(
- args.owners, mlist.no_reply_address,
- _('Your new mailing list: $fqdn_listname'),
- text, mlist.preferred_language)
- msg.send(mlist)
+@click.command(
+ cls=I18nCommand,
+ help=_('Remove a mailing list.'))
+@click.option(
+ '--quiet', '-q',
+ is_flag=True, default=False,
+ help=_('Suppress status messages'))
+@click.argument('listspec')
+def remove(quiet, listspec):
+ mlist = getUtility(IListManager).get(listspec)
+ if mlist is None:
+ if not quiet:
+ print(_('No such list matching spec: $listspec'))
+ sys.exit(0)
+ with transaction():
+ remove_list(mlist)
+ if not quiet:
+ print(_('Removed list: $listspec'))
@public
@implementer(ICLISubCommand)
class Remove:
- """Remove a mailing list."""
-
name = 'remove'
-
- def add(self, parser, command_parser):
- """See `ICLISubCommand`."""
- command_parser.add_argument(
- '-q', '--quiet',
- default=False, action='store_true',
- help=_('Suppress status messages'))
- # Required positional argument.
- command_parser.add_argument(
- 'listname', metavar='LISTNAME', nargs=1,
- help=_("""\
- The 'fully qualified list name', i.e. the posting address of the
- mailing list."""))
-
- @transactional
- def process(self, args):
- """See `ICLISubCommand`."""
- def log(message):
- if not args.quiet:
- print(message)
- assert len(args.listname) == 1, (
- 'Unexpected positional arguments: %s' % args.listname)
- fqdn_listname = args.listname[0]
- mlist = getUtility(IListManager).get(fqdn_listname)
- if mlist is None:
- log(_('No such list: $fqdn_listname'))
- return
- else:
- log(_('Removed list: $fqdn_listname'))
- remove_list(mlist)
+ command = remove