diff options
| author | Barry Warsaw | 2010-08-23 21:37:20 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2010-08-23 21:37:20 -0400 |
| commit | 537a9b8682565ebfa79aad0ed3e4efb6b89aa3f4 (patch) | |
| tree | 3545e10220a48ebab308c0248614f2f20f059d6a /src/mailman/rest/configuration.py | |
| parent | 316fa9af0949638a0354b67471052aa9bfe9fca0 (diff) | |
| download | mailman-537a9b8682565ebfa79aad0ed3e4efb6b89aa3f4.tar.gz mailman-537a9b8682565ebfa79aad0ed3e4efb6b89aa3f4.tar.zst mailman-537a9b8682565ebfa79aad0ed3e4efb6b89aa3f4.zip | |
Diffstat (limited to 'src/mailman/rest/configuration.py')
| -rw-r--r-- | src/mailman/rest/configuration.py | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/src/mailman/rest/configuration.py b/src/mailman/rest/configuration.py new file mode 100644 index 000000000..d2653fea4 --- /dev/null +++ b/src/mailman/rest/configuration.py @@ -0,0 +1,260 @@ +# Copyright (C) 2010 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/>. + +"""Mailing list configuration via REST API.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'ListConfiguration', + ] + + +from lazr.config import as_boolean, as_timedelta +from restish import http, resource + +from mailman.config import config +from mailman.interfaces.autorespond import ResponseAction +from mailman.interfaces.mailinglist import IAcceptableAliasSet +from mailman.rest.helpers import Validator, enum_validator, etag + + + +class GetterSetter: + """Get and set attributes on mailing lists. + + Most attributes are fairly simple - a getattr() or setattr() on the + mailing list does the trick, with the appropriate encoding or decoding on + the way in and out. Encoding doesn't happen here though; the standard + JSON library handles most types, but see ExtendedEncoder in + mailman.rest.helpers for additional support. + + Others are more complicated since they aren't kept in the model as direct + columns in the database. These will use subclasses of this base class. + Read-only attributes will have a decoder which always raises ValueError. + """ + + def __init__(self, decoder=None): + """Create a getter/setter for a specific list attribute. + + :param decoder: The callable for decoding a web request value string + into the specific data type needed by the `IMailingList` + attribute. Use None to indicate a read-only attribute. The + callable should raise ValueError when the web request value cannot + be converted. + :type decoder: callable + """ + self.decoder = decoder + + def get(self, mlist, attribute): + """Return the named mailing list attribute value. + + :param mlist: The mailing list. + :type mlist: `IMailingList` + :param attribute: The attribute name. + :type attribute: string + :return: The attribute value, ready for JSON encoding. + :rtype: object + """ + return getattr(mlist, attribute) + + def put(self, mlist, attribute, value): + """Set the named mailing list attribute value. + + :param mlist: The mailing list. + :type mlist: `IMailingList` + :param attribute: The attribute name. + :type attribute: string + :param value: The new value for the attribute. + :type request_value: object + """ + setattr(mlist, attribute, value) + + def __call__(self, value): + """Convert the value to its internal format. + + :param value: The web request value to convert. + :type value: string + :return: The converted value. + :rtype: object + """ + if self.decoder is None: + return value + return self.decoder(value) + + +class AcceptableAliases(GetterSetter): + """Resource for the acceptable aliases of a mailing list.""" + + def get(self, mlist, attribute): + """Return the mailing list's acceptable aliases.""" + assert attribute == 'acceptable_aliases', ( + 'Unexpected attribute: {0}'.format(attribute)) + aliases = IAcceptableAliasSet(mlist) + return sorted(aliases.aliases) + + def put(self, mlist, attribute, value): + """Change the acceptable aliases. + + Because this is a PUT operation, all previous aliases are cleared + first. Thus, this is an overwrite. The keys in the request are + ignored. + """ + assert attribute == 'acceptable_aliases', ( + 'Unexpected attribute: {0}'.format(attribute)) + alias_set = IAcceptableAliasSet(mlist) + alias_set.clear() + for alias in value: + alias_set.add(unicode(alias)) + + + +# Additional validators for converting from web request strings to internal +# data types. See below for details. + +def pipeline_validator(pipeline_name): + """Convert the pipeline name to a string, but only if it's known.""" + if pipeline_name in config.pipelines: + return unicode(pipeline_name) + raise ValueError('Unknown pipeline: {0}'.format(pipeline_name)) + + +def list_of_unicode(values): + """Turn a list of things into a list of unicodes.""" + return [unicode(value) for value in values] + + + +# This is the list of IMailingList attributes that are exposed through the +# REST API. The values of the keys are the GetterSetter instance holding the +# decoder used to convert the web request string to an internally valid value. +# The instance also contains the get() and put() methods used to retrieve and +# set the attribute values. Its .decoder attribute will be None for read-only +# attributes. +# +# The decoder must either return the internal value or raise a ValueError if +# the conversion failed (e.g. trying to turn 'Nope' into a boolean). +# +# Many internal value types can be automatically JSON encoded, but see +# mailman.rest.helpers.ExtendedEncoder for specializations of certain types +# (e.g. datetimes, timedeltas, enums). + +ATTRIBUTES = dict( + acceptable_aliases=AcceptableAliases(list_of_unicode), + admin_immed_notify=GetterSetter(as_boolean), + admin_notify_mchanges=GetterSetter(as_boolean), + administrivia=GetterSetter(as_boolean), + advertised=GetterSetter(as_boolean), + anonymous_list=GetterSetter(as_boolean), + autorespond_owner=GetterSetter(enum_validator(ResponseAction)), + autorespond_postings=GetterSetter(enum_validator(ResponseAction)), + autorespond_requests=GetterSetter(enum_validator(ResponseAction)), + autoresponse_grace_period=GetterSetter(as_timedelta), + autoresponse_owner_text=GetterSetter(unicode), + autoresponse_postings_text=GetterSetter(unicode), + autoresponse_request_text=GetterSetter(unicode), + bounces_address=GetterSetter(None), + collapse_alternatives=GetterSetter(as_boolean), + convert_html_to_plaintext=GetterSetter(as_boolean), + created_at=GetterSetter(None), + description=GetterSetter(unicode), + digest_last_sent_at=GetterSetter(None), + digest_size_threshold=GetterSetter(float), + filter_content=GetterSetter(as_boolean), + fqdn_listname=GetterSetter(None), + host_name=GetterSetter(None), + include_list_post_header=GetterSetter(as_boolean), + include_rfc2369_headers=GetterSetter(as_boolean), + join_address=GetterSetter(None), + last_post_at=GetterSetter(None), + leave_address=GetterSetter(None), + list_id=GetterSetter(None), + list_name=GetterSetter(None), + next_digest_number=GetterSetter(None), + no_reply_address=GetterSetter(None), + owner_address=GetterSetter(None), + pipeline=GetterSetter(pipeline_validator), + post_id=GetterSetter(None), + posting_address=GetterSetter(None), + real_name=GetterSetter(unicode), + request_address=GetterSetter(None), + scheme=GetterSetter(None), + volume=GetterSetter(None), + web_host=GetterSetter(None), + ) + + +VALIDATORS = ATTRIBUTES.copy() +for attribute, gettersetter in VALIDATORS.items(): + if gettersetter.decoder is None: + del VALIDATORS[attribute] + + + +class ListConfiguration(resource.Resource): + """A mailing list configuration resource.""" + + def __init__(self, mailing_list, attribute): + self._mlist = mailing_list + self._attribute = attribute + + @resource.GET() + def get_configuration(self, request): + """Get a mailing list configuration.""" + resource = {} + if self._attribute is None: + # Return all readable attributes. + for attribute in ATTRIBUTES: + value = ATTRIBUTES[attribute].get(self._mlist, attribute) + resource[attribute] = value + elif self._attribute not in ATTRIBUTES: + return http.bad_request( + [], b'Unknown attribute: {0}'.format(self._attribute)) + else: + attribute = self._attribute + value = ATTRIBUTES[attribute].get(self._mlist, attribute) + resource[attribute] = value + return http.ok([], etag(resource)) + + @resource.PUT() + def put_configuration(self, request): + """Set a mailing list configuration.""" + attribute = self._attribute + if attribute is None: + # Set all writable attributes. + try: + converted = Validator(**VALIDATORS)(request) + except ValueError as error: + return http.bad_request([], str(error)) + for key, value in converted.items(): + ATTRIBUTES[key].put(self._mlist, key, value) + elif attribute not in ATTRIBUTES: + return http.bad_request( + [], b'Unknown attribute: {0}'.format(attribute)) + elif ATTRIBUTES[attribute].decoder is None: + return http.bad_request( + [], b'Read-only attribute: {0}'.format(attribute)) + else: + validator = Validator(**{attribute: VALIDATORS[attribute]}) + try: + values = validator(request) + except ValueError as error: + return http.bad_request([], str(error)) + ATTRIBUTES[attribute].put( + self._mlist, attribute, values[attribute]) + return http.ok([], '') |
