------------------------------------------------------------
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

Reply via email to