summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/app/subscriptions.py49
-rw-r--r--src/mailman/app/tests/test_moderation.py4
-rw-r--r--src/mailman/app/tests/test_unsubscriptions.py49
-rw-r--r--src/mailman/app/tests/test_workflowmanager.py6
-rw-r--r--src/mailman/app/unsubscriptions.py255
-rw-r--r--src/mailman/commands/eml_membership.py10
-rw-r--r--src/mailman/commands/tests/test_confirm.py2
-rw-r--r--src/mailman/config/configure.zcml10
-rw-r--r--src/mailman/database/alembic/versions/448a93984c35_unsubscription_workflow.py29
-rw-r--r--src/mailman/interfaces/mailinglist.py1
-rw-r--r--src/mailman/model/mailinglist.py8
-rw-r--r--src/mailman/model/tests/test_mailinglist.py2
-rw-r--r--src/mailman/model/tests/test_workflow.py3
-rw-r--r--src/mailman/rest/members.py2
-rw-r--r--src/mailman/rest/sub_moderation.py3
-rw-r--r--src/mailman/rest/tests/test_membership.py2
-rw-r--r--src/mailman/rest/tests/test_moderation.py2
-rw-r--r--src/mailman/runners/tests/test_confirm.py2
-rw-r--r--src/mailman/runners/tests/test_join.py2
-rw-r--r--src/mailman/runners/tests/test_leave.py11
-rw-r--r--src/mailman/styles/base.py2
21 files changed, 96 insertions, 358 deletions
diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py
index c7aaef43d..26c3b1d17 100644
--- a/src/mailman/app/subscriptions.py
+++ b/src/mailman/app/subscriptions.py
@@ -24,6 +24,7 @@ from datetime import timedelta
from email.utils import formataddr
from enum import Enum
from mailman import public
+from mailman.app.membership import delete_member
from mailman.app.workflow import Workflow
from mailman.core.i18n import _
from mailman.database.transaction import flush
@@ -32,7 +33,7 @@ from mailman.interfaces.address import IAddress
from mailman.interfaces.bans import IBanManager
from mailman.interfaces.listmanager import ListDeletingEvent
from mailman.interfaces.mailinglist import SubscriptionPolicy
-from mailman.interfaces.member import MembershipIsBannedError
+from mailman.interfaces.member import MembershipIsBannedError, NotAMemberError
from mailman.interfaces.pending import IPendable, IPendings
from mailman.interfaces.workflowmanager import ConfirmationNeededEvent
from mailman.interfaces.subscriptions import (
@@ -335,6 +336,7 @@ class SubscriptionWorkflow(Workflow):
self.push(next_step)
+@public
class UnSubscriptionWorkflow(Workflow):
"""Workflow of a un-subscription request."""
@@ -445,17 +447,14 @@ class UnSubscriptionWorkflow(Workflow):
self.push('do_unsubscription')
return
# If we don't need the user's confirmation, then skip to the moderation
- # checks
+ # checks.
if self.mlist.unsubscription_policy is SubscriptionPolicy.moderate:
self.push('moderation_checks')
return
-
+ # If the request is pre-confirmed, then the user can unsubscribe right
+ # now.
if self.pre_confirmed:
- next_step = ('moderation_checks'
- if self.mlist.subscription_policy is
- SubscriptionPolicy.confirm_then_moderate # noqa
- else 'do_subscription')
- self.push(next_step)
+ self.push('do_unsubscription')
return
# The user must confirm their un-subsbcription.
self.push('send_confirmation')
@@ -469,11 +468,11 @@ class UnSubscriptionWorkflow(Workflow):
raise StopIteration
def _step_moderation_checks(self):
- # Does the moderator need to approve the unsubscription request.
+ # Does the moderator need to approve the unsubscription request?
assert self.mlist.unsubscription_policy in (
SubscriptionPolicy.moderate,
SubscriptionPolicy.confirm_then_moderate,
- ), self.mlist.unsubscription_policy
+ ), self.mlist.unsubscription_policy
if self.pre_approved:
self.push('do_unsubscription')
else:
@@ -491,11 +490,11 @@ class UnSubscriptionWorkflow(Workflow):
'from $self.address.email')
username = formataddr(
(self.subscriber.display_name, self.address.email))
- text = make('unsubauth.txt',
- mailing_list=self.mlist,
- username=username,
- listname=self.mlist.fqdn_listname,
- )
+ template = getUtility(ITemplateLoader).get(
+ 'list:admin:action:unsubscribe', self.mlist)
+ text = wrap(expand(template, self.mlist, dict(
+ member=username,
+ )))
# This message should appear to come from the <list>-owner so as
# to avoid any useless bounce processing.
msg = UserNotification(
@@ -506,26 +505,34 @@ class UnSubscriptionWorkflow(Workflow):
raise StopIteration
def _step_do_confirm_verify(self):
+ # Restore a little extra state that can't be stored in the database
+ # (because the order of setattr() on restore is indeterminate), then
+ # continue with the confirmation/verification step.
if self.which is WhichSubscriber.address:
self.subscriber = self.address
else:
assert self.which is WhichSubscriber.user
self.subscriber = self.user
- # Reset the token so it can't be used in a replay attack.
+ # Reset the token so it can't be used in a replay attack.
self._set_token(TokenOwner.no_one)
+ # The user has confirmed their unsubscription request
next_step = ('moderation_checks'
if self.mlist.unsubscription_policy in (
SubscriptionPolicy.moderate,
SubscriptionPolicy.confirm_then_moderate,
)
else 'do_unsubscription')
- self.push('do_unsubscription')
+ self.push(next_step)
def _step_do_unsubscription(self):
- delete_member(self.mlist, self.address.email)
+ try:
+ delete_member(self.mlist, self.address.email)
+ except NotAMemberError:
+ # The member has already been unsubscribed.
+ pass
self.member = None
# This workflow is done so throw away any associated state.
- getUtility(IWorkflowStateManager).restore(self.name, self.token)
+ getUtility(IWorkflowStateManager).restore(self.name, self.token)
def _step_unsubscribe_from_restored(self):
# Prevent replay attacks.
@@ -533,7 +540,7 @@ class UnSubscriptionWorkflow(Workflow):
if self.which is WhichSubscriber.address:
self.subscriber = self.address
else:
- assert self.which is WhichSubsriber.user
+ assert self.which is WhichSubscriber.user
self.subscriber = self.user
self.push('do_unsubscription')
@@ -569,7 +576,7 @@ class SubscriptionWorkflowManager(BaseSubscriptionManager):
def register(self, subscriber=None, *,
pre_verified=False, pre_confirmed=False, pre_approved=False):
- """See `IWorkflowManager`."""
+ """See `ISubscriptionManager`."""
workflow = SubscriptionWorkflow(
self._mlist, subscriber,
pre_verified=pre_verified,
diff --git a/src/mailman/app/tests/test_moderation.py b/src/mailman/app/tests/test_moderation.py
index a7fc9e1b4..9217ef4f5 100644
--- a/src/mailman/app/tests/test_moderation.py
+++ b/src/mailman/app/tests/test_moderation.py
@@ -24,7 +24,7 @@ from mailman.app.moderator import (
handle_message, handle_unsubscription, hold_message, hold_unsubscription)
from mailman.interfaces.action import Action
from mailman.interfaces.messages import IMessageStore
-from mailman.interfaces.workflowmanager import IWorkflowManager
+from mailman.interfaces.subscriptions import ISubscriptionManager
from mailman.interfaces.requests import IListRequests
from mailman.interfaces.usermanager import IUserManager
from mailman.runners.incoming import IncomingRunner
@@ -154,7 +154,7 @@ class TestUnsubscription(unittest.TestCase):
def setUp(self):
self._mlist = create_list('test@example.com')
self._registrar = getAdapter(
- self._mlist, IWorkflowManager, name='subscribe')
+ self._mlist, ISubscriptionManager, name='subscribe')
def test_unsubscribe_defer(self):
# When unsubscriptions must be approved by the moderator, but the
diff --git a/src/mailman/app/tests/test_unsubscriptions.py b/src/mailman/app/tests/test_unsubscriptions.py
index 9c7b27b87..c4f36e78a 100644
--- a/src/mailman/app/tests/test_unsubscriptions.py
+++ b/src/mailman/app/tests/test_unsubscriptions.py
@@ -23,14 +23,11 @@ import unittest
from contextlib import suppress
from mailman.app.lifecycle import create_list
from mailman.app.unsubscriptions import UnSubscriptionWorkflow
-from mailman.interfaces.bans import IBanManager
from mailman.interfaces.mailinglist import SubscriptionPolicy
-from mailman.interfaces.member import MembershipIsBannedError
from mailman.interfaces.pending import IPendings
from mailman.interfaces.subscriptions import TokenOwner
from mailman.interfaces.usermanager import IUserManager
-from mailman.testing.helpers import (
- LogFileMark, get_queue_messages, set_preferred)
+from mailman.testing.helpers import LogFileMark, get_queue_messages
from mailman.testing.layers import ConfigLayer
from mailman.utilities.datetime import now
from unittest.mock import patch
@@ -38,7 +35,6 @@ from zope.component import getUtility
class TestUnSubscriptionWorkflow(unittest.TestCase):
-
layer = ConfigLayer
maxDiff = None
@@ -62,8 +58,9 @@ class TestUnSubscriptionWorkflow(unittest.TestCase):
self.assertIsNone(workflow.member)
def test_pended_data(self):
- # Test there is a Pendable object associated with a held un-subscription
- # request and it has some valid data associated with it.
+ # Test there is a Pendable object associated with a held
+ # un-subscription request and it has some valid data associated with
+ # it.
workflow = UnSubscriptionWorkflow(self._mlist, self.anne)
with suppress(StopIteration):
workflow.run_thru('send_confirmation')
@@ -115,8 +112,8 @@ class TestUnSubscriptionWorkflow(unittest.TestCase):
# un-subscription is pre-confirmed. Since moderation is not reuqired,
# the user will be immediately un-subscribed.
self._mlist.unsubscription_policy = SubscriptionPolicy.confirm
- workflow = UnSubscriptionWorkflow(self._mlist, self.anne,
- pre_confirmed=True)
+ workflow = UnSubscriptionWorkflow(
+ self._mlist, self.anne, pre_confirmed=True)
workflow.run_thru('confirmation_checks')
with patch.object(workflow, '_step_do_unsubscription') as step:
next(workflow)
@@ -128,8 +125,8 @@ class TestUnSubscriptionWorkflow(unittest.TestCase):
# check will be performed.
self._mlist.unsubscription_policy = (
SubscriptionPolicy.confirm_then_moderate)
- workflow = UnSubscriptionWorkflow(self._mlist, self.anne,
- pre_confirmed=True)
+ workflow = UnSubscriptionWorkflow(
+ self._mlist, self.anne, pre_confirmed=True)
workflow.run_thru('confirmation_checks')
with patch.object(workflow, '_step_do_unsubscription') as step:
next(workflow)
@@ -173,9 +170,9 @@ class TestUnSubscriptionWorkflow(unittest.TestCase):
self.assertIsNone(member)
def test_do_unsubscription_pre_approved(self):
- # A moderation-requiring subscription policy plus a pre-approved address
- # means the user gets un-subscribed from the mailing list without any
- # further confirmation or approvals.
+ # A moderation-requiring subscription policy plus a pre-approved
+ # address means the user gets un-subscribed from the mailing list
+ # without any further confirmation or approvals.
self._mlist.unsubscription_policy = SubscriptionPolicy.moderate
workflow = UnSubscriptionWorkflow(self._mlist, self.anne,
pre_approved=True)
@@ -199,7 +196,7 @@ class TestUnSubscriptionWorkflow(unittest.TestCase):
list(workflow)
member = self._mlist.regular_members.get_member(self._anne)
self.assertIsNone(member)
- # No further token is needed.
+ # No further token is needed.
self.assertIsNone(workflow.token)
self.assertEqual(workflow.token_owner, TokenOwner.no_one)
@@ -233,8 +230,8 @@ class TestUnSubscriptionWorkflow(unittest.TestCase):
# point the workflow is saved. Once the moderator approves, the
# workflow resumes and the user is un-subscribed.
self._mlist.unsubscription_policy = SubscriptionPolicy.moderate
- workflow = UnSubscriptionWorkflow(self._mlist, self.anne,
- pre_confirmed=True)
+ workflow = UnSubscriptionWorkflow(
+ self._mlist, self.anne, pre_confirmed=True)
# Run the entire workflow.
list(workflow)
# The user is currently subscribed to the mailing list.
@@ -264,22 +261,22 @@ class TestUnSubscriptionWorkflow(unittest.TestCase):
# logged.
mark = LogFileMark('mailman.subscribe')
self._mlist.unsubscription_policy = SubscriptionPolicy.moderate
- workflow = UnSubscriptionWorkflow(self._mlist, self.anne,
- pre_confirmed=True)
+ workflow = UnSubscriptionWorkflow(
+ self._mlist, self.anne, pre_confirmed=True)
# Run the entire workflow.
list(workflow)
self.assertIn(
- 'test@example.com: held unsubscription request from anne@example.com',
- mark.readline()
- )
+ 'test@example.com: held unsubscription request from anne@example.com',
+ mark.readline()
+ )
def test_get_moderator_approval_notifies_moderators(self):
# When the un-subscription is held for moderator approval, and the list
# is so configured, a notification is sent to the list moderators.
self._mlist.admin_immed_notify = True
self._mlist.unsubscription_policy = SubscriptionPolicy.moderate
- workflow = UnSubscriptionWorkflow(self._mlist, self.anne,
- pre_confirmed=True)
+ workflow = UnSubscriptionWorkflow(
+ self._mlist, self.anne, pre_confirmed=True)
# Consume the entire state machine.
list(workflow)
items = get_queue_messages('virgin', expected_count=1)
@@ -302,8 +299,8 @@ request approval:
# moderators.
self._mlist.admin_immed_notify = False
self._mlist.unsubscription_policy = SubscriptionPolicy.moderate
- workflow = UnSubscriptionWorkflow(self._mlist, self.anne,
- pre_confirmed=True)
+ workflow = UnSubscriptionWorkflow(
+ self._mlist, self.anne, pre_confirmed=True)
# Consume the entire state machine.
list(workflow)
get_queue_messages('virgin', expected_count=0)
diff --git a/src/mailman/app/tests/test_workflowmanager.py b/src/mailman/app/tests/test_workflowmanager.py
index a6c5f92ca..047291838 100644
--- a/src/mailman/app/tests/test_workflowmanager.py
+++ b/src/mailman/app/tests/test_workflowmanager.py
@@ -18,14 +18,12 @@
"""Test email address registration."""
import unittest
-import pdb
from mailman.app.lifecycle import create_list
from mailman.interfaces.mailinglist import SubscriptionPolicy
from mailman.interfaces.member import MemberRole
from mailman.interfaces.pending import IPendings
-from mailman.interfaces.workflowmanager import IWorkflowManager
-from mailman.interfaces.subscriptions import TokenOwner
+from mailman.interfaces.subscriptions import ISubscriptionManager, TokenOwner
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import get_queue_messages
from mailman.testing.layers import ConfigLayer
@@ -41,7 +39,7 @@ class TestRegistrar(unittest.TestCase):
def setUp(self):
self._mlist = create_list('ant@example.com')
self._registrar = getAdapter(
- self._mlist, IWorkflowManager, name='subscribe')
+ self._mlist, ISubscriptionManager, name='subscribe')
self._pendings = getUtility(IPendings)
self._anne = getUtility(IUserManager).create_address(
'anne@example.com')
diff --git a/src/mailman/app/unsubscriptions.py b/src/mailman/app/unsubscriptions.py
deleted file mode 100644
index 1a88b121f..000000000
--- a/src/mailman/app/unsubscriptions.py
+++ /dev/null
@@ -1,255 +0,0 @@
-# Copyright (C) 2016 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman is free software: you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free
-# Software Foundation, either version 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-# more details.
-#
-# You should have received a copy of the GNU General Public License along with
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-"""Handle un-subscriptions."""
-
-import uuid
-import logging
-
-from datetime import timedelta
-from email.utils import formataddr
-from mailman import public
-from mailman.app.membership import delete_member
-from mailman.app.subscriptions import WhichSubscriber
-from mailman.app.workflow import Workflow
-from mailman.core.i18n import _
-from mailman.email.message import UserNotification
-from mailman.interfaces.address import IAddress
-from mailman.interfaces.mailinglist import SubscriptionPolicy
-from mailman.interfaces.workflowmanager import ConfirmationNeededEvent
-from mailman.interfaces.user import IUser
-from mailman.interfaces.pending import IPendings, IPendable
-from mailman.interfaces.subscriptions import TokenOwner
-from mailman.interfaces.usermanager import IUserManager
-from mailman.interfaces.workflow import IWorkflowStateManager
-from mailman.utilities.datetime import now
-from mailman.utilities.i18n import make
-from zope.component import getUtility
-from zope.event import notify
-from zope.interface import implementer
-
-
-log = logging.getLogger('mailman.subscribe')
-
-
-@implementer(IPendable)
-class Pendable(dict):
- PEND_TYPE = 'unsubscription'
-
-
-class UnSubscriptionWorkflow(Workflow):
- """Workflow of a un-subscription request
- """
-
- INITIAL_STATE = 'subscription_checks'
- SAVE_ATTRIBUTES = (
- 'pre_approved',
- 'pre_confirmed',
- 'address_key',
- 'user_key',
- 'subscriber_key',
- 'token_owner_key',
- )
-
- def __init__(self, mlist, subscriber=None, *,
- pre_approved=False, pre_confirmed = False):
- super().__init__()
- self.mlist = mlist
- self.address = None
- self.user = None
- self.which = None
- self._set_token(TokenOwner.no_one)
- # `subscriber` should be an implementer of IAddress.
- if IAddress.providedBy(subscriber):
- self.address = subscriber
- self.user = self.address.user
- self.which = WhichSubscriber.address
- self.member = self.mlist.regular_members.get_member(
- self.address.email)
- elif IUser.providedBy(subscriber):
- self.address = subscriber.preferred_address
- self.user = subscriber
- self.which = WhichSubscriber.address
- self.member = self.mlist.regular_members.get_member(
- self.address.email)
- self.subscriber = subscriber
- self.pre_confirmed = pre_confirmed
- self.pre_approved = pre_approved
-
- @property
- def user_key(self):
- # For save.
- return self.user.user_id.hex
-
- @user_key.setter
- def user_key(self, hex_key):
- # For restore.
- uid = uuid.UUID(hex_key)
- self.user = getUtility(IUserManager).get_user_by_id(uid)
- assert self.user is not None
-
- @property
- def address_key(self):
- # For save.
- return self.address.email
-
- @address_key.setter
- def address_key(self, email):
- # For restore.
- self.address = getUtility(IUserManager).get_address(email)
- assert self.address is not None
-
- @property
- def subscriber_key(self):
- return self.which.value
-
- @subscriber_key.setter
- def subscriber_key(self, key):
- self.which = WhichSubscriber(key)
-
- @property
- def token_owner_key(self):
- return self.token_owner.value
-
- @token_owner_key.setter
- def token_owner_key(self, value):
- self.token_owner = TokenOwner(value)
-
- def _set_token(self, token_owner):
- assert isinstance(token_owner, TokenOwner)
- pendings = getUtility(IPendings)
- # Clear out the previous pending token if there is one.
- if self.token is not None:
- pendings.confirm(self.token)
- # Create a new token to prevent replay attacks. It seems like this
- # would produce the same token, but it won't because the pending adds a
- # bit of randomization.
- self.token_owner = token_owner
- if token_owner is TokenOwner.no_one:
- self.token = None
- return
- pendable = Pendable(
- list_id=self.mlist.list_id,
- email=self.address.email,
- display_name=self.address.display_name,
- when=now().replace(microsecond=0).isoformat(),
- token_owner=token_owner.name,
- )
- self.token = pendings.add(pendable, timedelta(days=3650))
-
- def _step_subscription_checks(self):
- assert self.mlist.is_subscribed(self.subscriber)
- self.push('confirmation_checks')
-
- def _step_confirmation_checks(self):
- # If list's unsubscription policy is open, the user can unsubscribe
- # right now.
- if self.mlist.unsubscription_policy is SubscriptionPolicy.open:
- self.push('do_unsubscription')
- return
- # If we don't need the user's confirmation, then skip to the moderation
- # checks
- if self.mlist.unsubscription_policy is SubscriptionPolicy.moderate:
- self.push('moderation_checks')
- return
-
- if self.pre_confirmed:
- next_step = ('moderation_checks'
- if self.mlist.subscription_policy is
- SubscriptionPolicy.confirm_then_moderate # noqa
- else 'do_subscription')
- self.push(next_step)
- return
- # The user must confirm their un-subsbcription.
- self.push('send_confirmation')
-
- def _step_send_confirmation(self):
- self._set_token(TokenOwner.subscriber)
- self.push('do_confirm_verify')
- self.save()
- notify(ConfirmationNeededEvent(
- self.mlist, self.token, self.address.email))
- raise StopIteration
-
- def _step_moderation_checks(self):
- # Does the moderator need to approve the unsubscription request.
- assert self.mlist.unsubscription_policy in (
- SubscriptionPolicy.moderate,
- SubscriptionPolicy.confirm_then_moderate,
- ), self.mlist.unsubscription_policy
- if self.pre_approved:
- self.push('do_unsubscription')
- else:
- self.push('get_moderator_approval')
-
- def _step_get_moderator_approval(self):
- self._set_token(TokenOwner.moderator)
- self.push('unsubscribe_from_restored')
- self.save()
- log.info('{}: held unsubscription request from {}'.format(
- self.mlist.fqdn_listname, self.address.email))
- if self.mlist.admin_immed_notify:
- subject = _(
- 'New unsubscription request to $self.mlist.display_name '
- 'from $self.address.email')
- username = formataddr(
- (self.subscriber.display_name, self.address.email))
- text = make('unsubauth.txt',
- mailing_list=self.mlist,
- username=username,
- listname=self.mlist.fqdn_listname,
- )
- # This message should appear to come from the <list>-owner so as
- # to avoid any useless bounce processing.
- msg = UserNotification(
- self.mlist.owner_address, self.mlist.owner_address,
- subject, text, self.mlist.preferred_language)
- msg.send(self.mlist, tomoderators=True)
- # The workflow must stop running here
- raise StopIteration
-
- def _step_do_confirm_verify(self):
- if self.which is WhichSubscriber.address:
- self.subscriber = self.address
- else:
- assert self.which is WhichSubscriber.user
- self.subscriber = self.user
- # Reset the token so it can't be used in a replay attack.
- self._set_token(TokenOwner.no_one)
- next_step = ('moderation_checks'
- if self.mlist.unsubscription_policy in (
- SubscriptionPolicy.moderate,
- SubscriptionPolicy.confirm_then_moderate,
- )
- else 'do_unsubscription')
- self.push('do_unsubscription')
-
- def _step_do_unsubscription(self):
- delete_member(self.mlist, self.address.email)
- self.member = None
- # This workflow is done so throw away any associated state.
- getUtility(IWorkflowStateManager).restore(self.name, self.token)
-
- def _step_unsubscribe_from_restored(self):
- # Prevent replay attacks.
- self._set_token(TokenOwner.no_one)
- if self.which is WhichSubscriber.address:
- self.subscriber = self.address
- else:
- assert self.which is WhichSubsriber.user
- self.subscriber = self.user
- self.push('do_unsubscription')
diff --git a/src/mailman/commands/eml_membership.py b/src/mailman/commands/eml_membership.py
index af391fd5b..fad1cbe50 100644
--- a/src/mailman/commands/eml_membership.py
+++ b/src/mailman/commands/eml_membership.py
@@ -25,7 +25,7 @@ from mailman.interfaces.member import DeliveryMode, MemberRole
from mailman.interfaces.workflowmanager import IWorkflowManager
from mailman.interfaces.subscriptions import ISubscriptionService
from mailman.interfaces.usermanager import IUserManager
-from zope.component import getUtility, getAdapter
+from zope.component import getAdapter, getUtility
from zope.interface import implementer
@@ -101,8 +101,8 @@ used.
print(_('$person is already a member'), file=results)
return ContinueProcessing.yes
subscriber = match_subscriber(email, display_name)
- getAdapter(mlist,
- IWorkflowManager, name='subscribe').register(subscriber)
+ getAdapter(
+ mlist, IWorkflowManager, name='subscribe').register(subscriber)
print(_('Confirmation email sent to $person'), file=results)
return ContinueProcessing.yes
@@ -187,8 +187,8 @@ You may be asked to confirm your request.""")
'$self.name: $email is not a member of $mlist.fqdn_listname'),
file=results)
return ContinueProcessing.no
- getAdapter(mlist,
- IWorkflowManager, name='unsubscribe').register(user_address)
+ getAdapter(
+ mlist, IWorkflowManager, name='unsubscribe').register(user_address)
# member.unsubscribe()
person = formataddr((user.display_name, email)) # noqa
print(_('Confirmation email sent to $person to leave'
diff --git a/src/mailman/commands/tests/test_confirm.py b/src/mailman/commands/tests/test_confirm.py
index d7414fbfa..797edcad5 100644
--- a/src/mailman/commands/tests/test_confirm.py
+++ b/src/mailman/commands/tests/test_confirm.py
@@ -30,7 +30,7 @@ from mailman.interfaces.usermanager import IUserManager
from mailman.runners.command import CommandRunner, Results
from mailman.testing.helpers import get_queue_messages, make_testable_runner
from mailman.testing.layers import ConfigLayer
-from zope.component import getUtility, getAdapter
+from zope.component import getAdapter, getUtility
class TestConfirm(unittest.TestCase):
diff --git a/src/mailman/config/configure.zcml b/src/mailman/config/configure.zcml
index 153a8b9bd..98cfa1232 100644
--- a/src/mailman/config/configure.zcml
+++ b/src/mailman/config/configure.zcml
@@ -48,16 +48,16 @@
<adapter
for="mailman.interfaces.mailinglist.IMailingList"
- provides="mailman.interfaces.workflowmanager.IWorkflowManager"
+ provides="mailman.interfaces.subscriptions.ISubscriptionManager"
factory="mailman.app.workflowmanager.SubscriptionWorkflowManager"
- name='subscribe'
- />
+ name='subscribe'
+ />
<adapter
for="mailman.interfaces.mailinglist.IMailingList"
- provides="mailman.interfaces.workflowmanager.IWorkflowManager"
+ provides="mailman.interfaces.subscriptions.ISubscriptionManager"
factory="mailman.app.workflowmanager.UnsubscriptionWorkflowManager"
- name='unsubscribe'
+ name='unsubscribe'
/>
<utility
diff --git a/src/mailman/database/alembic/versions/448a93984c35_unsubscription_workflow.py b/src/mailman/database/alembic/versions/448a93984c35_unsubscription_workflow.py
index 51be76051..64fcdffcd 100644
--- a/src/mailman/database/alembic/versions/448a93984c35_unsubscription_workflow.py
+++ b/src/mailman/database/alembic/versions/448a93984c35_unsubscription_workflow.py
@@ -3,32 +3,34 @@
Revision ID: 448a93984c35
Revises: 7b254d88f122
Create Date: 2016-06-02 14:34:24.154723
-
"""
-# revision identifiers, used by Alembic.
-revision = '448a93984c35'
-down_revision = '7b254d88f122'
-
import sqlalchemy as sa
from alembic import op
-from mailman.database.helpers import is_sqlite, exists_in_db
+from mailman.database.helpers import exists_in_db
from mailman.database.types import Enum
from mailman.interfaces.mailinglist import SubscriptionPolicy
+
+# revision identifiers, used by Alembic.
+revision = '448a93984c35'
+down_revision = '7b254d88f122'
+
+
def upgrade():
if not exists_in_db(op.get_bind(), 'mailinglist', 'unsubscription_policy'):
- # SQLite may not have removed it when downgrading.
- op.add_column('mailinglist', sa.Column(
- 'unsubscription_policy', Enum(SubscriptionPolicy), nullable=True))
-
+ with op.batch_alter_table('mailinglist') as batch_op:
+ # SQLite may not have removed it when downgrading.
+ batch_op.add_column('mailinglist', sa.Column(
+ 'unsubscription_policy', Enum(SubscriptionPolicy),
+ nullable=True))
# Now migrate the data. Don't import the table definition from the
# models, it may break this migration when the model is updated in the
# future (see the Alembic doc).
mlist = sa.sql.table(
'mailinglist',
sa.sql.column('unsubscription_policy', Enum(SubscriptionPolicy))
- )
+ )
# There were no enforced subscription policy before, so all lists are
# considered open.
op.execute(mlist.update().values(
@@ -37,6 +39,5 @@ def upgrade():
def downgrade():
- if not is_sqlite(op.get_bind()):
- # SQLite does not support dropping columns.
- op.drop_column('mailinglist', 'unsubscription_policy')
+ with op.batch_alter_table('mailinglist') as batch_op:
+ batch_op.drop_column('mailinglist', 'unsubscription_policy')
diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py
index 40ee95187..366016872 100644
--- a/src/mailman/interfaces/mailinglist.py
+++ b/src/mailman/interfaces/mailinglist.py
@@ -58,7 +58,6 @@ class SubscriptionPolicy(Enum):
confirm_then_moderate = 3
-
@public
class IMailingList(Interface):
"""A mailing list."""
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index 396b8ed3d..4e76e61c1 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -457,11 +457,7 @@ class MailingList(Model):
Member.role == role,
Member.list_id == self._list_id,
Member._user == subscriber).first()
-
- if member:
- return True
- else:
- return False
+ return member is not None
@dbconnection
def subscribe(self, store, subscriber, role=MemberRole.member):
@@ -470,10 +466,8 @@ class MailingList(Model):
email = subscriber.email
elif IUser.providedBy(subscriber):
email = subscriber.preferred_address.email
-
if self.is_subscribed(subscriber, role):
raise AlreadySubscribedError(self.fqdn_listname, email, role)
-
member = Member(role=role,
list_id=self._list_id,
subscriber=subscriber)
diff --git a/src/mailman/model/tests/test_mailinglist.py b/src/mailman/model/tests/test_mailinglist.py
index 3e3c5814c..0a5c8a5e9 100644
--- a/src/mailman/model/tests/test_mailinglist.py
+++ b/src/mailman/model/tests/test_mailinglist.py
@@ -17,7 +17,6 @@
"""Test MailingLists and related model objects.."""
-import pdb
import unittest
from mailman.app.lifecycle import create_list
@@ -106,6 +105,7 @@ class TestMailingList(unittest.TestCase):
self._mlist.subscribe(address)
self.assertEqual(True, self._mlist.is_subscribed(address))
+
class TestListArchiver(unittest.TestCase):
layer = ConfigLayer
diff --git a/src/mailman/model/tests/test_workflow.py b/src/mailman/model/tests/test_workflow.py
index afcba613b..2b6a593a9 100644
--- a/src/mailman/model/tests/test_workflow.py
+++ b/src/mailman/model/tests/test_workflow.py
@@ -124,7 +124,8 @@ class TestWorkflow(unittest.TestCase):
self.assertEqual(self._manager.count, 1)
def test_discard(self):
- # Discard some workflow state. This is use by IWorkflowManager.discard().
+ # Discard some workflow state. This is use by
+ # IWorkflowManager.discard().
self._manager.save('ant', 'token', 'one')
self._manager.save('bee', 'token', 'two')
self._manager.save('ant', 'nekot', 'three')
diff --git a/src/mailman/rest/members.py b/src/mailman/rest/members.py
index ac3fd96b4..ed2246366 100644
--- a/src/mailman/rest/members.py
+++ b/src/mailman/rest/members.py
@@ -37,7 +37,7 @@ from mailman.rest.preferences import Preferences, ReadOnlyPreferences
from mailman.rest.validator import (
Validator, enum_validator, subscriber_validator)
from uuid import UUID
-from zope.component import getUtility, getAdapter
+from zope.component import getAdapter, getUtility
class _MemberBase(CollectionMixin):
diff --git a/src/mailman/rest/sub_moderation.py b/src/mailman/rest/sub_moderation.py
index a6ef2ad35..f37e62ac5 100644
--- a/src/mailman/rest/sub_moderation.py
+++ b/src/mailman/rest/sub_moderation.py
@@ -28,8 +28,7 @@ from mailman.rest.helpers import (
CollectionMixin, bad_request, child, conflict, etag, no_content,
not_found, okay)
from mailman.rest.validator import Validator, enum_validator
-from mailman.utilities.i18n import _
-from zope.component import getUtility, getAdapter
+from zope.component import getAdapter, getUtility
class _ModerationBase:
diff --git a/src/mailman/rest/tests/test_membership.py b/src/mailman/rest/tests/test_membership.py
index 9dec17fc8..2726a480f 100644
--- a/src/mailman/rest/tests/test_membership.py
+++ b/src/mailman/rest/tests/test_membership.py
@@ -35,7 +35,7 @@ from mailman.testing.helpers import (
from mailman.testing.layers import ConfigLayer, RESTLayer
from mailman.utilities.datetime import now
from urllib.error import HTTPError
-from zope.component import getUtility, getAdapter
+from zope.component import getAdapter, getUtility
class TestMembership(unittest.TestCase):
diff --git a/src/mailman/rest/tests/test_moderation.py b/src/mailman/rest/tests/test_moderation.py
index 1b9febdce..1adc1aa83 100644
--- a/src/mailman/rest/tests/test_moderation.py
+++ b/src/mailman/rest/tests/test_moderation.py
@@ -32,7 +32,7 @@ from mailman.testing.helpers import (
specialized_message_from_string as mfs)
from mailman.testing.layers import RESTLayer
from urllib.error import HTTPError
-from zope.component import getUtility, getAdapter
+from zope.component import getAdapter, getUtility
class TestPostModeration(unittest.TestCase):
diff --git a/src/mailman/runners/tests/test_confirm.py b/src/mailman/runners/tests/test_confirm.py
index d49ba217b..d6bc4c34d 100644
--- a/src/mailman/runners/tests/test_confirm.py
+++ b/src/mailman/runners/tests/test_confirm.py
@@ -31,7 +31,7 @@ from mailman.testing.helpers import (
get_queue_messages, make_testable_runner,
specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
-from zope.component import getUtility, getAdapter
+from zope.component import getAdapter, getUtility
class TestConfirm(unittest.TestCase):
diff --git a/src/mailman/runners/tests/test_join.py b/src/mailman/runners/tests/test_join.py
index 7409c8e89..b565ca048 100644
--- a/src/mailman/runners/tests/test_join.py
+++ b/src/mailman/runners/tests/test_join.py
@@ -31,7 +31,7 @@ from mailman.testing.helpers import (
get_queue_messages, make_testable_runner,
specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
-from zope.component import getUtility, getAdapter
+from zope.component import getAdapter, getUtility
class TestJoin(unittest.TestCase):
diff --git a/src/mailman/runners/tests/test_leave.py b/src/mailman/runners/tests/test_leave.py
index 23257124a..64a46a4da 100644
--- a/src/mailman/runners/tests/test_leave.py
+++ b/src/mailman/runners/tests/test_leave.py
@@ -17,9 +17,9 @@
"""Test mailing list un-subscriptions."""
-import pdb
import unittest
+from email.iterators import body_line_iterator
from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.database.transaction import transaction
@@ -30,8 +30,8 @@ from mailman.testing.helpers import (
specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
from mailman.testing.helpers import set_preferred
-from mailman.utilities.datetime import now
-from zope.component import getUtility, getAdapter
+from zope.component import getUtility
+
class TestLeave(unittest.TestCase):
"""Test mailing list un-subscriptions"""
@@ -49,7 +49,7 @@ class TestLeave(unittest.TestCase):
anne = getUtility(IUserManager).create_user('anne@example.org')
set_preferred(anne)
self._mlist.subscribe(list(anne.addresses)[0])
- msg = mfs("""\
+ msg = mfs("""\
From: anne@example.org
To: test-leave@example.com
@@ -60,9 +60,6 @@ leave
self._runner.run()
items = get_queue_messages('virgin', sort_on='subject',
expected_count=1)
- print(items[0].msg)
- print(anne.addresses)
- pdb.set_trace()
self.assertTrue(str(items[0].msg['subject']).startswith('confirm'))
confirmation_lines = []
in_results = False
diff --git a/src/mailman/styles/base.py b/src/mailman/styles/base.py
index 7226d762a..b4c1c336d 100644
--- a/src/mailman/styles/base.py
+++ b/src/mailman/styles/base.py
@@ -67,7 +67,7 @@ class BasicOperation:
mlist.default_member_action = Action.defer
mlist.default_nonmember_action = Action.hold
mlist.subscription_policy = SubscriptionPolicy.confirm
- mlist.unsubscription_policy = SubscriptionPolicy.confirm
+ mlist.unsubscription_policy = SubscriptionPolicy.open
# Notify the administrator of pending requests and membership changes.
mlist.admin_immed_notify = True
mlist.admin_notify_mchanges = False