summaryrefslogtreecommitdiff
path: root/src/mailman/styles
diff options
context:
space:
mode:
authorBarry Warsaw2012-12-28 16:35:08 -0500
committerBarry Warsaw2012-12-28 16:35:08 -0500
commit582d6e486f9693a2ce082071b747eec468df19b6 (patch)
treec25dcc945f161c6d44154c20de72793702be313e /src/mailman/styles
parent2b663ac20e7898167cf242d3628f8cf0feeab12f (diff)
downloadmailman-582d6e486f9693a2ce082071b747eec468df19b6.tar.gz
mailman-582d6e486f9693a2ce082071b747eec468df19b6.tar.zst
mailman-582d6e486f9693a2ce082071b747eec468df19b6.zip
LP: #975692 phase 1
* Rework list style management. No more style priorities or matching.. Now, you name a style explicitly to apply and that's it. * create_list() now takes a `style` argument. * config file now names both a default style to use, and a set of paths to scan for IStyle instances. (This could be a model for other plugins.) * added IMailingList.style_name to record the last style applied, but this is going to be removed in subsequent revisions. Also: * Move find_components() and scan_module() from app/finder.py to utilities/modules.py * Cleaned up lifecycle.rst for better documentation. Some tests moved to test_lifecycle.py. * Remove some unnecessary test tearDown() code.
Diffstat (limited to 'src/mailman/styles')
-rw-r--r--src/mailman/styles/default.py8
-rw-r--r--src/mailman/styles/docs/styles.rst187
-rw-r--r--src/mailman/styles/manager.py37
3 files changed, 101 insertions, 131 deletions
diff --git a/src/mailman/styles/default.py b/src/mailman/styles/default.py
index cb4da396d..138d49872 100644
--- a/src/mailman/styles/default.py
+++ b/src/mailman/styles/default.py
@@ -47,7 +47,6 @@ class DefaultStyle:
"""The default (i.e. legacy) style."""
name = 'default'
- priority = 0 # the lowest priority style
def apply(self, mailing_list):
"""See `IStyle`."""
@@ -55,6 +54,7 @@ class DefaultStyle:
mlist = mailing_list
# List identity.
mlist.display_name = mlist.list_name.capitalize()
+ mlist.style_name = self.name
mlist.include_rfc2369_headers = True
mlist.allow_list_posts = True
# Most of these were ripped from the old MailList.InitVars() method.
@@ -217,9 +217,3 @@ from: .*@uplinkpro.com
mlist.owner_chain = 'default-owner-chain'
# The default pipeline to send -owner email through.
mlist.owner_pipeline = 'default-owner-pipeline'
-
- def match(self, mailing_list, styles):
- """See `IStyle`."""
- # If no other styles have matched, then the default style matches.
- if len(styles) == 0:
- styles.append(self)
diff --git a/src/mailman/styles/docs/styles.rst b/src/mailman/styles/docs/styles.rst
index 8f589f10b..ccacbdb88 100644
--- a/src/mailman/styles/docs/styles.rst
+++ b/src/mailman/styles/docs/styles.rst
@@ -3,58 +3,37 @@ 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.
+new mailing lists. Every style has a name, which must be unique.
-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.
+Styles are generally only applied when a mailing list is created, although
+there is no reason why styles can't be applied to an existing mailing list.
+However, when a style changes, the mailing lists using that style are not
+automatically updated. Instead, think of styles as the initial set of
+defaults for just about any mailing list attribute. In fact, application of a
+style to a mailing list can really modify the mailing list in any way.
-Let's start with a vanilla mailing list and a default style manager.
-::
+To start with, there is only one style, the default style.
- >>> 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]
+ >>> from mailman.interfaces.styles import IStyleManager
+ >>> manager = getUtility(IStyleManager)
+ >>> for style in manager.styles:
+ ... print style.name
default
+When you create a mailing list through the low-level `IListManager` API, no
+style is applied.
-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
+ >>> from mailman.interfaces.listmanager import IListManager
+ >>> mlist = getUtility(IListManager).create('ant@example.com')
+ >>> print mlist.style_name
+ None
-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.
+By applying a style, the style name gets assigned.
- >>> matched_styles = [style.name for style in style_manager.lookup(mlist)]
- >>> len(matched_styles)
- 1
- >>> print matched_styles[0]
- default
+ >>> manager.get('default').apply(mlist)
+ >>> print mlist.list_id, mlist.style_name
+ ant.example.com default
Registering styles
@@ -66,82 +45,88 @@ New styles must implement the ``IStyle`` interface.
>>> from mailman.interfaces.styles import IStyle
>>> @implementer(IStyle)
... class TestStyle:
- ... name = 'test'
- ... priority = 10
+ ... name = 'a-test-style'
... def apply(self, mailing_list):
... # Just does something very simple.
+ ... mailing_list.style_name = self.name
... 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())
+ >>> 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.
+All registered styles are returned in alphabetical order by style name.
- >>> 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
+ >>> for style in manager.styles:
+ ... print style.name
+ a-test-style
+ default
+You can also ask the style manager for the style, by name.
-Style priority
-==============
+ >>> test_style = manager.get('a-test-style')
+ >>> print test_style.name
+ a-test-style
-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'
+Unregistering styles
+====================
- >>> 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 unregister a style, making it unavailable in the future.
+
+ >>> manager.unregister(test_style)
+ >>> for style in manager.styles:
+ ... print style.name
+ default
+
+Asking for a missing style returns None.
+
+ >>> print manager.get('a-test-style')
+ None
+
+
+.. _list-creation-styles:
+
+Apply styles at list creation
+=============================
+
+You can specify a style to apply when creating a list through the high-level
+API. Let's start by registering the test style.
+
+ >>> manager.register(test_style)
+
+Now, when we use the high level API, we can ask for the style to be applied.
+
+ >>> from mailman.app.lifecycle import create_list
+ >>> mlist = create_list('bee@example.com', style_name=test_style.name)
+ >>> print mlist.list_id, mlist.style_name
+ bee.example.com a-test-style
-You can change the priority of a style, and if you reapply the styles, they
-will take effect in the new priority order.
+The style has been applied.
- >>> 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
+If no style name is provided when creating the list, the system default style
+(which may or may not be the style named 'default') is applied.
-Unregistering styles
-====================
+ >>> @implementer(IStyle)
+ ... class AnotherStyle:
+ ... name = 'another-style'
+ ... def apply(self, mailing_list):
+ ... # Just does something very simple.
+ ... mailing_list.style_name = self.name
+ ... mailing_list.style_thing = 'thing 2'
+ >>> another_style = AnotherStyle()
-You can unregister a style, making it unavailable in the future.
+We'll set up the system default to apply this newly registered style if no
+other style is explicitly given.
- >>> 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
+ >>> from mailman.testing.helpers import configuration
+ >>> with configuration('styles', default=another_style.name):
+ ... manager.register(another_style)
+ ... mlist = create_list('cat@example.com')
+ >>> print mlist.style_name
+ another-style
+ >>> print mlist.style_thing
+ thing 2
diff --git a/src/mailman/styles/manager.py b/src/mailman/styles/manager.py
index c2729abfb..5962d308f 100644
--- a/src/mailman/styles/manager.py
+++ b/src/mailman/styles/manager.py
@@ -26,7 +26,6 @@ __all__ = [
]
-from operator import attrgetter
from zope.component import getUtility
from zope.interface import implementer
from zope.interface.verify import verifyObject
@@ -34,7 +33,7 @@ from zope.interface.verify import verifyObject
from mailman.interfaces.configuration import ConfigurationUpdatedEvent
from mailman.interfaces.styles import (
DuplicateStyleError, IStyle, IStyleManager)
-from mailman.utilities.modules import call_name
+from mailman.utilities.modules import find_components
@@ -50,35 +49,27 @@ class StyleManager:
self._styles.clear()
# Avoid circular imports.
from mailman.config import config
- # Install all the styles described by the configuration files.
- for section in config.style_configs:
- class_path = section['class']
- style = call_name(class_path)
- assert section.name.startswith('style'), (
- 'Bad style section name: %s' % section.name)
- style.name = section.name[6:]
- style.priority = int(section.priority)
- self.register(style)
+ # Calculate the Python import paths to search.
+ paths = filter(None, (path.strip()
+ for path in config.styles.paths.splitlines()))
+ for path in paths:
+ for style_class in find_components(path, IStyle):
+ style = style_class()
+ verifyObject(IStyle, style)
+ assert style.name not in self._styles, (
+ 'Duplicate style "{0}" found in {1}'.format(
+ style.name, style_class))
+ self._styles[style.name] = style
def get(self, name):
"""See `IStyleManager`."""
return self._styles.get(name)
- def lookup(self, mailing_list):
- """See `IStyleManager`."""
- matched_styles = []
- for style in self.styles:
- style.match(mailing_list, matched_styles)
- for style in matched_styles:
- yield style
-
@property
def styles(self):
"""See `IStyleManager`."""
- for style in sorted(self._styles.values(),
- key=attrgetter('priority'),
- reverse=True):
- yield style
+ for style_name in sorted(self._styles):
+ yield self._styles[style_name]
def register(self, style):
"""See `IStyleManager`."""