Merge lp:~abompard/mailman/sqlalchemy into lp:~raj-abhilash1/mailman/sqlalchemy

Proposed by Aurélien Bompard
Status: Needs review
Proposed branch: lp:~abompard/mailman/sqlalchemy
Merge into: lp:~raj-abhilash1/mailman/sqlalchemy
Diff against target: 1466 lines (+339/-301)
28 files modified
MANIFEST.in (+1/-4)
src/mailman/app/docs/moderator.rst (+29/-40)
src/mailman/commands/docs/conf.rst (+1/-2)
src/mailman/commands/docs/withlist.rst (+2/-2)
src/mailman/config/alembic.cfg (+20/-0)
src/mailman/config/config.py (+0/-2)
src/mailman/config/schema.cfg (+3/-9)
src/mailman/core/docs/runner.rst (+2/-0)
src/mailman/core/logging.py (+28/-21)
src/mailman/database/alembic/__init__.py (+4/-4)
src/mailman/database/alembic/env.py (+0/-5)
src/mailman/database/alembic/versions/51b7f92bd06c_initial.py (+46/-14)
src/mailman/database/base.py (+5/-1)
src/mailman/database/factory.py (+43/-48)
src/mailman/database/tests/test_factory.py (+80/-82)
src/mailman/handlers/docs/owner-recips.rst (+2/-2)
src/mailman/model/docs/autorespond.rst (+11/-11)
src/mailman/model/docs/mailinglist.rst (+6/-6)
src/mailman/model/docs/requests.rst (+10/-10)
src/mailman/model/domain.py (+4/-3)
src/mailman/model/listmanager.py (+2/-1)
src/mailman/model/mailinglist.py (+7/-6)
src/mailman/model/user.py (+2/-2)
src/mailman/rest/docs/moderation.rst (+15/-17)
src/mailman/testing/layers.py (+4/-8)
src/mailman/testing/testing.cfg (+1/-1)
src/mailman/utilities/importer.py (+1/-0)
src/mailman/utilities/tests/test_import.py (+10/-0)
To merge this branch: bzr merge lp:~abompard/mailman/sqlalchemy
Reviewer Review Type Date Requested Status
Abhilash Raj Pending
Review via email: mp+239952@code.launchpad.net

Description of the change

A small import fix

To post a comment you must log in.
lp:~abompard/mailman/sqlalchemy updated
7287. By Aurélien Bompard

Fix doctests with PostgreSQL

- drop the tables after the whole testsuite has run
- don't expose DB ids in the doctests
- sort lists by list-id in the ListManager

------------- This line and the following will be ignored --------------

modified:
  src/mailman/app/docs/moderator.rst
  src/mailman/commands/docs/withlist.rst
  src/mailman/database/base.py
  src/mailman/model/docs/mailinglist.rst
  src/mailman/model/domain.py
  src/mailman/model/listmanager.py
  src/mailman/rest/docs/moderation.rst
  src/mailman/testing/layers.py

7288. By Aurélien Bompard

Add a cleanup instruction in a doctest

7289. By Aurélien Bompard

One more sorting issue

Unmerged revisions

7289. By Aurélien Bompard

One more sorting issue

7288. By Aurélien Bompard

Add a cleanup instruction in a doctest

7287. By Aurélien Bompard

Fix doctests with PostgreSQL

- drop the tables after the whole testsuite has run
- don't expose DB ids in the doctests
- sort lists by list-id in the ListManager

------------- This line and the following will be ignored --------------

modified:
  src/mailman/app/docs/moderator.rst
  src/mailman/commands/docs/withlist.rst
  src/mailman/database/base.py
  src/mailman/model/docs/mailinglist.rst
  src/mailman/model/domain.py
  src/mailman/model/listmanager.py
  src/mailman/rest/docs/moderation.rst
  src/mailman/testing/layers.py

7286. By Aurélien Bompard

Importer: encode_ascii_prefixes need to be converted to bool

7285. By Barry Warsaw

Use print() to smooth over the SA return of Python longs in PostgreSQL.

7284. By Barry Warsaw

Remove some unnecessary code.

7283. By Barry Warsaw

Move alembic settings to a separate alembic.cfg.

7282. By Barry Warsaw

Merge Aurélien Bompard's latest merge branch, with some cleaning up by Barry.

7281. By Barry Warsaw

Add the [logging.database] section and use it to configure the SQLAlchemy and
Alembic loggers.

7280. By Barry Warsaw

