summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/core/tests/test_pipelines.py2
-rw-r--r--src/mailman/database/alembic/versions/70af5a4e5790_digests.py27
-rw-r--r--src/mailman/database/tests/test_migrations.py27
-rw-r--r--src/mailman/handlers/docs/digests.rst4
-rw-r--r--src/mailman/handlers/to_digest.py5
-rw-r--r--src/mailman/interfaces/mailinglist.py17
-rw-r--r--src/mailman/model/mailinglist.py3
-rw-r--r--src/mailman/rest/docs/listconf.rst9
-rw-r--r--src/mailman/rest/listconf.py4
-rw-r--r--src/mailman/rest/tests/test_listconf.py126
-rw-r--r--src/mailman/styles/base.py3
11 files changed, 211 insertions, 16 deletions
diff --git a/src/mailman/core/tests/test_pipelines.py b/src/mailman/core/tests/test_pipelines.py
index 134392668..627873fcf 100644
--- a/src/mailman/core/tests/test_pipelines.py
+++ b/src/mailman/core/tests/test_pipelines.py
@@ -151,7 +151,7 @@ testing
def test_only_decorate_output(self):
# Ensure that decoration is not done on the archive, digest, or
# usenet copy of the message.
- self.assertTrue(self._mlist.digestable)
+ self.assertTrue(self._mlist.digests_enabled)
# Set up NNTP.
self._mlist.gateway_to_news = True
self._mlist.linked_newsgroup = 'testing'
diff --git a/src/mailman/database/alembic/versions/70af5a4e5790_digests.py b/src/mailman/database/alembic/versions/70af5a4e5790_digests.py
new file mode 100644
index 000000000..5df46b584
--- /dev/null
+++ b/src/mailman/database/alembic/versions/70af5a4e5790_digests.py
@@ -0,0 +1,27 @@
+"""digests
+
+Revision ID: 70af5a4e5790
+Revises: 47294d3a604
+Create Date: 2015-12-19 12:05:42.202998
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '70af5a4e5790'
+down_revision = '47294d3a604'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ with op.batch_alter_table('mailinglist') as batch_op:
+ batch_op.alter_column('digestable', new_column_name='digests_enabled')
+ batch_op.drop_column('nondigestable')
+
+
+def downgrade():
+ with op.batch_alter_table('mailinglist') as batch_op:
+ batch_op.alter_column('digests_enabled', new_column_name='digestable')
+ # The data for this column is lost, it's not used anyway.
+ batch_op.add_column(sa.Column('nondigestable', sa.Boolean))
diff --git a/src/mailman/database/tests/test_migrations.py b/src/mailman/database/tests/test_migrations.py
index 823c94652..e3f4b9581 100644
--- a/src/mailman/database/tests/test_migrations.py
+++ b/src/mailman/database/tests/test_migrations.py
@@ -169,3 +169,30 @@ class TestMigrations(unittest.TestCase):
self.assertNotIn('type', results[i])
self.assertEqual(results[4]['type'], '"held message"')
self.assertEqual(results[5]['type'], '"registration"')
+
+ def test_70af5a4e5790_digests(self):
+ IDS_TO_DIGESTABLE = [
+ (1, True),
+ (2, False),
+ (3, False),
+ (4, True),
+ ]
+ mlist_table = sa.sql.table(
+ 'mailinglist',
+ sa.sql.column('id', sa.Integer),
+ sa.sql.column('digests_enabled', sa.Boolean)
+ )
+ # Downgrading.
+ for table_id, enabled in IDS_TO_DIGESTABLE:
+ config.db.store.execute(mlist_table.insert().values(
+ id=table_id, digests_enabled=enabled))
+ config.db.store.commit()
+ alembic.command.downgrade(alembic_cfg, '47294d3a604')
+ results = config.db.store.execute(
+ 'SELECT id, digestable FROM mailinglist').fetchall()
+ self.assertEqual(results, IDS_TO_DIGESTABLE)
+ # Upgrading.
+ alembic.command.upgrade(alembic_cfg, '70af5a4e5790')
+ results = config.db.store.execute(
+ 'SELECT id, digests_enabled FROM mailinglist').fetchall()
+ self.assertEqual(results, IDS_TO_DIGESTABLE)
diff --git a/src/mailman/handlers/docs/digests.rst b/src/mailman/handlers/docs/digests.rst
index c3fc62ebf..828a6b2cc 100644
--- a/src/mailman/handlers/docs/digests.rst
+++ b/src/mailman/handlers/docs/digests.rst
@@ -37,7 +37,7 @@ Short circuiting
When a message is posted to the mailing list, it is generally added to a
mailbox, unless the mailing list does not allow digests.
- >>> mlist.digestable = False
+ >>> mlist.digests_enabled = False
>>> msg = next(message_factory)
>>> process = config.handlers['to-digest'].process
>>> process(mlist, msg, {})
@@ -49,7 +49,7 @@ mailbox, unless the mailing list does not allow digests.
...or they may allow digests but the message is already a digest.
- >>> mlist.digestable = True
+ >>> mlist.digests_enabled = True
>>> process(mlist, msg, dict(isdigest=True))
>>> sum(1 for msg in digest_mbox(mlist))
0
diff --git a/src/mailman/handlers/to_digest.py b/src/mailman/handlers/to_digest.py
index fa3f624be..89e1acaea 100644
--- a/src/mailman/handlers/to_digest.py
+++ b/src/mailman/handlers/to_digest.py
@@ -44,8 +44,9 @@ class ToDigest:
def process(self, mlist, msg, msgdata):
"""See `IHandler`."""
- # Short circuit for non-digestable messages.
- if not mlist.digestable or msgdata.get('isdigest'):
+ # Short-circuit if digests are not enabled or if this message already
+ # is a digest.
+ if not mlist.digests_enabled or msgdata.get('isdigest'):
return
# Open the mailbox that will be used to collect the current digest.
mailbox_path = os.path.join(mlist.data_path, 'digest.mmdf')
diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py
index 0be8c2b68..75fdce327 100644
--- a/src/mailman/interfaces/mailinglist.py
+++ b/src/mailman/interfaces/mailinglist.py
@@ -308,6 +308,19 @@ class IMailingList(Interface):
# Digests.
+ digests_enabled = Attribute(
+ """Whether or not digests are enabled for this mailing list.""")
+
+ digest_size_threshold = Attribute(
+ """The maximum (approximate) size in kilobytes of the digest currently
+ being collected.""")
+
+ digest_send_periodic = Attribute(
+ "Should a digest be sent daily even when the size threshold isn't met?")
+
+ digest_volume_frequency = Attribute(
+ """How often should a new digest volume be started?""")
+
digest_last_sent_at = Attribute(
"""The date and time a digest of this mailing list was last sent.""")
@@ -322,10 +335,6 @@ class IMailingList(Interface):
the digest volume number is bumped, the digest number is reset to
1.""")
- digest_size_threshold = Attribute(
- """The maximum (approximate) size in kilobytes of the digest currently
- being collected.""")
-
def send_one_last_digest_to(address, delivery_mode):
"""Make sure to send one last digest to an address.
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index 0a5b20dd8..9ee52629f 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -135,13 +135,13 @@ class MailingList(Model):
default_member_action = Column(Enum(Action))
default_nonmember_action = Column(Enum(Action))
description = Column(Unicode)
+ digests_enabled = Column(Boolean)
digest_footer_uri = Column(Unicode)
digest_header_uri = Column(Unicode)
digest_is_default = Column(Boolean)
digest_send_periodic = Column(Boolean)
digest_size_threshold = Column(Float)
digest_volume_frequency = Column(Enum(DigestFrequency))
- digestable = Column(Boolean)
discard_these_nonmembers = Column(PickleType)
emergency = Column(Boolean)
encode_ascii_prefixes = Column(Boolean)
@@ -164,7 +164,6 @@ class MailingList(Model):
moderator_password = Column(LargeBinary) # TODO : was RawStr()
newsgroup_moderation = Column(Enum(NewsgroupModeration))
nntp_prefix_subject_too = Column(Boolean)
- nondigestable = Column(Boolean)
nonmember_rejection_notice = Column(Unicode)
obscure_addresses = Column(Boolean)
owner_chain = Column(Unicode)
diff --git a/src/mailman/rest/docs/listconf.rst b/src/mailman/rest/docs/listconf.rst
index 319028be5..8a7e2c526 100644
--- a/src/mailman/rest/docs/listconf.rst
+++ b/src/mailman/rest/docs/listconf.rst
@@ -37,7 +37,10 @@ All readable attributes for a list are available on a sub-resource.
default_nonmember_action: hold
description:
digest_last_sent_at: None
+ digest_send_periodic: True
digest_size_threshold: 30.0
+ digest_volume_frequency: monthly
+ digests_enabled: True
display_name: Ant
filter_content: False
first_strip_reply_to: False
@@ -97,7 +100,10 @@ When using ``PUT``, all writable attributes must be included.
... description='This is my mailing list',
... include_rfc2369_headers=False,
... allow_list_posts=False,
+ ... digest_send_periodic=False,
... digest_size_threshold=10.5,
+ ... digest_volume_frequency='yearly',
+ ... digests_enabled=False,
... posting_pipeline='virgin',
... filter_content=True,
... first_strip_reply_to=True,
@@ -145,7 +151,10 @@ These values are changed permanently.
default_nonmember_action: discard
description: This is my mailing list
...
+ digest_send_periodic: False
digest_size_threshold: 10.5
+ digest_volume_frequency: yearly
+ digests_enabled: False
display_name: Fnords
filter_content: True
first_strip_reply_to: True
diff --git a/src/mailman/rest/listconf.py b/src/mailman/rest/listconf.py
index 62b21644a..b85b32b14 100644
--- a/src/mailman/rest/listconf.py
+++ b/src/mailman/rest/listconf.py
@@ -29,6 +29,7 @@ from mailman.core.errors import (
from mailman.interfaces.action import Action
from mailman.interfaces.archiver import ArchivePolicy
from mailman.interfaces.autorespond import ResponseAction
+from mailman.interfaces.digests import DigestFrequency
from mailman.interfaces.mailinglist import (
IAcceptableAliasSet, ReplyToMunging, SubscriptionPolicy)
from mailman.rest.helpers import (
@@ -112,7 +113,10 @@ ATTRIBUTES = dict(
default_nonmember_action=GetterSetter(enum_validator(Action)),
description=GetterSetter(str),
digest_last_sent_at=GetterSetter(None),
+ digest_send_periodic=GetterSetter(as_boolean),
digest_size_threshold=GetterSetter(float),
+ digest_volume_frequency=GetterSetter(enum_validator(DigestFrequency)),
+ digests_enabled=GetterSetter(as_boolean),
filter_content=GetterSetter(as_boolean),
first_strip_reply_to=GetterSetter(as_boolean),
fqdn_listname=GetterSetter(None),
diff --git a/src/mailman/rest/tests/test_listconf.py b/src/mailman/rest/tests/test_listconf.py
index 0552233bb..0cbf37b1b 100644
--- a/src/mailman/rest/tests/test_listconf.py
+++ b/src/mailman/rest/tests/test_listconf.py
@@ -26,6 +26,7 @@ import unittest
from mailman.app.lifecycle import create_list
from mailman.database.transaction import transaction
+from mailman.interfaces.digests import DigestFrequency
from mailman.interfaces.mailinglist import (
IAcceptableAliasSet, SubscriptionPolicy)
from mailman.testing.helpers import call_api
@@ -58,9 +59,10 @@ RESOURCE = dict(
description='This is my mailing list',
include_rfc2369_headers=False,
allow_list_posts=False,
- #digest_send_periodic='yes',
+ digest_send_periodic=True,
digest_size_threshold=10.5,
- #digest_volume_frequency=1,
+ digest_volume_frequency='monthly',
+ digests_enabled=True,
posting_pipeline='virgin',
filter_content=True,
first_strip_reply_to=True,
@@ -198,7 +200,7 @@ class TestConfiguration(unittest.TestCase):
def test_patch_attribute_double(self):
with self.assertRaises(HTTPError) as cm:
- resource, response = call_api(
+ call_api(
'http://localhost:9001/3.0/lists/ant.example.com'
'/config/reply_to_address',
dict(display_name='bar@ant.example.com',
@@ -255,3 +257,121 @@ class TestConfiguration(unittest.TestCase):
self.assertEqual(cm.exception.code, 400)
self.assertEqual(cm.exception.reason,
b'Cannot convert parameters: posting_pipeline')
+
+ def test_get_digest_send_periodic(self):
+ with transaction():
+ self._mlist.digest_send_periodic = False
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digest_send_periodic')
+ self.assertFalse(resource['digest_send_periodic'])
+
+ def test_patch_digest_send_periodic(self):
+ with transaction():
+ self._mlist.digest_send_periodic = False
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digest_send_periodic',
+ dict(digest_send_periodic=True),
+ 'PATCH')
+ self.assertEqual(response.status, 204)
+ self.assertTrue(self._mlist.digest_send_periodic)
+
+ def test_put_digest_send_periodic(self):
+ with transaction():
+ self._mlist.digest_send_periodic = False
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digest_send_periodic',
+ dict(digest_send_periodic=True),
+ 'PUT')
+ self.assertEqual(response.status, 204)
+ self.assertTrue(self._mlist.digest_send_periodic)
+
+ def test_get_digest_volume_frequency(self):
+ with transaction():
+ self._mlist.digest_volume_frequency = DigestFrequency.yearly
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digest_volume_frequency')
+ self.assertEqual(resource['digest_volume_frequency'], 'yearly')
+
+ def test_patch_digest_volume_frequency(self):
+ with transaction():
+ self._mlist.digest_volume_frequency = DigestFrequency.yearly
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digest_volume_frequency',
+ dict(digest_volume_frequency='monthly'),
+ 'PATCH')
+ self.assertEqual(response.status, 204)
+ self.assertEqual(self._mlist.digest_volume_frequency,
+ DigestFrequency.monthly)
+
+ def test_put_digest_volume_frequency(self):
+ with transaction():
+ self._mlist.digest_volume_frequency = DigestFrequency.yearly
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digest_volume_frequency',
+ dict(digest_volume_frequency='monthly'),
+ 'PUT')
+ self.assertEqual(response.status, 204)
+ self.assertEqual(self._mlist.digest_volume_frequency,
+ DigestFrequency.monthly)
+
+ def test_bad_patch_digest_volume_frequency(self):
+ with transaction():
+ self._mlist.digest_volume_frequency = DigestFrequency.yearly
+ with self.assertRaises(HTTPError) as cm:
+ call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digest_volume_frequency',
+ dict(digest_volume_frequency='once in a while'),
+ 'PATCH')
+ self.assertEqual(cm.exception.code, 400)
+ self.assertEqual(cm.exception.reason,
+ b'Cannot convert parameters: digest_volume_frequency')
+
+ def test_bad_put_digest_volume_frequency(self):
+ with transaction():
+ self._mlist.digest_volume_frequency = DigestFrequency.yearly
+ with self.assertRaises(HTTPError) as cm:
+ call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digest_volume_frequency',
+ dict(digest_volume_frequency='once in a while'),
+ 'PUT')
+ self.assertEqual(cm.exception.code, 400)
+ self.assertEqual(cm.exception.reason,
+ b'Cannot convert parameters: digest_volume_frequency')
+
+ def test_get_digests_enabled(self):
+ with transaction():
+ self._mlist.digests_enabled = False
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digests_enabled')
+ self.assertFalse(resource['digests_enabled'])
+
+ def test_patch_digests_enabled(self):
+ with transaction():
+ self._mlist.digests_enabled = False
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digests_enabled',
+ dict(digests_enabled=True),
+ 'PATCH')
+ self.assertEqual(response.status, 204)
+ self.assertTrue(self._mlist.digests_enabled)
+
+ def test_put_digests_enabled(self):
+ with transaction():
+ self._mlist.digests_enabled = False
+ resource, response = call_api(
+ 'http://localhost:9001/3.0/lists/ant.example.com/config'
+ '/digests_enabled',
+ dict(digests_enabled=True),
+ 'PUT')
+ self.assertEqual(response.status, 204)
+ self.assertTrue(self._mlist.digests_enabled)
diff --git a/src/mailman/styles/base.py b/src/mailman/styles/base.py
index 7a77af609..4f9352fd3 100644
--- a/src/mailman/styles/base.py
+++ b/src/mailman/styles/base.py
@@ -87,7 +87,7 @@ class BasicOperation:
mlist.filter_action = FilterAction.discard
mlist.filter_content = False
# Digests.
- mlist.digestable = True
+ mlist.digests_enabled = True
mlist.digest_is_default = False
mlist.mime_is_default_digest = False
mlist.digest_size_threshold = 30 # KB
@@ -97,7 +97,6 @@ class BasicOperation:
'mailman:///$listname/$language/footer-generic.txt')
mlist.digest_volume_frequency = DigestFrequency.monthly
mlist.next_digest_number = 1
- mlist.nondigestable = True
# NNTP gateway
mlist.nntp_host = ''
mlist.linked_newsgroup = ''