------------------------------------------------------------ revno: 6546 committer: Barry Warsaw <[EMAIL PROTECTED]> branch nick: 3.0 timestamp: Tue 2007-08-07 23:24:13 -0500 message: Interfaces IRequests and IListRequests which are substitutes for the ListAdmin mixin. This latter will go away soon. Added implementation and tests. The implementation has some cruft though -- it forces us to use a flush() in the code because I don't yet know how to get to SA's last_inserted_ids(). Note that this implementation abuses the IPendings interface in order to store arbitrary string key/value pairs. Fix the auto-discovery of interfaces by allowing Enums in interface files as well. Long term, this is how it should work anyway. added: Mailman/database/model/requests.py Mailman/docs/requests.txt Mailman/interfaces/requests.py modified: Mailman/database/__init__.py Mailman/database/model/__init__.py Mailman/database/model/pending.py Mailman/interfaces/__init__.py Mailman/interfaces/pending.py
=== added file 'Mailman/database/model/requests.py' --- a/Mailman/database/model/requests.py 1970-01-01 00:00:00 +0000 +++ b/Mailman/database/model/requests.py 2007-08-08 04:24:13 +0000 @@ -0,0 +1,121 @@ +# Copyright (C) 2007 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Implementations of the IRequests and IListRequests interfaces.""" + +from datetime import timedelta +from elixir import * +from zope.interface import implements + +from Mailman.configuration import config +from Mailman.database.types import EnumType +from Mailman.interfaces import IListRequests, IPendable, IRequests, RequestType + + +MAILINGLIST_KIND = 'Mailman.database.model.mailinglist.MailingList' + + +__metaclass__ = type +__all__ = [ + 'Requests', + ] + + + +class DataPendable(dict): + implements(IPendable) + + + +class ListRequests: + implements(IListRequests) + + def __init__(self, mailing_list): + self.mailing_list = mailing_list + + @property + def count(self): + results = _Request.select_by(mailing_list=self.mailing_list._data) + return len(results) + + @property + def held_requests(self): + results = _Request.select_by(mailing_list=self.mailing_list._data) + for request in results: + yield request.id, request.type + + def hold_request(self, request_type, key, data=None): + if request_type not in RequestType: + raise TypeError(request_type) + if data is None: + data_hash = None + else: + # We're abusing the pending database as a way of storing arbitrary + # key/value pairs, where both are strings. This isn't ideal but + # it lets us get auxiliary data almost for free. We may need to + # lock this down more later. + pendable = DataPendable() + pendable.update(data) + token = config.db.pendings.add(pendable, timedelta(days=5000)) + data_hash = token + result = _Request(key=key, type=request_type, + mailing_list=self.mailing_list._data, + data_hash=data_hash) + # XXX We need a handle on last_inserted_ids() instead of requiring a + # flush of the database to get a valid id. + config.db.flush() + return result.id + + def get_request(self, request_id): + result = _Request.get(request_id) + if result is None: + return None + if result.data_hash is None: + return result.key, result.data_hash + pendable = config.db.pendings.confirm(result.data_hash, expunge=False) + data = dict() + data.update(pendable) + return result.key, data + + def delete_request(self, request_id): + result = _Request.get(request_id) + if result is None: + raise KeyError(request_id) + # Throw away the pended data. + config.db.pendings.confirm(result.data_hash) + result.delete() + + + +class Requests: + implements(IRequests) + + def get_list_requests(self, mailing_list): + return ListRequests(mailing_list) + + + +class _Request(Entity): + """Table for mailing list hold requests.""" + + has_field('key', Unicode) + has_field('type', EnumType) + has_field('data_hash', Unicode) + # Relationships + belongs_to('mailing_list', of_kind=MAILINGLIST_KIND) + # Options + using_options(shortnames=True) === added file 'Mailman/docs/requests.txt' --- a/Mailman/docs/requests.txt 1970-01-01 00:00:00 +0000 +++ b/Mailman/docs/requests.txt 2007-08-08 04:24:13 +0000 @@ -0,0 +1,149 @@ +Held requests +============= + +Various actions will be held for moderator approval, such as subscriptions to +closed lists, or postings by non-members. The requests database is the low +level interface to these actions requiring approval. + + >>> from Mailman.configuration import config + >>> from Mailman.database import flush + +Here is a helper function for printing out held requests. + + >>> def show_holds(requests): + ... for request in requests.held_requests: + ... print request[0], str(request[1]) + + +Mailing list centric +-------------------- + +A set of requests are always centric to a particular mailing list, so given a +mailing list you need to get its requests object. + + >>> from Mailman.interfaces import IListRequests, IRequests + >>> from zope.interface.verify import verifyObject + >>> verifyObject(IRequests, config.db.requests) + True + >>> mlist = config.db.list_manager.create('[EMAIL PROTECTED]') + >>> flush() + >>> requests = config.db.requests.get_list_requests(mlist) + >>> verifyObject(IListRequests, requests) + True + >>> requests.mailing_list + <mailing list "[EMAIL PROTECTED]" (unlocked) at ...> + + +Holding requests +---------------- + +The list's requests database starts out empty. + + >>> requests.count + 0 + >>> list(requests.held_requests) + [] + +At the lowest level, the requests database is very simple. Holding a request +requires a request type (as an enum value), a key, and an optional dictionary +of associated data. The request database assigns no semantics to the held +data, except for the request type. Here we hold some simple bits of data. + + >>> from Mailman.interfaces import RequestType + >>> id_1 = requests.hold_request(RequestType.held_message, 'hold_1') + >>> id_2 = requests.hold_request(RequestType.subscription, 'hold_2') + >>> id_3 = requests.hold_request(RequestType.unsubscription, 'hold_3') + >>> id_4 = requests.hold_request(RequestType.held_message, 'hold_4') + >>> id_1, id_2, id_3, id_4 + (1, 2, 3, 4) + >>> flush() + +And of course, now we can see that there are four requests being held. + + >>> requests.count + 4 + >>> show_holds(requests) + 1 RequestType.held_message + 2 RequestType.subscription + 3 RequestType.unsubscription + 4 RequestType.held_message + +If we try to hold a request with a bogus type, we get an exception. + + >>> requests.hold_request(5, 'foo') + Traceback (most recent call last): + ... + TypeError: 5 + +We can hold requests with additional data. + + >>> data = dict(foo='yes', bar='no') + >>> id_5 = requests.hold_request(RequestType.held_message, 'hold_5', data) + >>> flush() + >>> id_5 + 5 + >>> requests.count + 5 + >>> show_holds(requests) + 1 RequestType.held_message + 2 RequestType.subscription + 3 RequestType.unsubscription + 4 RequestType.held_message + 5 RequestType.held_message + + +Getting requests +---------------- + +We can ask the requests database for a specific request, by providing the id +of the request data we want. This returns a 2-tuple of the key and data we +originally held. + + >>> key, data = requests.get_request(2) + >>> key + 'hold_2' + +Because we did not store additional data with request 2, it comes back as None +now. + + >>> print data + None + +However, if we ask for a request that had data, we'd get it back now. + + >>> key, data = requests.get_request(5) + >>> key + 'hold_5' + >>> sorted(data.items()) + [('bar', 'no'), ('foo', 'yes')] + +If we ask for a request that is not in the database, we get None back. + + >>> print requests.get_request(801) + None + + +Deleting requests +----------------- + +Once a specific request has been handled, it will be deleted from the requests +database. + + >>> requests.delete_request(2) + >>> flush() + >>> requests.count + 4 + >>> show_holds(requests) + 1 RequestType.held_message + 3 RequestType.unsubscription + 4 RequestType.held_message + 5 RequestType.held_message + >>> print requests.get_request(2) + None + +We get an exception if we ask to delete a request that isn't in the database. + + >>> requests.delete_request(801) + Traceback (most recent call last): + ... + KeyError: 801 === added file 'Mailman/interfaces/requests.py' --- a/Mailman/interfaces/requests.py 1970-01-01 00:00:00 +0000 +++ b/Mailman/interfaces/requests.py 2007-08-08 04:24:13 +0000 @@ -0,0 +1,87 @@ +# Copyright (C) 2007 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, +# USA. + +"""Interfaces for the request database. + +The request database handles events that must be approved by the list +moderators, such as subscription requests and held messages. +""" + +from munepy import Enum +from zope.interface import Interface, Attribute + + + +class RequestType(Enum): + held_message = 1 + subscription = 2 + unsubscription = 3 + + + +class IListRequests(Interface): + """Held requests for a specific mailing list.""" + + mailing_list = Attribute( + """The IMailingList for these requests.""") + + count = Attribute( + """The total number of requests held for the mailing list.""") + + def hold_request(request_type, key, data=None): + """Hold some data for moderator approval. + + :param request_type: A `Request` enum value. + :param key: The key piece of request data being held. + :param data: Additional optional data in the form of a dictionary that + is associated with the held request. + :return: A unique id for this held request. + """ + + held_requests = Attribute( + """An iterator over the held requests, yielding a 2-tuple. + + The tuple has the form: (id, type) where `id` is the held request's + unique id and the `type` is a `Request` enum value. + """) + + def get_request(request_id): + """Get the data associated with the request id, or None. + + :param request_id: The unique id for the request. + :return: A 2-tuple of the key and data originally held, or None if the + `request_id` is not in the database. + """ + + def delete_request(request_id): + """Delete the request associated with the id. + + :param request_id: The unique id for the request. + :raises KeyError: If `request_id` is not in the database. + """ + + + +class IRequests(Interface): + """The requests database.""" + + def get_list_requests(mailing_list): + """Return the `IListRequests` object for the given mailing list. + + :param mailing_list: An `IMailingList`. + :return: An `IListRequests` object for the mailing list. + """ === modified file 'Mailman/database/__init__.py' --- a/Mailman/database/__init__.py 2007-08-02 14:47:56 +0000 +++ b/Mailman/database/__init__.py 2007-08-08 04:24:13 +0000 @@ -33,8 +33,10 @@ from Mailman.database.usermanager import UserManager from Mailman.database.messagestore import MessageStore from Mailman.database.model import Pendings +from Mailman.database.model import Requests -# Test suite convenience. +# Test suite convenience. Application code should use config.db.flush() +# instead. flush = None @@ -51,6 +53,7 @@ self.user_manager = None self.message_store = None self.pendings = None + self.requests = None def initialize(self): from Mailman.LockFile import LockFile @@ -65,6 +68,7 @@ self.user_manager = UserManager() self.message_store = MessageStore() self.pendings = Pendings() + self.requests = Requests() self.flush() def flush(self): === modified file 'Mailman/database/model/__init__.py' --- a/Mailman/database/model/__init__.py 2007-08-01 20:11:08 +0000 +++ b/Mailman/database/model/__init__.py 2007-08-08 04:24:13 +0000 @@ -48,6 +48,7 @@ from Mailman.database.model.message import Message from Mailman.database.model.pending import Pendings from Mailman.database.model.preferences import Preferences +from Mailman.database.model.requests import Requests from Mailman.database.model.user import User from Mailman.database.model.version import Version === modified file 'Mailman/database/model/pending.py' --- a/Mailman/database/model/pending.py 2007-08-01 20:11:08 +0000 +++ b/Mailman/database/model/pending.py 2007-08-08 04:24:13 +0000 @@ -15,6 +15,8 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. +"""Implementations of the IPendable and IPending interfaces.""" + import time import random import hashlib === modified file 'Mailman/interfaces/__init__.py' --- a/Mailman/interfaces/__init__.py 2007-05-28 20:21:41 +0000 +++ b/Mailman/interfaces/__init__.py 2007-08-08 04:24:13 +0000 @@ -18,6 +18,7 @@ import os import sys +from munepy import Enum from zope.interface import implementedBy from zope.interface.interfaces import IInterface @@ -38,7 +39,11 @@ module = sys.modules[modname] for name in dir(module): obj = getattr(module, name) - if IInterface.providedBy(obj): + try: + is_enum = issubclass(obj, Enum) + except TypeError: + is_enum = False + if IInterface.providedBy(obj) or is_enum: setattr(iface_mod, name, obj) __all__.append(name) === modified file 'Mailman/interfaces/pending.py' --- a/Mailman/interfaces/pending.py 2007-08-01 20:11:08 +0000 +++ b/Mailman/interfaces/pending.py 2007-08-08 04:24:13 +0000 @@ -15,9 +15,13 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. -"""Interfaces for the pending database.""" - -from munepy import Enum +"""Interfaces for the pending database. + +The pending database contains events that must be confirmed by the user. It +maps these events to a unique hash that can be used as a token for end user +confirmation. +""" + from zope.interface import Interface, Attribute -- https://code.launchpad.net/~mailman-coders/mailman/3.0 You are receiving this branch notification because you are subscribed to it. To unsubscribe from this branch go to https://code.launchpad.net/~mailman-coders/mailman/3.0/+subscription/mailman-checkins. _______________________________________________ Mailman-checkins mailing list Mailman-checkins@python.org Unsubscribe: http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org