Remove some unused stuff.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'MANIFEST.in'
--- MANIFEST.in 2014-10-07 10:06:44 +0000
+++ MANIFEST.in 2014-10-31 13:23:25 +0000
@@ -1,4 +1,4 @@
1include *.py *.rc1include *.py *.rc *.mako
2include COPYING2include COPYING
3recursive-include .buildout *3recursive-include .buildout *
4recursive-include contrib *4recursive-include contrib *
@@ -6,12 +6,9 @@
6recursive-include data *6recursive-include data *
7global-include *.txt *.rst *.po *.mo *.cfg *.sql *.zcml *.html7global-include *.txt *.rst *.po *.mo *.cfg *.sql *.zcml *.html
8global-exclude *.egg-info8global-exclude *.egg-info
9exclude MANIFEST.in
10prune src/attic9prune src/attic
11prune src/web10prune src/web
12prune eggs11prune eggs
13prune parts12prune parts
14include MANIFEST.in13include MANIFEST.in
15include src/mailman/testing/config.pck14include src/mailman/testing/config.pck
16include src/mailman/database/alembic/script.py.mako
17include src/mailman/database/alembic/versions/*.py
1815
=== modified file 'src/mailman/app/docs/moderator.rst'
--- src/mailman/app/docs/moderator.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/app/docs/moderator.rst 2014-10-31 13:23:25 +0000
@@ -211,9 +211,8 @@
211 ...211 ...
212 ... Here's something important about our mailing list.212 ... Here's something important about our mailing list.
213 ... """)213 ... """)
214 >>> hold_message(mlist, msg, {}, 'Needs approval')214 >>> req_id = hold_message(mlist, msg, {}, 'Needs approval')
215 2215 >>> handle_message(mlist, req_id, Action.discard, forward=['zack@example.com'])
216 >>> handle_message(mlist, 2, Action.discard, forward=['zack@example.com'])
217216
218The forwarded message is in the virgin queue, destined for the moderator.217The forwarded message is in the virgin queue, destined for the moderator.
219::218::
@@ -243,10 +242,9 @@
243242
244 >>> from mailman.app.moderator import hold_subscription243 >>> from mailman.app.moderator import hold_subscription
245 >>> from mailman.interfaces.member import DeliveryMode244 >>> from mailman.interfaces.member import DeliveryMode
246 >>> hold_subscription(mlist,245 >>> req_id = hold_subscription(mlist,
247 ... 'fred@example.org', 'Fred Person',246 ... 'fred@example.org', 'Fred Person',
248 ... '{NONE}abcxyz', DeliveryMode.regular, 'en')247 ... '{NONE}abcxyz', DeliveryMode.regular, 'en')
249 2
250248
251249
252Disposing of membership change requests250Disposing of membership change requests
@@ -257,26 +255,26 @@
257simply defer a decision for now.255simply defer a decision for now.
258256
259 >>> from mailman.app.moderator import handle_subscription257 >>> from mailman.app.moderator import handle_subscription
260 >>> handle_subscription(mlist, 2, Action.defer)258 >>> handle_subscription(mlist, req_id, Action.defer)
261 >>> requests.get_request(2) is not None259 >>> requests.get_request(req_id) is not None
262 True260 True
263261
264The held subscription can also be discarded.262The held subscription can also be discarded.
265263
266 >>> handle_subscription(mlist, 2, Action.discard)264 >>> handle_subscription(mlist, req_id, Action.discard)
267 >>> print(requests.get_request(2))265 >>> print(requests.get_request(req_id))
268 None266 None
269267
270Gwen tries to subscribe to the mailing list, but...268Gwen tries to subscribe to the mailing list, but...
271269
272 >>> hold_subscription(mlist,270 >>> req_id = hold_subscription(mlist,
273 ... 'gwen@example.org', 'Gwen Person',271 ... 'gwen@example.org', 'Gwen Person',
274 ... '{NONE}zyxcba', DeliveryMode.regular, 'en')272 ... '{NONE}zyxcba', DeliveryMode.regular, 'en')
275 2273
276274
277...her request is rejected...275...her request is rejected...
278276
279 >>> handle_subscription(mlist, 2, Action.reject, 'This is a closed list')277 >>> handle_subscription(mlist, req_id, Action.reject, 'This is a closed list')
280 >>> messages = get_queue_messages('virgin')278 >>> messages = get_queue_messages('virgin')
281 >>> len(messages)279 >>> len(messages)
282 1280 1
@@ -304,14 +302,13 @@
304mailing list.302mailing list.
305303
306 >>> mlist.send_welcome_message = False304 >>> mlist.send_welcome_message = False
307 >>> hold_subscription(mlist,305 >>> req_id = hold_subscription(mlist,
308 ... 'herb@example.org', 'Herb Person',306 ... 'herb@example.org', 'Herb Person',
309 ... 'abcxyz', DeliveryMode.regular, 'en')307 ... 'abcxyz', DeliveryMode.regular, 'en')
310 2
311308
312The moderators accept the subscription request.309The moderators accept the subscription request.
313310
314 >>> handle_subscription(mlist, 2, Action.accept)311 >>> handle_subscription(mlist, req_id, Action.accept)
315312
316And now Herb is a member of the mailing list.313And now Herb is a member of the mailing list.
317314
@@ -328,29 +325,27 @@
328Herb now wants to leave the mailing list, but his request must be approved.325Herb now wants to leave the mailing list, but his request must be approved.
329326
330 >>> from mailman.app.moderator import hold_unsubscription327 >>> from mailman.app.moderator import hold_unsubscription
331 >>> hold_unsubscription(mlist, 'herb@example.org')328 >>> req_id = hold_unsubscription(mlist, 'herb@example.org')
332 2
333329
334As with subscription requests, the unsubscription request can be deferred.330As with subscription requests, the unsubscription request can be deferred.
335331
336 >>> from mailman.app.moderator import handle_unsubscription332 >>> from mailman.app.moderator import handle_unsubscription
337 >>> handle_unsubscription(mlist, 2, Action.defer)333 >>> handle_unsubscription(mlist, req_id, Action.defer)
338 >>> print(mlist.members.get_member('herb@example.org').address)334 >>> print(mlist.members.get_member('herb@example.org').address)
339 Herb Person <herb@example.org>335 Herb Person <herb@example.org>
340336
341The held unsubscription can also be discarded, and the member will remain337The held unsubscription can also be discarded, and the member will remain
342subscribed.338subscribed.
343339
344 >>> handle_unsubscription(mlist, 2, Action.discard)340 >>> handle_unsubscription(mlist, req_id, Action.discard)
345 >>> print(mlist.members.get_member('herb@example.org').address)341 >>> print(mlist.members.get_member('herb@example.org').address)
346 Herb Person <herb@example.org>342 Herb Person <herb@example.org>
347343
348The request can be rejected, in which case a message is sent to the member,344The request can be rejected, in which case a message is sent to the member,
349and the person remains a member of the mailing list.345and the person remains a member of the mailing list.
350346
351 >>> hold_unsubscription(mlist, 'herb@example.org')347 >>> req_id = hold_unsubscription(mlist, 'herb@example.org')
352 2348 >>> handle_unsubscription(mlist, req_id, Action.reject, 'No can do')
353 >>> handle_unsubscription(mlist, 2, Action.reject, 'No can do')
354 >>> print(mlist.members.get_member('herb@example.org').address)349 >>> print(mlist.members.get_member('herb@example.org').address)
355 Herb Person <herb@example.org>350 Herb Person <herb@example.org>
356351
@@ -381,10 +376,9 @@
381The unsubscription request can also be accepted. This removes the member from376The unsubscription request can also be accepted. This removes the member from
382the mailing list.377the mailing list.
383378
384 >>> hold_unsubscription(mlist, 'herb@example.org')379 >>> req_id = hold_unsubscription(mlist, 'herb@example.org')
385 2
386 >>> mlist.send_goodbye_message = False380 >>> mlist.send_goodbye_message = False
387 >>> handle_unsubscription(mlist, 2, Action.accept)381 >>> handle_unsubscription(mlist, req_id, Action.accept)
388 >>> print(mlist.members.get_member('herb@example.org'))382 >>> print(mlist.members.get_member('herb@example.org'))
389 None383 None
390384
@@ -403,9 +397,8 @@
403397
404Iris tries to subscribe to the mailing list.398Iris tries to subscribe to the mailing list.
405399
406 >>> hold_subscription(mlist, 'iris@example.org', 'Iris Person',400 >>> req_id = hold_subscription(mlist, 'iris@example.org', 'Iris Person',
407 ... 'password', DeliveryMode.regular, 'en')401 ... 'password', DeliveryMode.regular, 'en')
408 2
409402
410There's now a message in the virgin queue, destined for the list owner.403There's now a message in the virgin queue, destined for the list owner.
411404
@@ -429,8 +422,7 @@
429Similarly, the administrator gets notifications on unsubscription requests.422Similarly, the administrator gets notifications on unsubscription requests.
430Jeff is a member of the mailing list, and chooses to unsubscribe.423Jeff is a member of the mailing list, and chooses to unsubscribe.
431424
432 >>> hold_unsubscription(mlist, 'jeff@example.org')425 >>> unsub_req_id = hold_unsubscription(mlist, 'jeff@example.org')
433 3
434 >>> messages = get_queue_messages('virgin')426 >>> messages = get_queue_messages('virgin')
435 >>> len(messages)427 >>> len(messages)
436 1428 1
@@ -457,7 +449,7 @@
457449
458 >>> mlist.admin_notify_mchanges = True450 >>> mlist.admin_notify_mchanges = True
459 >>> mlist.admin_immed_notify = False451 >>> mlist.admin_immed_notify = False
460 >>> handle_subscription(mlist, 2, Action.accept)452 >>> handle_subscription(mlist, req_id, Action.accept)
461 >>> messages = get_queue_messages('virgin')453 >>> messages = get_queue_messages('virgin')
462 >>> len(messages)454 >>> len(messages)
463 1455 1
@@ -474,9 +466,8 @@
474Similarly when an unsubscription request is accepted, the administrators can466Similarly when an unsubscription request is accepted, the administrators can
475get a notification.467get a notification.
476468
477 >>> hold_unsubscription(mlist, 'iris@example.org')469 >>> req_id = hold_unsubscription(mlist, 'iris@example.org')
478 4470 >>> handle_unsubscription(mlist, req_id, Action.accept)
479 >>> handle_unsubscription(mlist, 4, Action.accept)
480 >>> messages = get_queue_messages('virgin')471 >>> messages = get_queue_messages('virgin')
481 >>> len(messages)472 >>> len(messages)
482 1473 1
@@ -498,10 +489,9 @@
498489
499 >>> mlist.admin_notify_mchanges = False490 >>> mlist.admin_notify_mchanges = False
500 >>> mlist.send_welcome_message = True491 >>> mlist.send_welcome_message = True
501 >>> hold_subscription(mlist, 'kate@example.org', 'Kate Person',492 >>> req_id = hold_subscription(mlist, 'kate@example.org', 'Kate Person',
502 ... 'password', DeliveryMode.regular, 'en')493 ... 'password', DeliveryMode.regular, 'en')
503 4494 >>> handle_subscription(mlist, req_id, Action.accept)
504 >>> handle_subscription(mlist, 4, Action.accept)
505 >>> messages = get_queue_messages('virgin')495 >>> messages = get_queue_messages('virgin')
506 >>> len(messages)496 >>> len(messages)
507 1497 1
@@ -523,9 +513,8 @@
523goodbye message.513goodbye message.
524514
525 >>> mlist.send_goodbye_message = True515 >>> mlist.send_goodbye_message = True
526 >>> hold_unsubscription(mlist, 'kate@example.org')516 >>> req_id = hold_unsubscription(mlist, 'kate@example.org')
527 4517 >>> handle_unsubscription(mlist, req_id, Action.accept)
528 >>> handle_unsubscription(mlist, 4, Action.accept)
529 >>> messages = get_queue_messages('virgin')518 >>> messages = get_queue_messages('virgin')
530 >>> len(messages)519 >>> len(messages)
531 1520 1
532521
=== modified file 'src/mailman/commands/docs/conf.rst'
--- src/mailman/commands/docs/conf.rst 2014-10-10 04:59:43 +0000
+++ src/mailman/commands/docs/conf.rst 2014-10-31 13:23:25 +0000
@@ -22,7 +22,7 @@
22command without any options.22command without any options.
2323
24 >>> command.process(FakeArgs)24 >>> command.process(FakeArgs)
25 [alembic] script_location: mailman.database:alembic25 [logging.archiver] path: mailman.log
26 ...26 ...
27 [passwords] password_length: 827 [passwords] password_length: 8
28 ...28 ...
@@ -43,7 +43,6 @@
43 >>> FakeArgs.section = None43 >>> FakeArgs.section = None
44 >>> FakeArgs.key = 'path'44 >>> FakeArgs.key = 'path'
45 >>> command.process(FakeArgs)45 >>> command.process(FakeArgs)
46 [logging.dbmigration] path: mailman.log
47 [logging.archiver] path: mailman.log46 [logging.archiver] path: mailman.log
48 [logging.locks] path: mailman.log47 [logging.locks] path: mailman.log
49 [logging.mischief] path: mailman.log48 [logging.mischief] path: mailman.log
5049
=== modified file 'src/mailman/commands/docs/withlist.rst'
--- src/mailman/commands/docs/withlist.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/commands/docs/withlist.rst 2014-10-31 13:23:25 +0000
@@ -90,13 +90,13 @@
90 >>> args.listname = '^.*example.com'90 >>> args.listname = '^.*example.com'
91 >>> command.process(args)91 >>> command.process(args)
92 The list's display name is Aardvark92 The list's display name is Aardvark
93 The list's display name is Badboys
93 The list's display name is Badger94 The list's display name is Badger
94 The list's display name is Badboys
9595
96 >>> args.listname = '^bad.*'96 >>> args.listname = '^bad.*'
97 >>> command.process(args)97 >>> command.process(args)
98 The list's display name is Badboys
98 The list's display name is Badger99 The list's display name is Badger
99 The list's display name is Badboys
100100
101 >>> args.listname = '^foo'101 >>> args.listname = '^foo'
102 >>> command.process(args)102 >>> command.process(args)
103103
=== added file 'src/mailman/config/alembic.cfg'
--- src/mailman/config/alembic.cfg 1970-01-01 00:00:00 +0000
+++ src/mailman/config/alembic.cfg 2014-10-31 13:23:25 +0000
@@ -0,0 +1,20 @@
1# Copyright (C) 2014 by the Free Software Foundation, Inc.
2#
3# This file is part of GNU Mailman.
4#
5# GNU Mailman is free software: you can redistribute it and/or modify it under
6# the terms of the GNU General Public License as published by the Free
7# Software Foundation, either version 3 of the License, or (at your option)
8# any later version.
9#
10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13# more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
17
18[alembic]
19# Path to Alembic migration scripts.
20script_location: mailman.database:alembic
021
=== modified file 'src/mailman/config/config.py'
--- src/mailman/config/config.py 2014-10-02 14:46:00 +0000
+++ src/mailman/config/config.py 2014-10-31 13:23:25 +0000
@@ -87,7 +87,6 @@
87 self.pipelines = {}87 self.pipelines = {}
88 self.commands = {}88 self.commands = {}
89 self.password_context = None89 self.password_context = None
90 self.initialized = False
9190
92 def _clear(self):91 def _clear(self):
93 """Clear the cached configuration variables."""92 """Clear the cached configuration variables."""
@@ -137,7 +136,6 @@
137 # Expand and set up all directories.136 # Expand and set up all directories.
138 self._expand_paths()137 self._expand_paths()
139 self.ensure_directories_exist()138 self.ensure_directories_exist()
140 self.initialized = True
141 notify(ConfigurationUpdatedEvent(self))139 notify(ConfigurationUpdatedEvent(self))
142140
143 def _expand_paths(self):141 def _expand_paths(self):
144142
=== modified file 'src/mailman/config/schema.cfg'
--- src/mailman/config/schema.cfg 2014-10-10 04:59:43 +0000
+++ src/mailman/config/schema.cfg 2014-10-31 13:23:25 +0000
@@ -226,6 +226,7 @@
226# - archiver -- All archiver output226# - archiver -- All archiver output
227# - bounce -- All bounce processing logs go here227# - bounce -- All bounce processing logs go here
228# - config -- Configuration issues228# - config -- Configuration issues
229# - database -- Database logging (SQLAlchemy and Alembic)
229# - debug -- Only used for development230# - debug -- Only used for development
230# - error -- All exceptions go to this log231# - error -- All exceptions go to this log
231# - fromusenet -- Information related to the Usenet to Mailman gateway232# - fromusenet -- Information related to the Usenet to Mailman gateway
@@ -237,8 +238,6 @@
237# - smtp-failure -- Unsuccessful SMTP activity238# - smtp-failure -- Unsuccessful SMTP activity
238# - subscribe -- Information about leaves/joins239# - subscribe -- Information about leaves/joins
239# - vette -- Message vetting information240# - vette -- Message vetting information
240# - database -- Database activity
241# - dbmigration -- Database migrations
242format: %(asctime)s (%(process)d) %(message)s241format: %(asctime)s (%(process)d) %(message)s
243datefmt: %b %d %H:%M:%S %Y242datefmt: %b %d %H:%M:%S %Y
244propagate: no243propagate: no
@@ -254,6 +253,8 @@
254253
255[logging.config]254[logging.config]
256255
256[logging.database]
257
257[logging.debug]258[logging.debug]
258path: debug.log259path: debug.log
259level: info260level: info
@@ -306,9 +307,6 @@
306[logging.database]307[logging.database]
307level: warn308level: warn
308309
309[logging.dbmigration]
310level: warn
311
312310
313[webservice]311[webservice]
314# The hostname at which admin web service resources are exposed.312# The hostname at which admin web service resources are exposed.
@@ -645,7 +643,3 @@
645 CC X-Original-CC643 CC X-Original-CC
646 Content-Transfer-Encoding X-Original-Content-Transfer-Encoding644 Content-Transfer-Encoding X-Original-Content-Transfer-Encoding
647 MIME-Version X-MIME-Version645 MIME-Version X-MIME-Version
648
649[alembic]
650# path to migration scripts
651script_location = mailman.database:alembic
652646
=== modified file 'src/mailman/core/docs/runner.rst'
--- src/mailman/core/docs/runner.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/core/docs/runner.rst 2014-10-31 13:23:25 +0000
@@ -73,3 +73,5 @@
73 version : 373 version : 3
7474
75XXX More of the Runner API should be tested.75XXX More of the Runner API should be tested.
76
77 >>> config.pop('test-runner')
7678
=== modified file 'src/mailman/core/logging.py'
--- src/mailman/core/logging.py 2014-10-07 09:19:20 +0000
+++ src/mailman/core/logging.py 2014-10-31 13:23:25 +0000
@@ -104,6 +104,27 @@
104104
105105
106106
107107
108def _init_logger(propagate, sub_name, log, logger_config):
109 # Get settings from log configuration file (or defaults).
110 log_format = logger_config.format
111 log_datefmt = logger_config.datefmt
112 # Propagation to the root logger is how we handle logging to stderr
113 # when the runners are not run as a subprocess of 'bin/mailman start'.
114 log.propagate = (as_boolean(logger_config.propagate)
115 if propagate is None else propagate)
116 # Set the logger's level.
117 log.setLevel(as_log_level(logger_config.level))
118 # Create a formatter for this logger, then a handler, and link the
119 # formatter to the handler.
120 formatter = logging.Formatter(fmt=log_format, datefmt=log_datefmt)
121 path_str = logger_config.path
122 path_abs = os.path.normpath(os.path.join(config.LOG_DIR, path_str))
123 handler = ReopenableFileHandler(sub_name, path_abs)
124 _handlers[sub_name] = handler
125 handler.setFormatter(formatter)
126 log.addHandler(handler)
127
128
108def initialize(propagate=None):129def initialize(propagate=None):
109 """Initialize all logs.130 """Initialize all logs.
110131
@@ -126,32 +147,18 @@
126 continue147 continue
127 if sub_name == 'locks':148 if sub_name == 'locks':
128 log = logging.getLogger('flufl.lock')149 log = logging.getLogger('flufl.lock')
129 elif sub_name == 'database':150 if sub_name == 'database':
151 # Set both the SQLAlchemy and Alembic logs to the mailman.database
152 # log configuration, essentially ignoring the alembic.cfg
153 # settings. Do the SQLAlchemy one first, then let the Alembic one
154 # fall through to the common code path.
130 log = logging.getLogger('sqlalchemy')155 log = logging.getLogger('sqlalchemy')
131 elif sub_name == 'dbmigration':156 _init_logger(propagate, sub_name, log, logger_config)
132 log = logging.getLogger('alembic')157 log = logging.getLogger('alembic')
133 else:158 else:
134 logger_name = 'mailman.' + sub_name159 logger_name = 'mailman.' + sub_name
135 log = logging.getLogger(logger_name)160 log = logging.getLogger(logger_name)
136 # Get settings from log configuration file (or defaults).161 _init_logger(propagate, sub_name, log, logger_config)
137 log_format = logger_config.format
138 log_datefmt = logger_config.datefmt
139 # Propagation to the root logger is how we handle logging to stderr
140 # when the runners are not run as a subprocess of 'bin/mailman start'.
141 log.propagate = (as_boolean(logger_config.propagate)
142 if propagate is None else propagate)
143 # Set the logger's level.
144 log.setLevel(as_log_level(logger_config.level))
145 # Create a formatter for this logger, then a handler, and link the
146 # formatter to the handler.
147 formatter = logging.Formatter(fmt=log_format, datefmt=log_datefmt)
148 path_str = logger_config.path
149 path_abs = os.path.normpath(os.path.join(config.LOG_DIR, path_str))
150 handler = ReopenableFileHandler(sub_name, path_abs)
151 _handlers[sub_name] = handler
152 handler.setFormatter(formatter)
153 log.addHandler(handler)
154
155162
156163
157164
158def reopen():165def reopen():
159166
=== modified file 'src/mailman/database/alembic/__init__.py'
--- src/mailman/database/alembic/__init__.py 2014-10-10 04:59:43 +0000
+++ src/mailman/database/alembic/__init__.py 2014-10-31 13:23:25 +0000
@@ -15,18 +15,18 @@
15# You should have received a copy of the GNU General Public License along with15# You should have received a copy of the GNU General Public License along with
16# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.16# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
1717
18"Alembic config init."18"""Alembic configuration initization."""
1919
20from __future__ import absolute_import, print_function, unicode_literals20from __future__ import absolute_import, print_function, unicode_literals
2121
22__metaclass__ = type22__metaclass__ = type
23__all__ = [23__all__ = [
24 'alembic_cfg'24 'alembic_cfg',
25]25 ]
2626
2727
28from alembic.config import Config28from alembic.config import Config
29from mailman.utilities.modules import expand_path29from mailman.utilities.modules import expand_path
3030
3131
32alembic_cfg=Config(expand_path("python:mailman.config.schema"))32alembic_cfg = Config(expand_path('python:mailman.config.alembic'))
3333
=== modified file 'src/mailman/database/alembic/env.py'
--- src/mailman/database/alembic/env.py 2014-10-10 04:59:43 +0000
+++ src/mailman/database/alembic/env.py 2014-10-31 13:23:25 +0000
@@ -30,15 +30,10 @@
30from contextlib import closing30from contextlib import closing
31from sqlalchemy import create_engine31from sqlalchemy import create_engine
3232
33from mailman.core import initialize
34from mailman.config import config33from mailman.config import config
35from mailman.database.alembic import alembic_cfg
36from mailman.database.model import Model34from mailman.database.model import Model
37from mailman.utilities.string import expand35from mailman.utilities.string import expand
3836
39if not config.initialized:
40 initialize.initialize_1(context.config.config_file_name)
41
4237
4338
4439
45def run_migrations_offline():40def run_migrations_offline():
4641
=== modified file 'src/mailman/database/alembic/versions/51b7f92bd06c_initial.py'
--- src/mailman/database/alembic/versions/51b7f92bd06c_initial.py 2014-10-10 16:58:48 +0000
+++ src/mailman/database/alembic/versions/51b7f92bd06c_initial.py 2014-10-31 13:23:25 +0000
@@ -1,34 +1,66 @@
1"""initial1# Copyright (C) 2014 by the Free Software Foundation, Inc.
2#
3# This file is part of GNU Mailman.
4#
5# GNU Mailman is free software: you can redistribute it and/or modify it under
6# the terms of the GNU General Public License as published by the Free
7# Software Foundation, either version 3 of the License, or (at your option)
8# any later version.
9#
10# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13# more details.
14#
15# You should have received a copy of the GNU General Public License along with
16# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
17
18"""Initial migration.
19
20This empty migration file makes sure there is always an alembic_version
21in the database. As a consequence, if the database version is reported
22as None, it means the database needs to be created from scratch with
23SQLAlchemy itself.
24
25It also removes schema items left over from Storm.
226
3Revision ID: 51b7f92bd06c27Revision ID: 51b7f92bd06c
4Revises: None28Revises: None
5Create Date: 2014-10-10 09:53:35.62447229Create Date: 2014-10-10 09:53:35.624472
6
7"""30"""
831
9# revision identifiers, used by Alembic.32from __future__ import absolute_import, print_function, unicode_literals
33
34__metaclass__ = type
35__all__ = [
36 'downgrade',
37 'upgrade',
38 ]
39
40
41from alembic import op
42import sqlalchemy as sa
43
44
45# Revision identifiers, used by Alembic.
10revision = '51b7f92bd06c'46revision = '51b7f92bd06c'
11down_revision = None47down_revision = None
1248
13from alembic import op
14import sqlalchemy as sa
15
1649
17def upgrade():50def upgrade():
18 ### commands auto generated by Alembic - please adjust! ###
19 op.drop_table('version')51 op.drop_table('version')
20 if op.get_bind().dialect.name != "sqlite":52 if op.get_bind().dialect.name != 'sqlite':
21 # SQLite does not support dropping columns53 # SQLite does not support dropping columns.
22 op.drop_column('mailinglist', 'acceptable_aliases_id')54 op.drop_column('mailinglist', 'acceptable_aliases_id')
23 op.create_index(op.f('ix_user__user_id'), 'user', ['_user_id'], unique=False)55 op.create_index(op.f('ix_user__user_id'), 'user',
56 ['_user_id'], unique=False)
24 op.drop_index('ix_user_user_id', table_name='user')57 op.drop_index('ix_user_user_id', table_name='user')
25 ### end Alembic commands ###
2658
2759
28def downgrade():60def downgrade():
29 ### commands auto generated by Alembic - please adjust! ###
30 op.create_table('version')61 op.create_table('version')
31 op.create_index('ix_user_user_id', 'user', ['_user_id'], unique=False)62 op.create_index('ix_user_user_id', 'user', ['_user_id'], unique=False)
32 op.drop_index(op.f('ix_user__user_id'), table_name='user')63 op.drop_index(op.f('ix_user__user_id'), table_name='user')
33 op.add_column('mailinglist', sa.Column('acceptable_aliases_id', sa.INTEGER(), nullable=True))64 op.add_column(
34 ### end Alembic commands ###65 'mailinglist',
66 sa.Column('acceptable_aliases_id', sa.INTEGER(), nullable=True))
3567
=== modified file 'src/mailman/database/base.py'
--- src/mailman/database/base.py 2014-10-06 13:58:58 +0000
+++ src/mailman/database/base.py 2014-10-31 13:23:25 +0000
@@ -25,7 +25,6 @@
2525
26import logging26import logging
2727
28from alembic import command
29from sqlalchemy import create_engine28from sqlalchemy import create_engine
30from sqlalchemy.orm import sessionmaker29from sqlalchemy.orm import sessionmaker
31from zope.interface import implementer30from zope.interface import implementer
@@ -115,3 +114,8 @@
115 session = sessionmaker(bind=self.engine)114 session = sessionmaker(bind=self.engine)
116 self.store = session()115 self.store = session()
117 self.store.commit()116 self.store.commit()
117
118 def destroy(self):
119 """Drop all database tables"""
120 from mailman.database.model import Model
121 Model.metadata.drop_all(self.engine)
118122
=== modified file 'src/mailman/database/factory.py'
--- src/mailman/database/factory.py 2014-10-10 16:44:01 +0000
+++ src/mailman/database/factory.py 2014-10-31 13:23:25 +0000
@@ -28,8 +28,8 @@
2828
29import os29import os
30import types30import types
31import alembic.command
3132
32from alembic import command
33from alembic.migration import MigrationContext33from alembic.migration import MigrationContext
34from alembic.script import ScriptDirectory34from alembic.script import ScriptDirectory
35from flufl.lock import Lock35from flufl.lock import Lock
@@ -38,10 +38,14 @@
38from zope.interface.verify import verifyObject38from zope.interface.verify import verifyObject
3939
40from mailman.config import config40from mailman.config import config
41from mailman.database.alembic import alembic_cfg
41from mailman.database.model import Model42from mailman.database.model import Model
42from mailman.database.alembic import alembic_cfg43from mailman.interfaces.database import (
43from mailman.interfaces.database import IDatabase, IDatabaseFactory44 DatabaseError, IDatabase, IDatabaseFactory)
44from mailman.utilities.modules import call_name, expand_path45from mailman.utilities.modules import call_name
46
47
48LAST_STORM_SCHEMA_VERSION = '20130406000000'
4549
4650
4751
4852
@@ -57,67 +61,58 @@
57 database = call_name(database_class)61 database = call_name(database_class)
58 verifyObject(IDatabase, database)62 verifyObject(IDatabase, database)
59 database.initialize()63 database.initialize()
60 schema_mgr = SchemaManager(database)64 SchemaManager(database).setup_database()
61 schema_mgr.setup_db()
62 database.commit()65 database.commit()
63 return database66 return database
6467
6568
6669
6770
68class SchemaManager:71class SchemaManager:
6972 "Manage schema migrations."""
70 LAST_STORM_SCHEMA_VERSION = '20130406000000'
7173
72 def __init__(self, database):74 def __init__(self, database):
73 self.database = database75 self._database = database
74 self.script = ScriptDirectory.from_config(alembic_cfg)76 self._script = ScriptDirectory.from_config(alembic_cfg)
7577
76 def get_storm_schema_version(self):78 def _get_storm_schema_version(self):
77 md = MetaData()79 metadata = MetaData()
78 md.reflect(bind=self.database.engine)80 metadata.reflect(bind=self._database.engine)
79 if "version" not in md.tables:81 if 'version' not in metadata.tables:
82 # There are no Storm artifacts left.
80 return None83 return None
81 Version = md.tables["version"]84 Version = metadata.tables['version']
82 last_version = self.database.store.query(Version.c.version).filter(85 last_version = self._database.store.query(Version.c.version).filter(
83 Version.c.component == "schema"86 Version.c.component == 'schema'
84 ).order_by(Version.c.version.desc()).first()87 ).order_by(Version.c.version.desc()).first()
85 # Don't leave open transactions or they will block any schema change88 # Don't leave open transactions or they will block any schema change.
86 self.database.commit()89 self._database.commit()
87 return last_version90 return last_version
8891
89 def _create(self):92 def setup_database(self):
90 # initial DB creation93 context = MigrationContext.configure(self._database.store.connection())
91 Model.metadata.create_all(self.database.engine)
92 self.database.commit()
93 command.stamp(alembic_cfg, "head")
94
95 def _upgrade(self):
96 command.upgrade(alembic_cfg, "head")
97
98 def setup_db(self):
99 context = MigrationContext.configure(self.database.store.connection())
100 current_rev = context.get_current_revision()94 current_rev = context.get_current_revision()
101 head_rev = self.script.get_current_head()95 head_rev = self._script.get_current_head()
102 if current_rev == head_rev:96 if current_rev == head_rev:
103 return head_rev # already at the latest revision, nothing to do97 # We're already at the latest revision so there's nothing to do.
104 if current_rev == None:98 return head_rev
105 # no alembic information99 if current_rev is None:
106 storm_version = self.get_storm_schema_version()100 # No Alembic information is available.
101 storm_version = self._get_storm_schema_version()
107 if storm_version is None:102 if storm_version is None:
108 # initial DB creation103 # Initial database creation.
109 self._create()104 Model.metadata.create_all(self._database.engine)
105 self._database.commit()
106 alembic.command.stamp(alembic_cfg, 'head')
110 else:107 else:
111 # DB from a previous version managed by Storm108 # The database was previously managed by Storm.
112 if storm_version.version < self.LAST_STORM_SCHEMA_VERSION:109 if storm_version.version < LAST_STORM_SCHEMA_VERSION:
113 raise RuntimeError(110 raise DatabaseError(
114 "Upgrading while skipping beta version is "111 'Upgrades skipping beta versions is not supported.')
115 "unsupported, please install the previous "112 # Run migrations to remove the Storm-specific table and upgrade
116 "Mailman beta release")113 # to SQLAlchemy and Alembic.
117 # Run migrations to remove the Storm-specific table and114 alembic.command.upgrade(alembic_cfg, 'head')
118 # upgrade to SQLAlchemy & Alembic
119 self._upgrade()
120 elif current_rev != head_rev:115 elif current_rev != head_rev:
121 self._upgrade()116 alembic.command.upgrade(alembic_cfg, 'head')
122 return head_rev117 return head_rev
123118
124119
125120
=== modified file 'src/mailman/database/tests/test_factory.py'
--- src/mailman/database/tests/test_factory.py 2014-10-10 16:58:48 +0000
+++ src/mailman/database/tests/test_factory.py 2014-10-31 13:23:25 +0000
@@ -21,24 +21,24 @@
2121
22__metaclass__ = type22__metaclass__ = type
23__all__ = [23__all__ = [
24 'TestSchemaManager',
24 ]25 ]
2526
2627
27import unittest28import unittest
28import types
29
30import alembic.command29import alembic.command
31from mock import Mock30
31from mock import patch
32from sqlalchemy import MetaData, Table, Column, Integer, Unicode32from sqlalchemy import MetaData, Table, Column, Integer, Unicode
33from sqlalchemy.exc import ProgrammingError, OperationalError
33from sqlalchemy.schema import Index34from sqlalchemy.schema import Index
34from sqlalchemy.exc import ProgrammingError, OperationalError
3535
36from mailman.config import config36from mailman.config import config
37from mailman.testing.layers import ConfigLayer
38from mailman.database.factory import SchemaManager, _reset
39from mailman.database.sqlite import SQLiteDatabase
40from mailman.database.alembic import alembic_cfg37from mailman.database.alembic import alembic_cfg
38from mailman.database.factory import LAST_STORM_SCHEMA_VERSION, SchemaManager
41from mailman.database.model import Model39from mailman.database.model import Model
40from mailman.interfaces.database import DatabaseError
41from mailman.testing.layers import ConfigLayer
4242
4343
4444
4545
@@ -47,116 +47,114 @@
47 layer = ConfigLayer47 layer = ConfigLayer
4848
49 def setUp(self):49 def setUp(self):
50 # Drop the existing database50 # Drop the existing database.
51 Model.metadata.drop_all(config.db.engine)51 Model.metadata.drop_all(config.db.engine)
52 md = MetaData()52 md = MetaData()
53 md.reflect(bind=config.db.engine)53 md.reflect(bind=config.db.engine)
54 for tablename in ("alembic_version", "version"):54 for tablename in ('alembic_version', 'version'):
55 if tablename in md.tables:55 if tablename in md.tables:
56 md.tables[tablename].drop(config.db.engine)56 md.tables[tablename].drop(config.db.engine)
57 self.schema_mgr = SchemaManager(config.db)57 self.schema_mgr = SchemaManager(config.db)
5858
59 def tearDown(self):59 def tearDown(self):
60 self._drop_storm_database()60 self._drop_storm_database()
61 # Restore a virgin DB61 # Restore a virgin database.
62 Model.metadata.create_all(config.db.engine)62 Model.metadata.create_all(config.db.engine)
6363
64
65 def _table_exists(self, tablename):64 def _table_exists(self, tablename):
66 md = MetaData()65 md = MetaData()
67 md.reflect(bind=config.db.engine)66 md.reflect(bind=config.db.engine)
68 return tablename in md.tables67 return tablename in md.tables
6968
70 def _create_storm_database(self, revision):69 def _create_storm_database(self, revision):
71 version_table = Table("version", Model.metadata,70 version_table = Table(
72 Column("id", Integer, primary_key=True),71 'version', Model.metadata,
73 Column("component", Unicode),72 Column('id', Integer, primary_key=True),
74 Column("version", Unicode),73 Column('component', Unicode),
75 )74 Column('version', Unicode),
75 )
76 version_table.create(config.db.engine)76 version_table.create(config.db.engine)
77 config.db.store.execute(version_table.insert().values(77 config.db.store.execute(version_table.insert().values(
78 component='schema', version=revision))78 component='schema', version=revision))
79 config.db.commit()79 config.db.commit()
80 # Other Storm specific changes, those SQL statements hopefully work on80 # Other Storm specific changes, those SQL statements hopefully work on
81 # all DB engines...81 # all DB engines...
82 config.db.engine.execute(82 config.db.engine.execute(
83 "ALTER TABLE mailinglist ADD COLUMN acceptable_aliases_id INT")83 'ALTER TABLE mailinglist ADD COLUMN acceptable_aliases_id INT')
84 Index("ix_user__user_id").drop(bind=config.db.engine)84 Index('ix_user__user_id').drop(bind=config.db.engine)
85 # Don't pollute our main metadata object, create a new one85 # Don't pollute our main metadata object, create a new one.
86 md = MetaData()86 md = MetaData()
87 user_table = Model.metadata.tables["user"].tometadata(md)87 user_table = Model.metadata.tables['user'].tometadata(md)
88 Index("ix_user_user_id", user_table.c._user_id88 Index('ix_user_user_id', user_table.c._user_id).create(
89 ).create(bind=config.db.engine)89 bind=config.db.engine)
90 config.db.commit()90 config.db.commit()
9191
92 def _drop_storm_database(self):92 def _drop_storm_database(self):
93 """93 """Remove the leftovers from a Storm DB.
94 Remove the leftovers from a Storm DB.94
95 (you must issue a drop_all() afterwards)95 A drop_all() must be issued afterwards.
96 """96 """
97 if "version" in Model.metadata.tables:97 if 'version' in Model.metadata.tables:
98 version = Model.metadata.tables["version"]98 version = Model.metadata.tables['version']
99 version.drop(config.db.engine, checkfirst=True)99 version.drop(config.db.engine, checkfirst=True)
100 Model.metadata.remove(version)100 Model.metadata.remove(version)
101 try:101 try:
102 Index("ix_user_user_id").drop(bind=config.db.engine)102 Index('ix_user_user_id').drop(bind=config.db.engine)
103 except (ProgrammingError, OperationalError) as e:103 except (ProgrammingError, OperationalError):
104 # non-existant (PGSQL raises a ProgrammingError, while SQLite104 # Nonexistent. PostgreSQL raises a ProgrammingError, while SQLite
105 # raises an OperationalError)105 # raises an OperationalError.
106 pass106 pass
107 config.db.commit()107 config.db.commit()
108108
109109 def test_current_database(self):
110 def test_current_db(self):110 # The database is already at the latest version.
111 """The database is already at the latest version"""111 alembic.command.stamp(alembic_cfg, 'head')
112 alembic.command.stamp(alembic_cfg, "head")112 with patch('alembic.command') as alembic_command:
113 self.schema_mgr._create = Mock()113 self.schema_mgr.setup_database()
114 self.schema_mgr._upgrade = Mock()114 self.assertFalse(alembic_command.stamp.called)
115 self.schema_mgr.setup_db()115 self.assertFalse(alembic_command.upgrade.called)
116 self.assertFalse(self.schema_mgr._create.called)116
117 self.assertFalse(self.schema_mgr._upgrade.called)117 @patch('alembic.command')
118118 def test_initial(self, alembic_command):
119 def test_initial(self):119 # No existing database.
120 """No existing database"""120 self.assertFalse(self._table_exists('mailinglist'))
121 self.assertFalse(self._table_exists("mailinglist"))121 self.assertFalse(self._table_exists('alembic_version'))
122 self.assertFalse(self._table_exists("alembic_version"))122 self.schema_mgr.setup_database()
123 self.schema_mgr._upgrade = Mock()123 self.assertFalse(alembic_command.upgrade.called)
124 self.schema_mgr.setup_db()124 self.assertTrue(self._table_exists('mailinglist'))
125 self.assertFalse(self.schema_mgr._upgrade.called)125 self.assertTrue(self._table_exists('alembic_version'))
126 self.assertTrue(self._table_exists("mailinglist"))126
127 self.assertTrue(self._table_exists("alembic_version"))127 @patch('alembic.command.stamp')
128128 def test_storm(self, alembic_command_stamp):
129 def test_storm(self):129 # Existing Storm database.
130 """Existing Storm database"""130 Model.metadata.create_all(config.db.engine)
131 Model.metadata.create_all(config.db.engine)131 self._create_storm_database(LAST_STORM_SCHEMA_VERSION)
132 self._create_storm_database(132 self.schema_mgr.setup_database()
133 self.schema_mgr.LAST_STORM_SCHEMA_VERSION)133 self.assertFalse(alembic_command_stamp.called)
134 self.schema_mgr._create = Mock()134 self.assertTrue(
135 self.schema_mgr.setup_db()135 self._table_exists('mailinglist')
136 self.assertFalse(self.schema_mgr._create.called)136 and self._table_exists('alembic_version')
137 self.assertTrue(self._table_exists("mailinglist")137 and not self._table_exists('version'))
138 and self._table_exists("alembic_version")138
139 and not self._table_exists("version"))139 @patch('alembic.command')
140140 def test_old_storm(self, alembic_command):
141 def test_old_storm(self):141 # Existing Storm database in an old version.
142 """Existing Storm database in an old version"""142 Model.metadata.create_all(config.db.engine)
143 Model.metadata.create_all(config.db.engine)143 self._create_storm_database('001')
144 self._create_storm_database("001")144 self.assertRaises(DatabaseError, self.schema_mgr.setup_database)
145 self.schema_mgr._create = Mock()145 self.assertFalse(alembic_command.stamp.called)
146 self.assertRaises(RuntimeError, self.schema_mgr.setup_db)146 self.assertFalse(alembic_command.upgrade.called)
147 self.assertFalse(self.schema_mgr._create.called)
148147
149 def test_old_db(self):148 def test_old_db(self):
150 """The database is in an old revision, must upgrade"""149 # The database is in an old revision, must upgrade.
151 alembic.command.stamp(alembic_cfg, "head")150 alembic.command.stamp(alembic_cfg, 'head')
152 md = MetaData()151 md = MetaData()
153 md.reflect(bind=config.db.engine)152 md.reflect(bind=config.db.engine)
154 config.db.store.execute(md.tables["alembic_version"].delete())153 config.db.store.execute(md.tables['alembic_version'].delete())
155 config.db.store.execute(md.tables["alembic_version"].insert().values(154 config.db.store.execute(md.tables['alembic_version'].insert().values(
156 version_num="dummyrevision"))155 version_num='dummyrevision'))
157 config.db.commit()156 config.db.commit()
158 self.schema_mgr._create = Mock()157 with patch('alembic.command') as alembic_command:
159 self.schema_mgr._upgrade = Mock()158 self.schema_mgr.setup_database()
160 self.schema_mgr.setup_db()159 self.assertFalse(alembic_command.stamp.called)
161 self.assertFalse(self.schema_mgr._create.called)160 self.assertTrue(alembic_command.upgrade.called)
162 self.assertTrue(self.schema_mgr._upgrade.called)
163161
=== modified file 'src/mailman/handlers/docs/owner-recips.rst'
--- src/mailman/handlers/docs/owner-recips.rst 2012-03-23 20:34:54 +0000
+++ src/mailman/handlers/docs/owner-recips.rst 2014-10-31 13:23:25 +0000
@@ -41,7 +41,7 @@
41 >>> handler.process(mlist_1, msg, msgdata)41 >>> handler.process(mlist_1, msg, msgdata)
42 >>> dump_list(msgdata['recipients'])42 >>> dump_list(msgdata['recipients'])
43 bart@example.com43 bart@example.com
44 44
45If Bart also disables his owner delivery, then no one could contact the list's45If Bart also disables his owner delivery, then no one could contact the list's
46owners. Since this is unacceptable, the site owner is used as a fallback.46owners. Since this is unacceptable, the site owner is used as a fallback.
4747
@@ -55,7 +55,7 @@
55a fallback.55a fallback.
5656
57 >>> mlist_2 = create_list('beta@example.com')57 >>> mlist_2 = create_list('beta@example.com')
58 >>> mlist_2.administrators.member_count58 >>> print(mlist_2.administrators.member_count)
59 059 0
60 >>> msgdata = {}60 >>> msgdata = {}
61 >>> handler.process(mlist_2, msg, msgdata)61 >>> handler.process(mlist_2, msg, msgdata)
6262
=== modified file 'src/mailman/model/docs/autorespond.rst'
--- src/mailman/model/docs/autorespond.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/model/docs/autorespond.rst 2014-10-31 13:23:25 +0000
@@ -37,34 +37,34 @@
37 ... 'aperson@example.com')37 ... 'aperson@example.com')
3838
39 >>> from mailman.interfaces.autorespond import Response39 >>> from mailman.interfaces.autorespond import Response
40 >>> response_set.todays_count(address, Response.hold)40 >>> print(response_set.todays_count(address, Response.hold))
41 041 0
42 >>> response_set.todays_count(address, Response.command)42 >>> print(response_set.todays_count(address, Response.command))
43 043 0
4444
45Using the response set, we can record that a hold response is sent to the45Using the response set, we can record that a hold response is sent to the
46address.46address.
4747
48 >>> response_set.response_sent(address, Response.hold)48 >>> response_set.response_sent(address, Response.hold)
49 >>> response_set.todays_count(address, Response.hold)49 >>> print(response_set.todays_count(address, Response.hold))
50 150 1
51 >>> response_set.todays_count(address, Response.command)51 >>> print(response_set.todays_count(address, Response.command))
52 052 0
5353
54We can also record that a command response was sent.54We can also record that a command response was sent.
5555
56 >>> response_set.response_sent(address, Response.command)56 >>> response_set.response_sent(address, Response.command)
57 >>> response_set.todays_count(address, Response.hold)57 >>> print(response_set.todays_count(address, Response.hold))
58 158 1
59 >>> response_set.todays_count(address, Response.command)59 >>> print(response_set.todays_count(address, Response.command))
60 160 1
6161
62Let's send one more.62Let's send one more.
6363
64 >>> response_set.response_sent(address, Response.command)64 >>> response_set.response_sent(address, Response.command)
65 >>> response_set.todays_count(address, Response.hold)65 >>> print(response_set.todays_count(address, Response.hold))
66 166 1
67 >>> response_set.todays_count(address, Response.command)67 >>> print(response_set.todays_count(address, Response.command))
68 268 2
6969
70Now the day flips over and all the counts reset.70Now the day flips over and all the counts reset.
@@ -73,9 +73,9 @@
73 >>> from mailman.utilities.datetime import factory73 >>> from mailman.utilities.datetime import factory
74 >>> factory.fast_forward()74 >>> factory.fast_forward()
7575
76 >>> response_set.todays_count(address, Response.hold)76 >>> print(response_set.todays_count(address, Response.hold))
77 077 0
78 >>> response_set.todays_count(address, Response.command)78 >>> print(response_set.todays_count(address, Response.command))
79 079 0
8080
8181
@@ -110,7 +110,7 @@
110110
111 >>> address = getUtility(IUserManager).create_address(111 >>> address = getUtility(IUserManager).create_address(
112 ... 'bperson@example.com')112 ... 'bperson@example.com')
113 >>> response_set.todays_count(address, Response.command)113 >>> print(response_set.todays_count(address, Response.command))
114 0114 0
115 >>> print(response_set.last_response(address, Response.command))115 >>> print(response_set.last_response(address, Response.command))
116 None116 None
117117
=== modified file 'src/mailman/model/docs/mailinglist.rst'
--- src/mailman/model/docs/mailinglist.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/model/docs/mailinglist.rst 2014-10-31 13:23:25 +0000
@@ -50,7 +50,7 @@
5050
51Both addresses appear on the roster of members.51Both addresses appear on the roster of members.
5252
53 >>> for member in mlist.members.members:53 >>> for member in sorted(mlist.members.members, key=lambda m: m.address.email):
54 ... print(member)54 ... print(member)
55 <Member: aperson@example.com on aardvark@example.com as MemberRole.member>55 <Member: aperson@example.com on aardvark@example.com as MemberRole.member>
56 <Member: bperson@example.com on aardvark@example.com as MemberRole.member>56 <Member: bperson@example.com on aardvark@example.com as MemberRole.member>
@@ -72,7 +72,7 @@
72an owner and a moderator.72an owner and a moderator.
73::73::
7474
75 >>> for member in mlist.owners.members:75 >>> for member in sorted(mlist.owners.members, key=lambda m: m.address.email):
76 ... print(member)76 ... print(member)
77 <Member: aperson@example.com on aardvark@example.com as MemberRole.owner>77 <Member: aperson@example.com on aardvark@example.com as MemberRole.owner>
78 <Member: cperson@example.com on aardvark@example.com as MemberRole.owner>78 <Member: cperson@example.com on aardvark@example.com as MemberRole.owner>
@@ -87,13 +87,13 @@
87::87::
8888
89 >>> roster = mlist.get_roster(MemberRole.member)89 >>> roster = mlist.get_roster(MemberRole.member)
90 >>> for member in roster.members:90 >>> for member in sorted(roster.members, key=lambda m: m.address.email):
91 ... print(member)91 ... print(member)
92 <Member: aperson@example.com on aardvark@example.com as MemberRole.member>92 <Member: aperson@example.com on aardvark@example.com as MemberRole.member>
93 <Member: bperson@example.com on aardvark@example.com as MemberRole.member>93 <Member: bperson@example.com on aardvark@example.com as MemberRole.member>
9494
95 >>> roster = mlist.get_roster(MemberRole.owner)95 >>> roster = mlist.get_roster(MemberRole.owner)
96 >>> for member in roster.members:96 >>> for member in sorted(roster.members, key=lambda m: m.address.email):
97 ... print(member)97 ... print(member)
98 <Member: aperson@example.com on aardvark@example.com as MemberRole.owner>98 <Member: aperson@example.com on aardvark@example.com as MemberRole.owner>
99 <Member: cperson@example.com on aardvark@example.com as MemberRole.owner>99 <Member: cperson@example.com on aardvark@example.com as MemberRole.owner>
@@ -122,7 +122,7 @@
122 >>> mlist.subscribe(user)122 >>> mlist.subscribe(user)
123 <Member: Dave Person <dperson@example.com> on aardvark@example.com123 <Member: Dave Person <dperson@example.com> on aardvark@example.com
124 as MemberRole.member>124 as MemberRole.member>
125 >>> for member in mlist.members.members:125 >>> for member in sorted(mlist.members.members, key=lambda m: m.address.email):
126 ... print(member)126 ... print(member)
127 <Member: aperson@example.com on aardvark@example.com as MemberRole.member>127 <Member: aperson@example.com on aardvark@example.com as MemberRole.member>
128 <Member: bperson@example.com on aardvark@example.com as MemberRole.member>128 <Member: bperson@example.com on aardvark@example.com as MemberRole.member>
@@ -133,7 +133,7 @@
133 >>> new_address.verified_on = now()133 >>> new_address.verified_on = now()
134 >>> user.preferred_address = new_address134 >>> user.preferred_address = new_address
135135
136 >>> for member in mlist.members.members:136 >>> for member in sorted(mlist.members.members, key=lambda m: m.address.email):
137 ... print(member)137 ... print(member)
138 <Member: aperson@example.com on aardvark@example.com as MemberRole.member>138 <Member: aperson@example.com on aardvark@example.com as MemberRole.member>
139 <Member: bperson@example.com on aardvark@example.com as MemberRole.member>139 <Member: bperson@example.com on aardvark@example.com as MemberRole.member>
140140
=== modified file 'src/mailman/model/docs/requests.rst'
--- src/mailman/model/docs/requests.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/model/docs/requests.rst 2014-10-31 13:23:25 +0000
@@ -35,7 +35,7 @@
3535
36The list's requests database starts out empty.36The list's requests database starts out empty.
3737
38 >>> requests.count38 >>> print(requests.count)
39 039 0
40 >>> dump_list(requests.held_requests)40 >>> dump_list(requests.held_requests)
41 *Empty*41 *Empty*
@@ -68,21 +68,21 @@
6868
69We can see the total number of requests being held.69We can see the total number of requests being held.
7070
71 >>> requests.count71 >>> print(requests.count)
72 372 3
7373
74We can also see the number of requests being held by request type.74We can also see the number of requests being held by request type.
7575
76 >>> requests.count_of(RequestType.subscription)76 >>> print(requests.count_of(RequestType.subscription))
77 177 1
78 >>> requests.count_of(RequestType.unsubscription)78 >>> print(requests.count_of(RequestType.unsubscription))
79 179 1
8080
81We can also see when there are multiple held requests of a particular type.81We can also see when there are multiple held requests of a particular type.
8282
83 >>> requests.hold_request(RequestType.held_message, 'hold_4')83 >>> print(requests.hold_request(RequestType.held_message, 'hold_4'))
84 484 4
85 >>> requests.count_of(RequestType.held_message)85 >>> print(requests.count_of(RequestType.held_message))
86 286 2
8787
88We can ask the requests database for a specific request, by providing the id88We can ask the requests database for a specific request, by providing the id
@@ -132,7 +132,7 @@
132To make it easier to find specific requests, the list requests can be iterated132To make it easier to find specific requests, the list requests can be iterated
133over by type.133over by type.
134134
135 >>> requests.count_of(RequestType.held_message)135 >>> print(requests.count_of(RequestType.held_message))
136 3136 3
137 >>> for request in requests.of_type(RequestType.held_message):137 >>> for request in requests.of_type(RequestType.held_message):
138 ... key, data = requests.get_request(request.id)138 ... key, data = requests.get_request(request.id)
@@ -154,10 +154,10 @@
154Once a specific request has been handled, it can be deleted from the requests154Once a specific request has been handled, it can be deleted from the requests
155database.155database.
156156
157 >>> requests.count157 >>> print(requests.count)
158 5158 5
159 >>> requests.delete_request(2)159 >>> requests.delete_request(2)
160 >>> requests.count160 >>> print(requests.count)
161 4161 4
162162
163Request 2 is no longer in the database.163Request 2 is no longer in the database.
@@ -167,5 +167,5 @@
167167
168 >>> for request in requests.held_requests:168 >>> for request in requests.held_requests:
169 ... requests.delete_request(request.id)169 ... requests.delete_request(request.id)
170 >>> requests.count170 >>> print(requests.count)
171 0171 0
172172
=== modified file 'src/mailman/model/domain.py'
--- src/mailman/model/domain.py 2014-09-22 18:47:02 +0000
+++ src/mailman/model/domain.py 2014-10-31 13:23:25 +0000
@@ -48,7 +48,7 @@
4848
49 id = Column(Integer, primary_key=True)49 id = Column(Integer, primary_key=True)
5050
51 mail_host = Column(Unicode)51 mail_host = Column(Unicode) # TODO: add index?
52 base_url = Column(Unicode)52 base_url = Column(Unicode)
53 description = Column(Unicode)53 description = Column(Unicode)
54 contact_address = Column(Unicode)54 contact_address = Column(Unicode)
@@ -95,7 +95,8 @@
95 def mailing_lists(self, store):95 def mailing_lists(self, store):
96 """See `IDomain`."""96 """See `IDomain`."""
97 mailing_lists = store.query(MailingList).filter(97 mailing_lists = store.query(MailingList).filter(
98 MailingList.mail_host == self.mail_host)98 MailingList.mail_host == self.mail_host
99 ).order_by(MailingList._list_id)
99 for mlist in mailing_lists:100 for mlist in mailing_lists:
100 yield mlist101 yield mlist
101102
@@ -170,7 +171,7 @@
170 @dbconnection171 @dbconnection
171 def __iter__(self, store):172 def __iter__(self, store):
172 """See `IDomainManager`."""173 """See `IDomainManager`."""
173 for domain in store.query(Domain).all():174 for domain in store.query(Domain).order_by(Domain.mail_host).all():
174 yield domain175 yield domain
175176
176 @dbconnection177 @dbconnection
177178
=== modified file 'src/mailman/model/listmanager.py'
--- src/mailman/model/listmanager.py 2014-09-21 21:06:40 +0000
+++ src/mailman/model/listmanager.py 2014-10-31 13:23:25 +0000
@@ -86,7 +86,8 @@
86 @dbconnection86 @dbconnection
87 def mailing_lists(self, store):87 def mailing_lists(self, store):
88 """See `IListManager`."""88 """See `IListManager`."""
89 for mlist in store.query(MailingList).all():89 for mlist in store.query(MailingList).order_by(
90 MailingList._list_id).all():
90 yield mlist91 yield mlist
9192
92 @dbconnection93 @dbconnection
9394
=== modified file 'src/mailman/model/mailinglist.py'
--- src/mailman/model/mailinglist.py 2014-10-10 04:59:43 +0000
+++ src/mailman/model/mailinglist.py 2014-10-31 13:23:25 +0000
@@ -504,10 +504,9 @@
504504
505 id = Column(Integer, primary_key=True)505 id = Column(Integer, primary_key=True)
506506
507 mailing_list_id = Column(Integer,507 mailing_list_id = Column(
508 ForeignKey('mailinglist.id'),508 Integer, ForeignKey('mailinglist.id'),
509 index=True,509 index=True, nullable=False)
510 nullable=False)
511 mailing_list = relationship('MailingList', backref='acceptable_alias')510 mailing_list = relationship('MailingList', backref='acceptable_alias')
512 alias = Column(Unicode, index=True, nullable=False)511 alias = Column(Unicode, index=True, nullable=False)
513512
@@ -561,9 +560,11 @@
561560
562 id = Column(Integer, primary_key=True)561 id = Column(Integer, primary_key=True)
563562
564 mailing_list_id = Column(Integer, ForeignKey('mailinglist.id'),563 mailing_list_id = Column(
565 index=True, nullable=False)564 Integer, ForeignKey('mailinglist.id'),
565 index=True, nullable=False)
566 mailing_list = relationship('MailingList')566 mailing_list = relationship('MailingList')
567
567 name = Column(Unicode, nullable=False)568 name = Column(Unicode, nullable=False)
568 _is_enabled = Column(Boolean)569 _is_enabled = Column(Boolean)
569570
570571
=== modified file 'src/mailman/model/user.py'
--- src/mailman/model/user.py 2014-10-10 04:59:43 +0000
+++ src/mailman/model/user.py 2014-10-31 13:23:25 +0000
@@ -56,7 +56,7 @@
5656
57 id = Column(Integer, primary_key=True)57 id = Column(Integer, primary_key=True)
58 display_name = Column(Unicode)58 display_name = Column(Unicode)
59 _password = Column('password', LargeBinary) # TODO : was RawStr()59 _password = Column('password', LargeBinary)
60 _user_id = Column(UUID, index=True)60 _user_id = Column(UUID, index=True)
61 _created_on = Column(DateTime)61 _created_on = Column(DateTime)
6262
@@ -68,7 +68,7 @@
68 Integer,68 Integer,
69 ForeignKey('address.id', use_alter=True,69 ForeignKey('address.id', use_alter=True,
70 name='_preferred_address',70 name='_preferred_address',
71 ondelete="SET NULL"))71 ondelete='SET NULL'))
7272
73 _preferred_address = relationship(73 _preferred_address = relationship(
74 'Address', primaryjoin=(_preferred_address_id==Address.id),74 'Address', primaryjoin=(_preferred_address_id==Address.id),
7575
=== modified file 'src/mailman/rest/docs/moderation.rst'
--- src/mailman/rest/docs/moderation.rst 2014-04-28 15:23:35 +0000
+++ src/mailman/rest/docs/moderation.rst 2014-10-31 13:23:25 +0000
@@ -226,10 +226,9 @@
226226
227 >>> from mailman.app.moderator import hold_subscription227 >>> from mailman.app.moderator import hold_subscription
228 >>> from mailman.interfaces.member import DeliveryMode228 >>> from mailman.interfaces.member import DeliveryMode
229 >>> hold_subscription(229 >>> sub_req_id = hold_subscription(
230 ... ant, 'anne@example.com', 'Anne Person',230 ... ant, 'anne@example.com', 'Anne Person',
231 ... 'password', DeliveryMode.regular, 'en')231 ... 'password', DeliveryMode.regular, 'en')
232 1
233 >>> transaction.commit()232 >>> transaction.commit()
234233
235The subscription request is available from the mailing list.234The subscription request is available from the mailing list.
@@ -242,7 +241,7 @@
242 http_etag: "..."241 http_etag: "..."
243 language: en242 language: en
244 password: password243 password: password
245 request_id: 1244 request_id: ...
246 type: subscription245 type: subscription
247 when: 2005-08-01T07:49:23246 when: 2005-08-01T07:49:23
248 http_etag: "..."247 http_etag: "..."
@@ -259,8 +258,7 @@
259 >>> from mailman.app.moderator import hold_unsubscription258 >>> from mailman.app.moderator import hold_unsubscription
260 >>> bart = add_member(ant, 'bart@example.com', 'Bart Person',259 >>> bart = add_member(ant, 'bart@example.com', 'Bart Person',
261 ... 'password', DeliveryMode.regular, 'en')260 ... 'password', DeliveryMode.regular, 'en')
262 >>> hold_unsubscription(ant, 'bart@example.com')261 >>> unsub_req_id = hold_unsubscription(ant, 'bart@example.com')
263 2
264 >>> transaction.commit()262 >>> transaction.commit()
265263
266The unsubscription request is also available from the mailing list.264The unsubscription request is also available from the mailing list.
@@ -273,13 +271,13 @@
273 http_etag: "..."271 http_etag: "..."
274 language: en272 language: en
275 password: password273 password: password
276 request_id: 1274 request_id: ...
277 type: subscription275 type: subscription
278 when: 2005-08-01T07:49:23276 when: 2005-08-01T07:49:23
279 entry 1:277 entry 1:
280 address: bart@example.com278 address: bart@example.com
281 http_etag: "..."279 http_etag: "..."
282 request_id: 2280 request_id: ...
283 type: unsubscription281 type: unsubscription
284 http_etag: "..."282 http_etag: "..."
285 start: 0283 start: 0
@@ -292,23 +290,25 @@
292You can view an individual membership change request by providing the290You can view an individual membership change request by providing the
293request id. Anne's subscription request looks like this.291request id. Anne's subscription request looks like this.
294292
295 >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests/1')293 >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/'
294 ... 'requests/{}'.format(sub_req_id))
296 address: anne@example.com295 address: anne@example.com
297 delivery_mode: regular296 delivery_mode: regular
298 display_name: Anne Person297 display_name: Anne Person
299 http_etag: "..."298 http_etag: "..."
300 language: en299 language: en
301 password: password300 password: password
302 request_id: 1301 request_id: ...
303 type: subscription302 type: subscription
304 when: 2005-08-01T07:49:23303 when: 2005-08-01T07:49:23
305304
306Bart's unsubscription request looks like this.305Bart's unsubscription request looks like this.
307306
308 >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/requests/2')307 >>> dump_json('http://localhost:9001/3.0/lists/ant@example.com/'
308 ... 'requests/{}'.format(unsub_req_id))
309 address: bart@example.com309 address: bart@example.com
310 http_etag: "..."310 http_etag: "..."
311 request_id: 2311 request_id: ...
312 type: unsubscription312 type: unsubscription
313313
314314
@@ -328,9 +328,8 @@
328Anne's subscription request is accepted.328Anne's subscription request is accepted.
329329
330 >>> dump_json('http://localhost:9001/3.0/lists/'330 >>> dump_json('http://localhost:9001/3.0/lists/'
331 ... 'ant@example.com/requests/1', {331 ... 'ant@example.com/requests/{}'.format(sub_req_id),
332 ... 'action': 'accept',332 ... {'action': 'accept'})
333 ... })
334 content-length: 0333 content-length: 0
335 date: ...334 date: ...
336 server: ...335 server: ...
@@ -347,9 +346,8 @@
347Bart's unsubscription request is discarded.346Bart's unsubscription request is discarded.
348347
349 >>> dump_json('http://localhost:9001/3.0/lists/'348 >>> dump_json('http://localhost:9001/3.0/lists/'
350 ... 'ant@example.com/requests/2', {349 ... 'ant@example.com/requests/{}'.format(unsub_req_id),
351 ... 'action': 'discard',350 ... {'action': 'discard'})
352 ... })
353 content-length: 0351 content-length: 0
354 date: ...352 date: ...
355 server: ...353 server: ...
356354
=== modified file 'src/mailman/testing/layers.py'
--- src/mailman/testing/layers.py 2014-10-06 17:17:50 +0000
+++ src/mailman/testing/layers.py 2014-10-31 13:23:25 +0000
@@ -47,16 +47,13 @@
4747
48from lazr.config import as_boolean48from lazr.config import as_boolean
49from pkg_resources import resource_string49from pkg_resources import resource_string
50from sqlalchemy import MetaData
51from textwrap import dedent50from textwrap import dedent
52from zope import event
53from zope.component import getUtility51from zope.component import getUtility
5452
55from mailman.config import config53from mailman.config import config
56from mailman.core import initialize54from mailman.core import initialize
57from mailman.core.initialize import INHIBIT_CONFIG_FILE55from mailman.core.initialize import INHIBIT_CONFIG_FILE
58from mailman.core.logging import get_handler56from mailman.core.logging import get_handler
59from mailman.database.model import Model
60from mailman.database.transaction import transaction57from mailman.database.transaction import transaction
61from mailman.interfaces.domain import IDomainManager58from mailman.interfaces.domain import IDomainManager
62from mailman.testing.helpers import (59from mailman.testing.helpers import (
@@ -101,9 +98,7 @@
101 # Set up the basic configuration stuff. Turn off path creation until98 # Set up the basic configuration stuff. Turn off path creation until
102 # we've pushed the testing config.99 # we've pushed the testing config.
103 config.create_paths = False100 config.create_paths = False
104 if not event.subscribers:101 initialize.initialize_1(INHIBIT_CONFIG_FILE)
105 # only if not yet initialized by another layer
106 initialize.initialize_1(INHIBIT_CONFIG_FILE)
107 assert cls.var_dir is None, 'Layer already set up'102 assert cls.var_dir is None, 'Layer already set up'
108 # Calculate a temporary VAR_DIR directory so that run-time artifacts103 # Calculate a temporary VAR_DIR directory so that run-time artifacts
109 # of the tests won't tread on the installation's data. This also104 # of the tests won't tread on the installation's data. This also
@@ -195,10 +190,11 @@
195 @classmethod190 @classmethod
196 def tearDown(cls):191 def tearDown(cls):
197 assert cls.var_dir is not None, 'Layer not set up'192 assert cls.var_dir is not None, 'Layer not set up'
198 # Reset the test database after the tests are done so that there is no193 reset_the_world()
194 # Destroy the test database after the tests are done so that there is no
199 # data in case the tests are rerun with a database layer like mysql or195 # data in case the tests are rerun with a database layer like mysql or
200 # postgresql which are not deleted in teardown.196 # postgresql which are not deleted in teardown.
201 reset_the_world()197 config.db.destroy()
202 config.pop('test config')198 config.pop('test config')
203 shutil.rmtree(cls.var_dir)199 shutil.rmtree(cls.var_dir)
204 cls.var_dir = None200 cls.var_dir = None
205201
=== modified file 'src/mailman/testing/testing.cfg'
--- src/mailman/testing/testing.cfg 2014-10-10 04:59:43 +0000
+++ src/mailman/testing/testing.cfg 2014-10-31 13:23:25 +0000
@@ -20,7 +20,7 @@
20# For testing against PostgreSQL.20# For testing against PostgreSQL.
21# [database]21# [database]
22# class: mailman.database.postgresql.PostgreSQLDatabase22# class: mailman.database.postgresql.PostgreSQLDatabase
23# url: postgresql://maxking:maxking@localhost/mailman_test23# url: postgresql://$USER:$USER@localhost/mailman_test
2424
25[mailman]25[mailman]
26site_owner: noreply@example.com26site_owner: noreply@example.com
2727
=== modified file 'src/mailman/utilities/importer.py'
--- src/mailman/utilities/importer.py 2014-09-28 00:17:05 +0000
+++ src/mailman/utilities/importer.py 2014-10-31 13:23:25 +0000
@@ -175,6 +175,7 @@
175 allow_list_posts=bool,175 allow_list_posts=bool,
176 include_rfc2369_headers=bool,176 include_rfc2369_headers=bool,
177 nntp_prefix_subject_too=bool,177 nntp_prefix_subject_too=bool,
178 encode_ascii_prefixes=bool,
178 )179 )
179180
180181
181182
=== modified file 'src/mailman/utilities/tests/test_import.py'
--- src/mailman/utilities/tests/test_import.py 2014-04-28 15:23:35 +0000
+++ src/mailman/utilities/tests/test_import.py 2014-10-31 13:23:25 +0000
@@ -34,6 +34,7 @@
34from datetime import timedelta, datetime34from datetime import timedelta, datetime
35from enum import Enum35from enum import Enum
36from pkg_resources import resource_filename36from pkg_resources import resource_filename
37from sqlalchemy.exc import IntegrityError
37from zope.component import getUtility38from zope.component import getUtility
3839
39from mailman.app.lifecycle import create_list40from mailman.app.lifecycle import create_list
@@ -291,6 +292,15 @@
291 else:292 else:
292 self.fail('Import21Error was not raised')293 self.fail('Import21Error was not raised')
293294
295 def test_encode_ascii_prefixes(self):
296 self._pckdict['encode_ascii_prefixes'] = 2
297 self.assertEqual(self._mlist.encode_ascii_prefixes, False)
298 try:
299 self._import()
300 except IntegrityError as e:
301 self.fail(e)
302 self.assertEqual(self._mlist.encode_ascii_prefixes, True)
303
294304
295305
296306
297class TestArchiveImport(unittest.TestCase):307class TestArchiveImport(unittest.TestCase):

Subscribers

People subscribed via source and target branches