summaryrefslogtreecommitdiff
path: root/src/mailman/styles
diff options
context:
space:
mode:
authorBarry Warsaw2012-03-12 18:03:35 -0700
committerBarry Warsaw2012-03-12 18:03:35 -0700
commitce02817cd4d3cf982ca58dfcb177a0b62fa75d54 (patch)
tree701efb7db4b7d045b1f0bd376eb993c519f7915f /src/mailman/styles
parent3fe7d15a5c6e54cc0c201b10b9dd1329dffe2cd2 (diff)
downloadmailman-ce02817cd4d3cf982ca58dfcb177a0b62fa75d54.tar.gz
mailman-ce02817cd4d3cf982ca58dfcb177a0b62fa75d54.tar.zst
mailman-ce02817cd4d3cf982ca58dfcb177a0b62fa75d54.zip
Clean up of style test code, along with refactoring of corner cases to
unittests where they belong.
Diffstat (limited to 'src/mailman/styles')
-rw-r--r--src/mailman/styles/docs/__init__.py0
-rw-r--r--src/mailman/styles/docs/styles.rst147
-rw-r--r--src/mailman/styles/tests/__init__.py0
-rw-r--r--src/mailman/styles/tests/test_styles.py90
4 files changed, 237 insertions, 0 deletions
diff --git a/src/mailman/styles/docs/__init__.py b/src/mailman/styles/docs/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/mailman/styles/docs/__init__.py
diff --git a/src/mailman/styles/docs/styles.rst b/src/mailman/styles/docs/styles.rst
new file mode 100644
index 000000000..90a02227b
--- /dev/null
+++ b/src/mailman/styles/docs/styles.rst
@@ -0,0 +1,147 @@
+===========
+List styles
+===========
+
+List styles are a way to name and apply a template of attribute settings to
+new mailing lists. Every style has a name, which must be unique within the
+context of a specific style manager. There is usually only one global style
+manager.
+
+Styles also have a priority, which allows you to specify the order in which
+multiple styles will be applied. A style has a `match` function which is used
+to determine whether the style should be applied to a particular mailing list
+or not. And finally, application of a style to a mailing list can really
+modify the mailing list any way it wants.
+
+Let's start with a vanilla mailing list and a default style manager.
+::
+
+ >>> from mailman.interfaces.listmanager import IListManager
+ >>> from zope.component import getUtility
+ >>> mlist = getUtility(IListManager).create('_xtest@example.com')
+
+ >>> from mailman.styles.manager import StyleManager
+ >>> style_manager = StyleManager()
+ >>> style_manager.populate()
+ >>> styles = sorted(style.name for style in style_manager.styles)
+ >>> len(styles)
+ 1
+ >>> print styles[0]
+ default
+
+
+The default style
+=================
+
+There is a default style which implements a legacy style roughly corresponding
+to discussion mailing lists. This style matches when no other styles match,
+and it has the lowest priority. The low priority means that it is matched
+last and if it matches, it is applied last.
+
+ >>> default_style = style_manager.get('default')
+ >>> print default_style.name
+ default
+ >>> default_style.priority
+ 0
+
+Given a mailing list, you can ask the style manager to find all the styles
+that match the list. The registered styles will be sorted by decreasing
+priority and each style's ``match()`` method will be called in turn. The
+sorted list of matching styles will be returned -- but not applied -- by the
+style manager's ``lookup()`` method.
+
+ >>> matched_styles = [style.name for style in style_manager.lookup(mlist)]
+ >>> len(matched_styles)
+ 1
+ >>> print matched_styles[0]
+ default
+
+
+Registering styles
+==================
+
+New styles must implement the ``IStyle`` interface.
+
+ >>> from zope.interface import implements
+ >>> from mailman.interfaces.styles import IStyle
+ >>> class TestStyle:
+ ... implements(IStyle)
+ ... name = 'test'
+ ... priority = 10
+ ... def apply(self, mailing_list):
+ ... # Just does something very simple.
+ ... mailing_list.style_thing = 'thing 1'
+ ... def match(self, mailing_list, styles):
+ ... # Applies to any test list
+ ... if 'test' in mailing_list.fqdn_listname:
+ ... styles.append(self)
+
+You can register a new style with the style manager.
+
+ >>> style_manager.register(TestStyle())
+
+And now if you look up matching styles, you should find only the new test
+style. This is because the default style only gets applied when no other
+styles match the mailing list.
+
+ >>> matched_styles = sorted(
+ ... style.name for style in style_manager.lookup(mlist))
+ >>> len(matched_styles)
+ 1
+ >>> print matched_styles[0]
+ test
+ >>> for style in style_manager.lookup(mlist):
+ ... style.apply(mlist)
+ >>> print mlist.style_thing
+ thing 1
+
+
+Style priority
+==============
+
+When multiple styles match a particular mailing list, they are applied in
+descending order of priority. In other words, a priority zero style would be
+applied last.
+::
+
+ >>> class AnotherTestStyle(TestStyle):
+ ... name = 'another'
+ ... priority = 5
+ ... # Use the base class's match() method.
+ ... def apply(self, mailing_list):
+ ... mailing_list.style_thing = 'thing 2'
+
+ >>> mlist.style_thing = 'thing 0'
+ >>> print mlist.style_thing
+ thing 0
+ >>> style_manager.register(AnotherTestStyle())
+ >>> for style in style_manager.lookup(mlist):
+ ... style.apply(mlist)
+ >>> print mlist.style_thing
+ thing 2
+
+You can change the priority of a style, and if you reapply the styles, they
+will take effect in the new priority order.
+
+ >>> style_1 = style_manager.get('test')
+ >>> style_1.priority = 5
+ >>> style_2 = style_manager.get('another')
+ >>> style_2.priority = 10
+ >>> for style in style_manager.lookup(mlist):
+ ... style.apply(mlist)
+ >>> print mlist.style_thing
+ thing 1
+
+
+Unregistering styles
+====================
+
+You can unregister a style, making it unavailable in the future.
+
+ >>> style_manager.unregister(style_2)
+ >>> matched_styles = sorted(
+ ... style.name for style in style_manager.lookup(mlist))
+ >>> len(matched_styles)
+ 1
+ >>> print matched_styles[0]
+ test
diff --git a/src/mailman/styles/tests/__init__.py b/src/mailman/styles/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/mailman/styles/tests/__init__.py
diff --git a/src/mailman/styles/tests/test_styles.py b/src/mailman/styles/tests/test_styles.py
new file mode 100644
index 000000000..ce8b5064d
--- /dev/null
+++ b/src/mailman/styles/tests/test_styles.py
@@ -0,0 +1,90 @@
+# Copyright (C) 2012 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/>.
+
+"""Test styles."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestStyle',
+ ]
+
+
+import unittest
+
+from zope.component import getUtility
+from zope.interface import implements
+from zope.interface.exceptions import DoesNotImplement
+
+from mailman.interfaces.styles import (
+ DuplicateStyleError, IStyle, IStyleManager)
+from mailman.testing.layers import ConfigLayer
+
+
+
+class DummyStyle:
+ implements(IStyle)
+
+ name = 'dummy'
+ priority = 1
+
+ def apply(self, mlist):
+ pass
+
+ def match(self, mlist, styles):
+ styles.append(self)
+
+
+
+class TestStyle(unittest.TestCase):
+ """Test styles."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self.manager = getUtility(IStyleManager)
+
+ def test_register_style_again(self):
+ # Registering a style with the same name as a previous style raises an
+ # exception.
+ self.manager.register(DummyStyle())
+ try:
+ self.manager.register(DummyStyle())
+ except DuplicateStyleError:
+ pass
+ else:
+ raise AssertionError('DuplicateStyleError exception expected')
+
+ def test_register_a_non_style(self):
+ # You can't register something that doesn't implement the IStyle
+ # interface.
+ try:
+ self.manager.register(object())
+ except DoesNotImplement:
+ pass
+ else:
+ raise AssertionError('DoesNotImplement exception expected')
+
+ def test_unregister_a_non_registered_style(self):
+ # You cannot unregister a style that hasn't yet been registered.
+ try:
+ self.manager.unregister(DummyStyle())
+ except KeyError:
+ pass
+ else:
+ raise AssertionError('KeyError expected')