summaryrefslogtreecommitdiff
path: root/Mailman/database
diff options
context:
space:
mode:
Diffstat (limited to 'Mailman/database')
-rw-r--r--Mailman/database/model/__init__.py9
-rw-r--r--Mailman/database/model/address.py21
-rw-r--r--Mailman/database/model/language.py2
-rw-r--r--Mailman/database/model/mailinglist.py122
-rw-r--r--Mailman/database/model/member.py61
-rw-r--r--Mailman/database/model/profile.py31
-rw-r--r--Mailman/database/model/roster.py177
-rw-r--r--Mailman/database/model/user.py10
-rw-r--r--Mailman/database/model/version.py8
-rw-r--r--Mailman/database/usermanager.py51
10 files changed, 288 insertions, 204 deletions
diff --git a/Mailman/database/model/__init__.py b/Mailman/database/model/__init__.py
index 11ca11f89..612510632 100644
--- a/Mailman/database/model/__init__.py
+++ b/Mailman/database/model/__init__.py
@@ -19,9 +19,7 @@ __all__ = [
'Address',
'Language',
'MailingList',
- 'Profile',
- 'Roster',
- 'RosterSet',
+ 'Preferences',
'User',
'Version',
]
@@ -44,9 +42,8 @@ from Mailman.configuration import config
from Mailman.database.model.address import Address
from Mailman.database.model.language import Language
from Mailman.database.model.mailinglist import MailingList
-from Mailman.database.model.profile import Profile
-from Mailman.database.model.roster import Roster
-from Mailman.database.model.rosterset import RosterSet
+from Mailman.database.model.member import Member
+from Mailman.database.model.profile import Preferences
from Mailman.database.model.user import User
from Mailman.database.model.version import Version
diff --git a/Mailman/database/model/address.py b/Mailman/database/model/address.py
index 53d5016e5..450c40235 100644
--- a/Mailman/database/model/address.py
+++ b/Mailman/database/model/address.py
@@ -21,11 +21,12 @@ from zope.interface import implements
from Mailman.interfaces import IAddress
-
-ROSTER_KIND = 'Mailman.database.model.roster.Roster'
-USER_KIND = 'Mailman.database.model.user.User'
+MEMBER_KIND = 'Mailman.database.model.member.Member'
+PREFERENCE_KIND = 'Mailman.database.model.profile.Preferences'
+USER_KIND = 'Mailman.database.model.user.User'
+
class Address(Entity):
implements(IAddress)
@@ -35,8 +36,10 @@ class Address(Entity):
has_field('registered_on', DateTime)
has_field('validated_on', DateTime)
# Relationships
- has_and_belongs_to_many('rosters', of_kind=ROSTER_KIND)
- belongs_to('user', of_kind=USER_KIND)
+ belongs_to('user', of_kind=USER_KIND)
+ belongs_to('preferences', of_kind=PREFERENCE_KIND)
+ # Options
+ using_options(shortnames=True)
def __str__(self):
return formataddr((self.real_name, self.address))
@@ -44,3 +47,11 @@ class Address(Entity):
def __repr__(self):
return '<Address: %s [%s]>' % (
str(self), ('verified' if self.verified else 'not verified'))
+
+ def subscribe(self, mlist, role):
+ from Mailman.database.model import Member
+ # This member has no preferences by default.
+ member = Member(role=role,
+ mailing_list=mlist.fqdn_listname,
+ address=self)
+ return member
diff --git a/Mailman/database/model/language.py b/Mailman/database/model/language.py
index 3597a128d..e065d5bad 100644
--- a/Mailman/database/model/language.py
+++ b/Mailman/database/model/language.py
@@ -20,3 +20,5 @@ from elixir import *
class Language(Entity):
has_field('code', Unicode)
+ # Options
+ using_options(shortnames=True)
diff --git a/Mailman/database/model/mailinglist.py b/Mailman/database/model/mailinglist.py
index 28e2c11dc..edd2eab0d 100644
--- a/Mailman/database/model/mailinglist.py
+++ b/Mailman/database/model/mailinglist.py
@@ -50,16 +50,6 @@ class MailingList(Entity):
has_field('one_last_digest', PickleType),
has_field('volume', Integer),
has_field('last_post_time', Float),
- # OldStyleMemberships attributes, temporarily stored as pickles.
- has_field('bounce_info', PickleType),
- has_field('delivery_status', PickleType),
- has_field('digest_members', PickleType),
- has_field('language', PickleType),
- has_field('members', PickleType),
- has_field('passwords', PickleType),
- has_field('topics_userinterest', PickleType),
- has_field('user_options', PickleType),
- has_field('usernames', PickleType),
# Attributes which are directly modifiable via the web u/i. The more
# complicated attributes are currently stored as pickles, though that
# will change as the schema and implementation is developed.
@@ -163,113 +153,29 @@ class MailingList(Entity):
has_field('umbrella_member_suffix', Unicode),
has_field('unsubscribe_policy', Integer),
has_field('welcome_msg', Unicode),
- # Indirect relationships
- has_field('owner_rosterset', Unicode),
- has_field('moderator_rosterset', Unicode),
# Relationships
## has_and_belongs_to_many(
## 'available_languages',
## of_kind='Mailman.database.model.languages.Language')
+ # Options
+ using_options(shortnames=True)
def __init__(self, fqdn_listname):
super(MailingList, self).__init__()
listname, hostname = split_listname(fqdn_listname)
self.list_name = listname
self.host_name = hostname
- # Create two roster sets, one for the owners and one for the
- # moderators. MailingLists are connected to RosterSets indirectly, in
- # order to preserve the ability to store user data and list data in
- # different databases.
- name = fqdn_listname + ' owners'
- self.owner_rosterset = name
- roster = config.user_manager.create_roster(name)
- config.user_manager.create_rosterset(name).add(roster)
- name = fqdn_listname + ' moderators'
- self.moderator_rosterset = name
- roster = config.user_manager.create_roster(name)
- config.user_manager.create_rosterset(name).add(roster)
-
- def delete_rosters(self):
- listname = fqdn_listname(self.list_name, self.host_name)
- # Delete the list owner roster and roster set.
- name = listname + ' owners'
- roster = config.user_manager.get_roster(name)
- assert roster, 'Missing roster: %s' % name
- config.user_manager.delete_roster(roster)
- rosterset = config.user_manager.get_rosterset(name)
- assert rosterset, 'Missing roster set: %s' % name
- config.user_manager.delete_rosterset(rosterset)
- name = listname + ' moderators'
- roster = config.user_manager.get_roster(name)
- assert roster, 'Missing roster: %s' % name
- config.user_manager.delete_roster(roster)
- rosterset = config.user_manager.get_rosterset(name)
- assert rosterset, 'Missing roster set: %s' % name
- config.user_manager.delete_rosterset(rosterset)
-
- # IMailingListRosters
+ # Create several rosters for filtering out or querying the membership
+ # table.
+ from Mailman.database.model import roster
+ self.owners = roster.OwnerRoster(self)
+ self.moderators = roster.ModeratorRoster(self)
+ self.administrators = roster.AdministratorRoster(self)
+ self.members = roster.MemberRoster(self)
+ self.regular_members = roster.RegularMemberRoster(self)
+ self.digest_members = roster.DigestMemberRoster(self)
@property
- def owners(self):
- for user in _collect_users(self.owner_rosterset):
- yield user
-
- @property
- def moderators(self):
- for user in _collect_users(self.moderator_rosterset):
- yield user
-
- @property
- def administrators(self):
- for user in _collect_users(self.owner_rosterset,
- self.moderator_rosterset):
- yield user
-
- @property
- def owner_rosters(self):
- rosterset = config.user_manager.get_rosterset(self.owner_rosterset)
- for roster in rosterset.rosters:
- yield roster
-
- @property
- def moderator_rosters(self):
- rosterset = config.user_manager.get_rosterset(self.moderator_rosterset)
- for roster in rosterset.rosters:
- yield roster
-
- def add_owner_roster(self, roster):
- rosterset = config.user_manager.get_rosterset(self.owner_rosterset)
- rosterset.add(roster)
-
- def delete_owner_roster(self, roster):
- rosterset = config.user_manager.get_rosterset(self.owner_rosterset)
- rosterset.delete(roster)
-
- def add_moderator_roster(self, roster):
- rosterset = config.user_manager.get_rosterset(self.moderator_rosterset)
- rosterset.add(roster)
-
- def delete_moderator_roster(self, roster):
- rosterset = config.user_manager.get_rosterset(self.moderator_rosterset)
- rosterset.delete(roster)
-
-
-
-def _collect_users(*rosterset_names):
- users = set()
- for name in rosterset_names:
- # We have to indirectly look up the roster set's name in the user
- # manager. This is how we enforce separation between the list manager
- # and the user manager storages.
- rosterset = config.user_manager.get_rosterset(name)
- assert rosterset is not None, 'No RosterSet named: %s' % name
- for roster in rosterset.rosters:
- # Rosters collect addresses. It's not required that an address is
- # linked to a user, but it must be the case that all addresses on
- # the owner roster are linked to a user. Get the user that's
- # linked to each address and add it to the set.
- for address in roster.addresses:
- user = config.user_manager.get_user(address.address)
- assert user is not None, 'Unlinked address: ' + address.address
- users.add(user)
- return users
+ def fqdn_listname(self):
+ """See IMailingListIdentity."""
+ return fqdn_listname(self.list_name, self.host_name)
diff --git a/Mailman/database/model/member.py b/Mailman/database/model/member.py
new file mode 100644
index 000000000..db37ebb49
--- /dev/null
+++ b/Mailman/database/model/member.py
@@ -0,0 +1,61 @@
+# 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.
+
+from elixir import *
+from zope.interface import implements
+
+from Mailman.Utils import split_listname
+from Mailman.constants import SystemDefaultPreferences
+from Mailman.database.types import EnumType
+from Mailman.interfaces import IMember, IPreferences
+
+
+ADDRESS_KIND = 'Mailman.database.model.address.Address'
+PREFERENCE_KIND = 'Mailman.database.model.profile.Preferences'
+
+
+
+class Member(Entity):
+ implements(IMember)
+
+ has_field('role', EnumType)
+ has_field('mailing_list', Unicode)
+ # Relationships
+ belongs_to('address', of_kind=ADDRESS_KIND)
+ belongs_to('_preferences', of_kind=PREFERENCE_KIND)
+ # Options
+ using_options(shortnames=True)
+
+ def __repr__(self):
+ return '<Member: %s on %s as %s>' % (
+ self.address, self.mailing_list, self.role)
+
+ @property
+ def preferences(self):
+ from Mailman.database.model import MailingList
+ if self._preferences:
+ return self._preferences
+ if self.address.preferences:
+ return self.address.preferences
+ if self.address.user.preferences:
+ return self.address.user.preferences
+ list_name, host_name = split_listname(self.mailing_list)
+ mlist = MailingList.get_by(list_name=list_name,
+ host_name=host_name)
+ if mlist.preferences:
+ return mlist.preferences
+ return SystemDefaultPreferences
diff --git a/Mailman/database/model/profile.py b/Mailman/database/model/profile.py
index 49e108728..935ae08fb 100644
--- a/Mailman/database/model/profile.py
+++ b/Mailman/database/model/profile.py
@@ -19,13 +19,18 @@ from elixir import *
from email.utils import formataddr
from zope.interface import implements
-from Mailman.constants import DeliveryMode
+from Mailman.constants import SystemDefaultPreferences as Prefs
from Mailman.database.types import EnumType
-from Mailman.interfaces import IProfile
+from Mailman.interfaces import IPreferences
+ADDRESS_KIND = 'Mailman.database.model.address.Address'
+MEMBER_KIND = 'Mailman.database.model.member.Member'
+USER_KIND = 'Mailman.database.model.user.User'
-class Profile(Entity):
- implements(IProfile)
+
+
+class Preferences(Entity):
+ implements(IPreferences)
has_field('acknowledge_posts', Boolean)
has_field('hide_address', Boolean)
@@ -33,14 +38,14 @@ class Profile(Entity):
has_field('receive_list_copy', Boolean)
has_field('receive_own_postings', Boolean)
has_field('delivery_mode', EnumType)
- # Relationships
- belongs_to('user', of_kind='Mailman.database.model.user.User')
+ # Options
+ using_options(shortnames=True)
def __init__(self):
- super(Profile, self).__init__()
- self.acknowledge_posts = False
- self.hide_address = True
- self.preferred_language = 'en'
- self.receive_list_copy = True
- self.receive_own_postings = True
- self.delivery_mode = DeliveryMode.regular
+ super(Preferences, self).__init__()
+ self.acknowledge_posts = Prefs.acknowledge_posts
+ self.hide_address = Prefs.hide_address
+ self.preferred_language = Prefs.preferred_language
+ self.receive_list_copy = Prefs.receive_list_copy
+ self.receive_own_postings = Prefs.receive_own_postings
+ self.delivery_mode = Prefs.delivery_mode
diff --git a/Mailman/database/model/roster.py b/Mailman/database/model/roster.py
index bf8447433..03aa9efc3 100644
--- a/Mailman/database/model/roster.py
+++ b/Mailman/database/model/roster.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 by the Free Software Foundation, Inc.
+# 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
@@ -15,37 +15,164 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
-from elixir import *
+"""An implementation of an IRoster.
+
+These are hard-coded rosters which know how to filter a set of members to find
+the ones that fit a particular role. These are used as the member, owner,
+moderator, and administrator roster filters.
+"""
+
from zope.interface import implements
-from Mailman.Errors import ExistingAddressError
+from Mailman.constants import DeliveryMode, MemberRole
+from Mailman.database.model import Member
from Mailman.interfaces import IRoster
-ADDRESS_KIND = 'Mailman.database.model.address.Address'
-ROSTERSET_KIND = 'Mailman.database.model.rosterset.RosterSet'
+
+class AbstractRoster(object):
+ """An abstract IRoster class.
+ This class takes the simple approach of implemented the 'users' and
+ 'addresses' properties in terms of the 'members' property. This may not
+ be the most efficient way, but it works.
-class Roster(Entity):
+ This requires that subclasses implement the 'members' property.
+ """
implements(IRoster)
- has_field('name', Unicode)
- # Relationships
- has_and_belongs_to_many('addresses', of_kind=ADDRESS_KIND)
- has_and_belongs_to_many('roster_set', of_kind=ROSTERSET_KIND)
+ def __init__(self, mlist):
+ self._mlist = mlist
+
+ @property
+ def members(self):
+ raise NotImplementedError
+
+ @property
+ def users(self):
+ # Members are linked to addresses, which in turn are linked to users.
+ # So while the 'members' attribute does most of the work, we have to
+ # keep a set of unique users. It's possible for the same user to be
+ # subscribed to a mailing list multiple times with different
+ # addresses.
+ users = set(member.address.user for member in self.members)
+ for user in users:
+ yield user
+
+ @property
+ def addresses(self):
+ # Every Member is linked to exactly one address so the 'members'
+ # attribute does most of the work.
+ for member in self.members:
+ yield member.address
+
+
+
+class MemberRoster(AbstractRoster):
+ """Return all the members of a list."""
+
+ name = 'member'
+
+ @property
+ def members(self):
+ # Query for all the Members which have a role of MemberRole.member and
+ # are subscribed to this mailing list. XXX we have to use a private
+ # data attribute of MailList for now.
+ for member in Member.select_by(mailing_list=self._mlist.fqdn_listname,
+ role=MemberRole.member):
+ yield member
+
+
+
+class OwnerRoster(AbstractRoster):
+ """Return all the owners of a list."""
+
+ name = 'owner'
+
+ @property
+ def members(self):
+ # Query for all the Members which have a role of MemberRole.member and
+ # are subscribed to this mailing list. XXX we have to use a private
+ # data attribute of MailList for now.
+ for member in Member.select_by(mailing_list=self._mlist.fqdn_listname,
+ role=MemberRole.owner):
+ yield member
+
+
+
+class ModeratorRoster(AbstractRoster):
+ """Return all the owners of a list."""
+
+ name = 'moderator'
+
+ @property
+ def members(self):
+ # Query for all the Members which have a role of MemberRole.member and
+ # are subscribed to this mailing list. XXX we have to use a private
+ # data attribute of MailList for now.
+ for member in Member.select_by(mailing_list=self._mlist.fqdn_listname,
+ role=MemberRole.moderator):
+ yield member
+
+
+
+class AdministratorRoster(AbstractRoster):
+ """Return all the administrators of a list."""
+
+ name = 'administrator'
+
+ @property
+ def members(self):
+ # Administrators are defined as the union of the owners and the
+ # moderators. Until I figure out a more efficient way of doing this,
+ # this will have to do.
+ owners = Member.select_by(mailing_list=self._mlist.fqdn_listname,
+ role=MemberRole.owner)
+ moderators = Member.select_by(mailing_list=self._mlist.fqdn_listname,
+ role=MemberRole.moderator)
+ members = set(owners)
+ members.update(set(moderators))
+ for member in members:
+ yield member
+
+
+
+class RegularMemberRoster(AbstractRoster):
+ """Return all the regular delivery members of a list."""
+
+ name = 'regular_members'
+
+ @property
+ def members(self):
+ # Query for all the Members which have a role of MemberRole.member and
+ # are subscribed to this mailing list. Then return only those members
+ # that have a regular delivery mode.
+ for member in Member.select_by(mailing_list=self._mlist.fqdn_listname,
+ role=MemberRole.member):
+ if member.preferences.delivery_mode == DeliveryMode.regular:
+ yield member
+
+
+
+_digest_modes = (
+ DeliveryMode.mime_digests,
+ DeliveryMode.plaintext_digests,
+ DeliveryMode.summary_digests,
+ )
+
+
+
+class DigestMemberRoster(AbstractRoster):
+ """Return all the regular delivery members of a list."""
+
+ name = 'regular_members'
- def create(self, email_address, real_name=None):
- """See IRoster"""
- from Mailman.database.model.address import Address
- addr = Address.get_by(address=email_address)
- if addr:
- raise ExistingAddressError(email_address)
- addr = Address(address=email_address, real_name=real_name)
- # Make sure all the expected links are made, including to the null
- # (i.e. everyone) roster.
- self.addresses.append(addr)
- addr.rosters.append(self)
- null_roster = Roster.get_by(name='')
- null_roster.addresses.append(addr)
- addr.rosters.append(null_roster)
- return addr
+ @property
+ def members(self):
+ # Query for all the Members which have a role of MemberRole.member and
+ # are subscribed to this mailing list. Then return only those members
+ # that have one of the digest delivery modes.
+ for member in Member.select_by(mailing_list=self._mlist.fqdn_listname,
+ role=MemberRole.member):
+ if member.preferences.delivery_mode in _digest_modes:
+ yield member
diff --git a/Mailman/database/model/user.py b/Mailman/database/model/user.py
index be634b9df..d646606a9 100644
--- a/Mailman/database/model/user.py
+++ b/Mailman/database/model/user.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2006-2007 by the Free Software Foundation, Inc.
+# 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
@@ -23,15 +23,19 @@ from Mailman import Errors
from Mailman.database.model import Address
from Mailman.interfaces import IUser
+ADDRESS_KIND = 'Mailman.database.model.address.Address'
+
+
class User(Entity):
implements(IUser)
has_field('real_name', Unicode)
has_field('password', Unicode)
# Relationships
- has_one('profile', of_kind='Mailman.database.model.profile.Profile')
- has_many('addresses', of_kind='Mailman.database.model.address.Address')
+ has_many('addresses', of_kind=ADDRESS_KIND)
+ # Options
+ using_options(shortnames=True)
def link(self, address):
if address.user is not None:
diff --git a/Mailman/database/model/version.py b/Mailman/database/model/version.py
index e22e8ae11..7b12778ce 100644
--- a/Mailman/database/model/version.py
+++ b/Mailman/database/model/version.py
@@ -19,7 +19,7 @@ from elixir import *
class Version(Entity):
- with_fields(
- component = Field(String),
- version = Field(Integer),
- )
+ has_field('component', Unicode)
+ has_field('version', Integer)
+ # Options
+ using_options(shortnames=True)
diff --git a/Mailman/database/usermanager.py b/Mailman/database/usermanager.py
index 97a740803..ed0b552a8 100644
--- a/Mailman/database/usermanager.py
+++ b/Mailman/database/usermanager.py
@@ -35,47 +35,18 @@ from Mailman.interfaces import IUserManager
class UserManager(object):
implements(IUserManager)
- def __init__(self):
- # Create the null roster if it does not already exist. It's more
- # likely to exist than not so try to get it before creating it.
- lockfile = os.path.join(config.LOCK_DIR, '<umgrcreatelock>')
- with LockFile(lockfile):
- roster = self.get_roster('')
- if roster is None:
- self.create_roster('')
- objectstore.flush()
-
- def create_roster(self, name):
- roster = Roster.get_by(name=name)
- if roster:
- raise Errors.RosterExistsError(name)
- return Roster(name=name)
-
- def get_roster(self, name):
- return Roster.get_by(name=name)
-
- def delete_roster(self, roster):
- roster.delete()
-
- @property
- def rosters(self):
- for roster in Roster.select():
- yield roster
-
- def create_rosterset(self, name):
- return RosterSet(name=name)
-
- def delete_rosterset(self, rosterset):
- rosterset.delete()
-
- def get_rosterset(self, name):
- return RosterSet.get_by(name=name)
-
- def create_user(self):
+ def create_user(self, address=None, real_name=None):
user = User()
- # Users always have a profile
- user.profile = Profile()
- user.profile.user = user
+ # Users always have preferences
+ user.preferences = Preferences()
+ user.preferences.user = user
+ if real_name:
+ user.real_name = real_name
+ if address:
+ kws = dict(address=address)
+ if real_name:
+ kws['real_name'] = real_name
+ user.link(Address(**kws))
return user
def delete_user(self, user):