diff --git a/mailmanclient/__init__.py b/mailmanclient/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mailmanclient/__init__.py
diff --git a/mailmanclient/rest.py b/mailmanclient/rest.py
new file mode 100644
index 0000000..dd82ec3
--- /dev/null
+++ b/mailmanclient/rest.py
@@ -0,0 +1,393 @@
+# 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 .
+
+"""A client library for the Mailman REST API."""
+
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'MailmanRESTClient',
+ 'MailmanRESTClientError',
+ ]
+
+
+import re
+import json
+import os.path
+
+from base64 import b64encode
+from httplib2 import Http
+from operator import itemgetter
+from urllib import urlencode
+from urllib2 import HTTPError
+
+
+class MailmanRESTClientError(Exception):
+ """An exception thrown by the Mailman REST API client."""
+
+
+class MailmanRESTClient():
+ """A wrapper for the Mailman REST API."""
+
+ def __init__(self, host, username, password):
+ """Check and modify the host name. Also, try to parse a config file to
+ get the API credentials.
+
+ :param host: the host name of the REST API
+ :type host: string
+ :param username: auth user name for the rest api
+ :type: username: string
+ :param password: pwd for the rest api
+ :type: password: string
+ :return: a MailmanRESTClient object
+ :rtype: objectFirst line should
+ """
+ self.host = host
+ self.username = username
+ self.password = password
+ # If there is a trailing slash remove it
+ if self.host[-1] == '/':
+ self.host = self.host[:-1]
+ # If there is no protocol, fall back to http://
+ if self.host[0:4] != 'http':
+ self.host = 'http://' + self.host
+
+ def __repr__(self):
+ return '' % self.host
+
+ def _http_request(self, path, data=None, method=None):
+ """Send an HTTP request.
+
+ :param path: the path to send the request to
+ :type path: string
+ :param data: POST oder PUT data to send
+ :type data: dict
+ :param method: the HTTP method; defaults to GET or POST (if
+ data is not None)
+ :type method: string
+ :return: the request content or a status code, depending on the
+ method and if the request was successful
+ :rtype: int, list or dict
+ """
+ url = self.host + path
+ # Include general header information
+ headers = {
+ 'User-Agent': 'MailmanRESTClient',
+ 'Accept': 'text/plain',
+ }
+ if data is not None:
+ data = urlencode(data, doseq=True)
+ headers['Content-type'] = "application/x-www-form-urlencoded"
+ if method is None:
+ if data is None:
+ method = 'GET'
+ else:
+ method = 'POST'
+ method = method.upper()
+ basic_auth = '{0}:{1}'.format(self.username, self.password)
+ headers['Authorization'] = 'Basic ' + b64encode(basic_auth)
+ response, content = Http().request(url, method, data, headers)
+
+ if method == 'GET':
+ if response.status // 100 != 2:
+ raise HTTPError(url, response.status, content, response, None)
+ else:
+ return json.loads(content)
+ else:
+ return response.status
+
+ def create_domain(self, email_host):
+ """Create a domain and return a domain object.
+
+ :param email_host: The host domain to create
+ :type email_host: string
+ :return: A domain object or a status code (if the create
+ request failed)
+ :rtype int or object
+ """
+ data = {
+ 'email_host': email_host,
+ }
+ response = self._http_request('/3.0/domains', data, 'POST')
+ if response == 201:
+ return _Domain(self.host, self.username, self.password, email_host)
+ else:
+ return response
+
+ def get_domain(self, email_host):
+ """Return a domain object.
+
+ :param email_host: host domain
+ :type email_host: string
+ :rtype object
+ """
+ return _Domain(self.host, self.username, self.password, email_host)
+
+ def get_lists(self):
+ """Get a list of all mailing list.
+
+ :return: a list of dicts with all mailing lists
+ :rtype: list
+ """
+ response = self._http_request('/3.0/lists')
+ if 'entries' not in response:
+ return []
+ else:
+ # Return a dict with entries sorted by fqdn_listname
+ return sorted(response['entries'],
+ key=itemgetter('fqdn_listname'))
+
+ def get_list(self, fqdn_listname):
+ """Find and return a list object.
+
+ :param fqdn_listname: the mailing list address
+ :type fqdn_listname: string
+ :rtype: object
+ """
+ return _List(self.host, self.username, self.password, fqdn_listname)
+
+ def get_members(self):
+ """Get a list of all list members.
+
+ :return: a list of dicts with the members of all lists
+ :rtype: list
+ """
+ response = self._http_request('/3.0/members')
+ if 'entries' not in response:
+ return []
+ else:
+ return sorted(response['entries'],
+ key=itemgetter('self_link'))
+
+ def get_member(self, email_address, fqdn_listname):
+ """Return a member object.
+
+ :param email_adresses: the email address used
+ :type email_address: string
+ :param fqdn_listname: the mailing list
+ :type fqdn_listname: string
+ :return: a member object
+ :rtype: _Member
+ """
+ return _Member(self.host, self.username, self.password, email_address, fqdn_listname)
+
+
+ def get_user(self, email_address):
+ """Find and return a user object.
+
+ :param email_address: one of the user's email addresses
+ :type email: string:
+ :returns: a user object
+ :rtype: _User
+ """
+ return _User(self.host, self.username, self.password, email_address)
+
+
+class _Domain(MailmanRESTClient):
+ """A domain wrapper for the MailmanRESTClient."""
+
+ def __init__(self, host, username, password, email_host):
+ """Connect to host and get list information.
+
+ :param host: the host name of the REST API
+ :type host: string
+ :param username: auth user name for the rest api
+ :type: username: string
+ :param password: pwd for the rest api
+ :type: password: string
+ :param email_host: host domain
+ :type email_host: string
+ :rtype: object
+ """
+ super(_Domain, self).__init__(host, username, password)
+ self.info = self._http_request('/3.0/domains/' + email_host)
+
+ def create_list(self, list_name):
+ """Create a mailing list and return a list object.
+
+ :param list_name: the name of the list to be created
+ :type list_name: string
+ :rtype: object
+ """
+ fqdn_listname = list_name + '@' + self.info['email_host']
+ data = {
+ 'fqdn_listname': fqdn_listname
+ }
+ response = self._http_request('/3.0/lists', data, 'POST')
+ return _List(self.host, self.username, self.password, fqdn_listname)
+
+ def delete_list(self, list_name):
+ fqdn_listname = list_name + '@' + self.info['email_host']
+ return self._http_request('/3.0/lists/' + fqdn_listname, None, 'DELETE')
+
+
+class _List(MailmanRESTClient):
+ """A mailing list wrapper for the MailmanRESTClient."""
+
+ def __init__(self, host, username, password, fqdn_listname):
+ """Connect to host and get list information.
+
+ :param host: the host name of the REST API
+ :type host: string
+ :param username: auth user name for the rest api
+ :type: username: string
+ :param password: pwd for the rest api
+ :type: password: string
+ :param fqdn_listname: the mailing list address
+ :type fqdn_listname: string
+ :rtype: object
+ """
+ super(_List, self).__init__(host, username, password)
+ self.info = self._http_request('/3.0/lists/' + fqdn_listname)
+ self.config = self._http_request('/3.0/lists/%s/config' % fqdn_listname)
+
+ def subscribe(self, address, real_name=None):
+ """Add an address to a list.
+
+ :param address: email address to add to the list.
+ :type address: string
+ :param real_name: the real name of the new member
+ :type real_name: string
+ """
+ data = {
+ 'fqdn_listname': self.info['fqdn_listname'],
+ 'address': address,
+ 'real_name': real_name
+ }
+ return self._http_request('/3.0/members', data, 'POST')
+
+ def unsubscribe(self, address):
+ """Unsubscribe an address to a list.
+
+ :param address: email address to add to the list.
+ :type address: string
+ :param real_name: the real name of the new member
+ :type real_name: string
+ """
+ return self._http_request('/3.0/lists/' +
+ self.info['fqdn_listname'] +
+ '/member/' +
+ address,
+ None,
+ 'DELETE')
+
+ def get_members(self):
+ """Get a list of all list members.
+
+ :return: a list of dicts with all members
+ :rtype: list
+ """
+ response = self._http_request('/3.0/lists/' +
+ self.info['fqdn_listname'] +
+ '/roster/members')
+ if 'entries' not in response:
+ return []
+ else:
+ return sorted(response['entries'],
+ key=itemgetter('self_link'))
+
+ def get_member(self, email_address):
+ """Return a member object.
+
+ :param email_adresses: the email address used
+ :type email_address: string
+ :param fqdn_listname: the mailing list
+ :type fqdn_listname: string
+ :return: a member object
+ :rtype: _Member
+ """
+ return _Member(self.host, self.username, self.password, email_address, self.info['fqdn_listname'])
+
+ def update_list(self, data):
+ """Update the settings for a list.
+ """
+ return self._http_request('/3.0/lists/' + self.info['fqdn_listname'],
+ data, method='PATCH')
+
+ def update_config(self, data):
+ """Update the list configuration.
+
+ :param data: A dict with the config attributes to be updated.
+ :type data: dict
+ :return: the return code of the http request
+ :rtype: integer
+ """
+ url = '/3.0/lists/%s/config' % self.info['fqdn_listname']
+ status = self._http_request(url, data, 'PATCH')
+ if status == 200:
+ for key in data:
+ self.config[key] = data[key]
+ return status
+
+ def __str__(self):
+ """A string representation of a list.
+ """
+ return "A list object for the list '%s'." % self.info['fqdn_listname']
+
+
+class _Member(MailmanRESTClient):
+ """A user wrapper for the MailmanRESTClient."""
+
+ def __init__(self, host, username, password, email_address, fqdn_listname):
+ """Connect to host and get membership information.
+
+ :param host: the host name of the REST API
+ :type host: string
+ :param username: auth user name for the rest api
+ :type: username: string
+ :param password: pwd for the rest api
+ :type: password: string
+ :param email_address: email address
+ :type email_address: string
+ :param fqdn_listname: the mailing list
+ :type fqdn_listname: string
+ :return: a member object
+ :rtype: _Member
+ """
+ super(_Member, self).__init__(host, username, password)
+ # Create an info attribute to receive member info via the API.
+ # To be filled with actual data as soon as the API supports it.
+ self.info = {}
+ # Keep these values out of the info dict so info can be send
+ # back to the server without additional keys
+ self.email_address = email_address
+ self.fqdn_listname = fqdn_listname
+ self.info = self._http_request('/3.0/lists/%s/member/%s' % (fqdn_listname, email_address))
+
+ def unsubscribe(self, fqdn_listname):
+ url = '/3.0/lists/%s/member/%s' % (fqdn_listname, 'jack@example.com')
+ return self._http_request(url, None, 'DELETE')
+
+ def update(self, data=None):
+ """Update member settings."""
+
+ # If data is None, use the info dict
+ if data is None:
+ data = self.info
+
+ path = '/3.0/lists/%s/member/%s' % (self.fqdn_listname,
+ self.email_address)
+ return self._http_request(path, data, method='PATCH')
+
+ def __str__(self):
+ """A string representation of a member."""
+
+ return "A member object for '%s', subscribed to '%s'." \
+ % (self.email_address, self.fqdn_listname)
+
diff --git a/static/mailman_django/css/style.css b/static/mailman_django/css/style.css
index 61cf659..437dc53 100755
--- a/static/mailman_django/css/style.css
+++ b/static/mailman_django/css/style.css
@@ -3,18 +3,53 @@
/*************************
* Reset
*************************/
-html {
- height: 101%;
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 100%;
+ font-family: inherit;
+ vertical-align: baseline;
}
-img {
- border: none;
+/* remember to define focus styles! */
+:focus {
+ outline: 0;
}
-ul,
-li {
- list-style: none;
- margin: 0;
- padding: 0;
+body {
+ line-height: 1;
+ color: black;
+ background: white;
}
+ol, ul {
+ list-style: none;
+}
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+ border-collapse: separate;
+ border-spacing: 0;
+}
+caption, th, td {
+ text-align: left;
+ font-weight: normal;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: "";
+}
+blockquote, q {
+ quotes: "" "";
+}
+
/*************************
* Fonts
@@ -176,3 +211,6 @@
}
}
+
+@media screen and (max-width: 600px) {
+}