------------------------------------------------------------
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
[email protected]
Unsubscribe:
http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org