diff options
Diffstat (limited to 'src/mailman/rest/listconf.py')
| -rw-r--r-- | src/mailman/rest/listconf.py | 91 |
1 files changed, 71 insertions, 20 deletions
diff --git a/src/mailman/rest/listconf.py b/src/mailman/rest/listconf.py index 3294ba8b6..0982e7970 100644 --- a/src/mailman/rest/listconf.py +++ b/src/mailman/rest/listconf.py @@ -25,12 +25,14 @@ from mailman.interfaces.archiver import ArchivePolicy from mailman.interfaces.autorespond import ResponseAction from mailman.interfaces.digests import DigestFrequency from mailman.interfaces.mailinglist import ( - IAcceptableAliasSet, ReplyToMunging, SubscriptionPolicy) + IAcceptableAliasSet, IMailingList, ReplyToMunging, SubscriptionPolicy) +from mailman.interfaces.template import ITemplateManager from mailman.rest.helpers import ( GetterSetter, bad_request, etag, no_content, not_found, okay) from mailman.rest.validator import ( PatchValidator, ReadOnlyPATCHRequestError, UnknownPATCHRequestError, Validator, enum_validator, list_of_strings_validator) +from zope.component import getUtility class AcceptableAliases(GetterSetter): @@ -58,6 +60,31 @@ class AcceptableAliases(GetterSetter): alias_set.add(alias) +TEMPLATE_ATTRIBUTES = dict( + digest_footer_uri='list:digest:footer', + digest_header_uri='list:digest:header', + footer_uri='list:regular:footer', + goodbye_message_uri='user:ack:goodbye', + header_uri='list:regular:header', + welcome_message_uri='user:ack:welcome', + ) + + +class URIAttributeMapper(GetterSetter): + """Map old IMailingList uri attributes to the new template manager.""" + + def get(self, obj, attribute): + assert IMailingList.providedBy(obj), obj + template_name = TEMPLATE_ATTRIBUTES[attribute] + template = getUtility(ITemplateManager).raw(template_name, obj.list_id) + return '' if template is None else template.uri + + def put(self, obj, attribute, value): + assert IMailingList.providedBy(obj), obj + template_name = TEMPLATE_ATTRIBUTES[attribute] + getUtility(ITemplateManager).set(template_name, obj.list_id, value) + + # Additional validators for converting from web request strings to internal # data types. See below for details. @@ -117,7 +144,6 @@ ATTRIBUTES = dict( digests_enabled=GetterSetter(as_boolean), filter_content=GetterSetter(as_boolean), first_strip_reply_to=GetterSetter(as_boolean), - goodbye_message_uri=GetterSetter(str), fqdn_listname=GetterSetter(None), mail_host=GetterSetter(None), allow_list_posts=GetterSetter(as_boolean), @@ -137,13 +163,10 @@ ATTRIBUTES = dict( reply_goes_to_list=GetterSetter(enum_validator(ReplyToMunging)), reply_to_address=GetterSetter(str), request_address=GetterSetter(None), - scheme=GetterSetter(None), send_welcome_message=GetterSetter(as_boolean), subject_prefix=GetterSetter(str), subscription_policy=GetterSetter(enum_validator(SubscriptionPolicy)), volume=GetterSetter(None), - web_host=GetterSetter(None), - welcome_message_uri=GetterSetter(str), ) @@ -153,6 +176,20 @@ for attribute, gettersetter in list(VALIDATORS.items()): del VALIDATORS[attribute] +def api_attributes(api): + # The list of readable attributes is different depending on the API being + # requested. Specifically, in API 3.0 the templates are exposed as list + # attributes, although we map them to templates. In API 3.1 and beyond, + # only the template manager API can be used for these. + attributes = ATTRIBUTES.copy() + if api.version_info == (3, 0): + attributes.update({ + attribute: URIAttributeMapper(str) + for attribute in TEMPLATE_ATTRIBUTES + }) + return attributes + + @public class ListConfiguration: """A mailing list configuration resource.""" @@ -164,31 +201,44 @@ class ListConfiguration: def on_get(self, request, response): """Get a mailing list configuration.""" resource = {} + attributes = api_attributes(self.api) if self._attribute is None: - # This is a requst for all the mailing list's configuration + # This is a request for all the mailing list's configuration # variables. Return all readable attributes. - for attribute in ATTRIBUTES: - value = ATTRIBUTES[attribute].get(self._mlist, attribute) + for attribute, getter in attributes.items(): + value = getter.get(self._mlist, attribute) resource[attribute] = value - elif self._attribute not in ATTRIBUTES: + elif self._attribute in attributes: + # This is a request for a specific attribute. + value = attributes[self._attribute].get( + self._mlist, self._attribute) + resource[self._attribute] = value + else: # This is a request for a specific, nonexistent attribute. not_found( response, 'Unknown attribute: {}'.format(self._attribute)) return - else: - # This is a request for a specific attribute. - attribute = self._attribute - value = ATTRIBUTES[attribute].get(self._mlist, attribute) - resource[attribute] = value okay(response, etag(resource)) def on_put(self, request, response): """Set a mailing list configuration.""" attribute = self._attribute + # The list of required attributes differs between API version. For + # backward compatibility, in API 3.0 all of the *_uri attributes are + # optional. In API 3.1 none of these are allowed since they are + # handled by the template manager API. + validators = VALIDATORS.copy() + attributes = api_attributes(self.api) + if self.api.version_info == (3, 0): + validators.update({ + attribute: URIAttributeMapper(str) + for attribute in TEMPLATE_ATTRIBUTES + }) + validators['_optional'] = TEMPLATE_ATTRIBUTES.keys() if attribute is None: # This is a request to update all the list's writable # configuration variables. All must be provided in the request. - validator = Validator(**VALIDATORS) + validator = Validator(**validators) try: validator.update(self._mlist, request) except ValueError as error: @@ -200,18 +250,18 @@ class ListConfiguration: # contain sufficient details, so just return it as the reason. bad_request(response, str(error)) return - elif attribute not in ATTRIBUTES: + elif attribute not in attributes: # Here we're PUTting to a specific resource, but that attribute is # bogus so the URL is considered pointing to a missing resource. not_found(response, 'Unknown attribute: {}'.format(attribute)) return - elif ATTRIBUTES[attribute].decoder is None: + elif attributes[attribute].decoder is None: bad_request( response, 'Read-only attribute: {}'.format(attribute)) return else: # We're PUTting to a specific configuration sub-resource. - validator = Validator(**{attribute: VALIDATORS[attribute]}) + validator = Validator(**{attribute: validators[attribute]}) try: validator.update(self._mlist, request) except ValueError as error: @@ -221,11 +271,12 @@ class ListConfiguration: def on_patch(self, request, response): """Patch the configuration (i.e. partial update).""" + attributes = api_attributes(self.api) if self._attribute is None: # We're PATCHing one or more of the attributes on the list's # configuration resource, so all the writable attributes are valid # candidates for updating. - converters = ATTRIBUTES + converters = attributes else: # We're PATCHing a specific list configuration attribute # sub-resource. Because the request data must be a dictionary, we @@ -237,7 +288,7 @@ class ListConfiguration: bad_request(response, 'Expected 1 attribute, got {}'.format( len(keys))) return - converter = ATTRIBUTES.get(self._attribute) + converter = attributes.get(self._attribute) if converter is None: # This is the case where the URL points to a nonexisting list # configuration attribute sub-resource. |
