summaryrefslogtreecommitdiff
path: root/Mailman/MailList.py
diff options
context:
space:
mode:
authorBarry Warsaw2008-02-27 01:26:18 -0500
committerBarry Warsaw2008-02-27 01:26:18 -0500
commita1c73f6c305c7f74987d99855ba59d8fa823c253 (patch)
tree65696889450862357c9e05c8e9a589f1bdc074ac /Mailman/MailList.py
parent3f31f8cce369529d177cfb5a7c66346ec1e12130 (diff)
downloadmailman-a1c73f6c305c7f74987d99855ba59d8fa823c253.tar.gz
mailman-a1c73f6c305c7f74987d99855ba59d8fa823c253.tar.zst
mailman-a1c73f6c305c7f74987d99855ba59d8fa823c253.zip
Diffstat (limited to 'Mailman/MailList.py')
-rw-r--r--Mailman/MailList.py731
1 files changed, 0 insertions, 731 deletions
diff --git a/Mailman/MailList.py b/Mailman/MailList.py
deleted file mode 100644
index 3f16fbaaa..000000000
--- a/Mailman/MailList.py
+++ /dev/null
@@ -1,731 +0,0 @@
-# Copyright (C) 1998-2008 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.
-
-
-"""The class representing a Mailman mailing list.
-
-Mixes in many task-specific classes.
-"""
-
-from __future__ import with_statement
-
-import os
-import re
-import sys
-import time
-import errno
-import shutil
-import socket
-import urllib
-import cPickle
-import logging
-import marshal
-import email.Iterators
-
-from UserDict import UserDict
-from cStringIO import StringIO
-from string import Template
-from types import MethodType
-from urlparse import urlparse
-from zope.interface import implements
-
-from email.Header import Header
-from email.Utils import getaddresses, formataddr, parseaddr
-
-from Mailman import Errors
-from Mailman import Utils
-from Mailman import Version
-from Mailman import database
-from Mailman.UserDesc import UserDesc
-from Mailman.configuration import config
-from Mailman.interfaces import *
-
-# Base classes
-from Mailman.Archiver import Archiver
-from Mailman.Bouncer import Bouncer
-from Mailman.Digester import Digester
-from Mailman.SecurityManager import SecurityManager
-
-# GUI components package
-from Mailman import Gui
-
-# Other useful classes
-from Mailman import i18n
-from Mailman import MemberAdaptor
-from Mailman import Message
-
-_ = i18n._
-
-DOT = '.'
-EMPTYSTRING = ''
-OR = '|'
-
-clog = logging.getLogger('mailman.config')
-elog = logging.getLogger('mailman.error')
-vlog = logging.getLogger('mailman.vette')
-slog = logging.getLogger('mailman.subscribe')
-
-
-
-# Use mixins here just to avoid having any one chunk be too large.
-class MailList(object, Archiver, Digester, SecurityManager, Bouncer):
-
- implements(
- IMailingList,
- IMailingListAddresses,
- IMailingListIdentity,
- IMailingListRosters,
- )
-
- def __init__(self, data):
- self._data = data
- # Only one level of mixin inheritance allowed.
- for baseclass in self.__class__.__bases__:
- if hasattr(baseclass, '__init__'):
- baseclass.__init__(self)
- # Initialize the web u/i components.
- self._gui = []
- for component in dir(Gui):
- if component.startswith('_'):
- continue
- self._gui.append(getattr(Gui, component)())
- # Give the extension mechanism a chance to process this list.
- try:
- from Mailman.ext import init_mlist
- except ImportError:
- pass
- else:
- init_mlist(self)
-
- def __getattr__(self, name):
- missing = object()
- if name.startswith('_'):
- return getattr(super(MailList, self), name)
- # Delegate to the database model object if it has the attribute.
- obj = getattr(self._data, name, missing)
- if obj is not missing:
- return obj
- # Finally, delegate to one of the gui components.
- for guicomponent in self._gui:
- obj = getattr(guicomponent, name, missing)
- if obj is not missing:
- return obj
- # Nothing left to delegate to, so it's got to be an error.
- raise AttributeError(name)
-
- def __repr__(self):
- return '<mailing list "%s" at %x>' % (self.fqdn_listname, id(self))
-
-
- def GetConfirmJoinSubject(self, listname, cookie):
- if config.VERP_CONFIRMATIONS and cookie:
- cset = i18n.get_translation().charset() or \
- Utils.GetCharSet(self.preferred_language)
- subj = Header(
- _('Your confirmation is required to join the %(listname)s mailing list'),
- cset, header_name='subject')
- return subj
- else:
- return 'confirm ' + cookie
-
- def GetConfirmLeaveSubject(self, listname, cookie):
- if config.VERP_CONFIRMATIONS and cookie:
- cset = i18n.get_translation().charset() or \
- Utils.GetCharSet(self.preferred_language)
- subj = Header(
- _('Your confirmation is required to leave the %(listname)s mailing list'),
- cset, header_name='subject')
- return subj
- else:
- return 'confirm ' + cookie
-
- def GetMemberAdminEmail(self, member):
- """Usually the member addr, but modified for umbrella lists.
-
- Umbrella lists have other mailing lists as members, and so admin stuff
- like confirmation requests and passwords must not be sent to the
- member addresses - the sublists - but rather to the administrators of
- the sublists. This routine picks the right address, considering
- regular member address to be their own administrative addresses.
-
- """
- if not self.umbrella_list:
- return member
- else:
- acct, host = tuple(member.split('@'))
- return "%s%s@%s" % (acct, self.umbrella_member_suffix, host)
-
- def GetScriptURL(self, target, absolute=False):
- if absolute:
- return self.web_page_url + target + '/' + self.fqdn_listname
- else:
- return Utils.ScriptURL(target) + '/' + self.fqdn_listname
-
- def GetOptionsURL(self, user, obscure=False, absolute=False):
- url = self.GetScriptURL('options', absolute)
- if obscure:
- user = Utils.ObscureEmail(user)
- return '%s/%s' % (url, urllib.quote(user.lower()))
-
-
- #
- # Web API support via administrative categories
- #
- def GetConfigCategories(self):
- class CategoryDict(UserDict):
- def __init__(self):
- UserDict.__init__(self)
- self.keysinorder = config.ADMIN_CATEGORIES[:]
- def keys(self):
- return self.keysinorder
- def items(self):
- items = []
- for k in config.ADMIN_CATEGORIES:
- items.append((k, self.data[k]))
- return items
- def values(self):
- values = []
- for k in config.ADMIN_CATEGORIES:
- values.append(self.data[k])
- return values
-
- categories = CategoryDict()
- # Only one level of mixin inheritance allowed
- for gui in self._gui:
- k, v = gui.GetConfigCategory()
- categories[k] = (v, gui)
- return categories
-
- def GetConfigSubCategories(self, category):
- for gui in self._gui:
- if hasattr(gui, 'GetConfigSubCategories'):
- # Return the first one that knows about the given subcategory
- subcat = gui.GetConfigSubCategories(category)
- if subcat is not None:
- return subcat
- return None
-
- def GetConfigInfo(self, category, subcat=None):
- for gui in self._gui:
- if hasattr(gui, 'GetConfigInfo'):
- value = gui.GetConfigInfo(self, category, subcat)
- if value:
- return value
-
-
- #
- # Membership management front-ends and assertion checks
- #
- def InviteNewMember(self, userdesc, text=''):
- """Invite a new member to the list.
-
- This is done by creating a subscription pending for the user, and then
- crafting a message to the member informing them of the invitation.
- """
- invitee = userdesc.address
- Utils.ValidateEmail(invitee)
- # check for banned address
- pattern = Utils.get_pattern(invitee, self.ban_list)
- if pattern:
- raise Errors.MembershipIsBanned(pattern)
- # Hack alert! Squirrel away a flag that only invitations have, so
- # that we can do something slightly different when an invitation
- # subscription is confirmed. In those cases, we don't need further
- # admin approval, even if the list is so configured. The flag is the
- # list name to prevent invitees from cross-subscribing.
- userdesc.invitation = self.internal_name()
- cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc)
- requestaddr = self.getListAddress('request')
- confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1),
- cookie)
- listname = self.real_name
- text += Utils.maketext(
- 'invite.txt',
- {'email' : invitee,
- 'listname' : listname,
- 'hostname' : self.host_name,
- 'confirmurl' : confirmurl,
- 'requestaddr': requestaddr,
- 'cookie' : cookie,
- 'listowner' : self.GetOwnerEmail(),
- }, mlist=self)
- sender = self.GetRequestEmail(cookie)
- msg = Message.UserNotification(
- invitee, sender,
- text=text, lang=self.preferred_language)
- subj = self.GetConfirmJoinSubject(listname, cookie)
- del msg['subject']
- msg['Subject'] = subj
- msg.send(self)
-
- def AddMember(self, userdesc, remote=None):
- """Front end to member subscription.
-
- This method enforces subscription policy, validates values, sends
- notifications, and any other grunt work involved in subscribing a
- user. It eventually calls ApprovedAddMember() to do the actual work
- of subscribing the user.
-
- userdesc is an instance with the following public attributes:
-
- address -- the unvalidated email address of the member
- fullname -- the member's full name (i.e. John Smith)
- digest -- a flag indicating whether the user wants digests or not
- language -- the requested default language for the user
- password -- the user's password
-
- Other attributes may be defined later. Only address is required; the
- others all have defaults (fullname='', digests=0, language=list's
- preferred language, password=generated).
-
- remote is a string which describes where this add request came from.
- """
- assert self.Locked()
- # Suck values out of userdesc, apply defaults, and reset the userdesc
- # attributes (for passing on to ApprovedAddMember()). Lowercase the
- # addr's domain part.
- email = Utils.LCDomain(userdesc.address)
- name = getattr(userdesc, 'fullname', '')
- lang = getattr(userdesc, 'language', self.preferred_language)
- digest = getattr(userdesc, 'digest', None)
- password = getattr(userdesc, 'password', Utils.MakeRandomPassword())
- if digest is None:
- if self.nondigestable:
- digest = 0
- else:
- digest = 1
- # Validate the e-mail address to some degree.
- Utils.ValidateEmail(email)
- if self.isMember(email):
- raise Errors.MMAlreadyAMember, email
- if email.lower() == self.GetListEmail().lower():
- # Trying to subscribe the list to itself!
- raise Errors.InvalidEmailAddress
- realname = self.real_name
- # Is the subscribing address banned from this list?
- pattern = Utils.get_pattern(email, self.ban_list)
- if pattern:
- vlog.error('%s banned subscription: %s (matched: %s)',
- realname, email, pattern)
- raise Errors.MembershipIsBanned, pattern
- # Sanity check the digest flag
- if digest and not self.digestable:
- raise Errors.MMCantDigestError
- elif not digest and not self.nondigestable:
- raise Errors.MMMustDigestError
-
- userdesc.address = email
- userdesc.fullname = name
- userdesc.digest = digest
- userdesc.language = lang
- userdesc.password = password
-
- # Apply the list's subscription policy. 0 means open subscriptions; 1
- # means the user must confirm; 2 means the admin must approve; 3 means
- # the user must confirm and then the admin must approve
- if self.subscribe_policy == 0:
- self.ApprovedAddMember(userdesc, whence=remote or '')
- elif self.subscribe_policy == 1 or self.subscribe_policy == 3:
- # User confirmation required. BAW: this should probably just
- # accept a userdesc instance.
- cookie = self.pend_new(Pending.SUBSCRIPTION, userdesc)
- # Send the user the confirmation mailback
- if remote is None:
- by = remote = ''
- else:
- by = ' ' + remote
- remote = _(' from %(remote)s')
-
- recipient = self.GetMemberAdminEmail(email)
- confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1),
- cookie)
- text = Utils.maketext(
- 'verify.txt',
- {'email' : email,
- 'listaddr' : self.GetListEmail(),
- 'listname' : realname,
- 'cookie' : cookie,
- 'requestaddr' : self.getListAddress('request'),
- 'remote' : remote,
- 'listadmin' : self.GetOwnerEmail(),
- 'confirmurl' : confirmurl,
- }, lang=lang, mlist=self)
- msg = Message.UserNotification(
- recipient, self.GetRequestEmail(cookie),
- text=text, lang=lang)
- # BAW: See ChangeMemberAddress() for why we do it this way...
- del msg['subject']
- msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie)
- msg['Reply-To'] = self.GetRequestEmail(cookie)
- msg.send(self)
- who = formataddr((name, email))
- slog.info('%s: pending %s %s', self.internal_name(), who, by)
- raise Errors.MMSubscribeNeedsConfirmation
- elif self.HasAutoApprovedSender(email):
- # no approval necessary:
- self.ApprovedAddMember(userdesc)
- else:
- # Subscription approval is required. Add this entry to the admin
- # requests database. BAW: this should probably take a userdesc
- # just like above.
- self.HoldSubscription(email, name, password, digest, lang)
- raise Errors.MMNeedApproval, _(
- 'subscriptions to %(realname)s require moderator approval')
-
- def DeleteMember(self, name, whence=None, admin_notif=None, userack=True):
- realname, email = parseaddr(name)
- if self.unsubscribe_policy == 0:
- self.ApprovedDeleteMember(name, whence, admin_notif, userack)
- else:
- self.HoldUnsubscription(email)
- raise Errors.MMNeedApproval, _(
- 'unsubscriptions require moderator approval')
-
- def ChangeMemberAddress(self, oldaddr, newaddr, globally):
- # Changing a member address consists of verifying the new address,
- # making sure the new address isn't already a member, and optionally
- # going through the confirmation process.
- #
- # Most of these checks are copied from AddMember
- newaddr = Utils.LCDomain(newaddr)
- Utils.ValidateEmail(newaddr)
- # Raise an exception if this email address is already a member of the
- # list, but only if the new address is the same case-wise as the old
- # address and we're not doing a global change.
- if not globally and newaddr == oldaddr and self.isMember(newaddr):
- raise Errors.MMAlreadyAMember
- if newaddr == self.GetListEmail().lower():
- raise Errors.InvalidEmailAddress
- realname = self.real_name
- # Don't allow changing to a banned address. MAS: maybe we should
- # unsubscribe the oldaddr too just for trying, but that's probably
- # too harsh.
- pattern = Utils.get_pattern(newaddr, self.ban_list)
- if pattern:
- vlog.error('%s banned address change: %s -> %s (matched: %s)',
- realname, oldaddr, newaddr, pattern)
- raise Errors.MembershipIsBanned, pattern
- # Pend the subscription change
- cookie = self.pend_new(Pending.CHANGE_OF_ADDRESS,
- oldaddr, newaddr, globally)
- confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1),
- cookie)
- lang = self.getMemberLanguage(oldaddr)
- text = Utils.maketext(
- 'verify.txt',
- {'email' : newaddr,
- 'listaddr' : self.GetListEmail(),
- 'listname' : realname,
- 'cookie' : cookie,
- 'requestaddr': self.getListAddress('request'),
- 'remote' : '',
- 'listadmin' : self.GetOwnerEmail(),
- 'confirmurl' : confirmurl,
- }, lang=lang, mlist=self)
- # BAW: We don't pass the Subject: into the UserNotification
- # constructor because it will encode it in the charset of the language
- # being used. For non-us-ascii charsets, this means it will probably
- # quopri quote it, and thus replies will also be quopri encoded. But
- # CommandRunner doesn't yet grok such headers. So, just set the
- # Subject: in a separate step, although we have to delete the one
- # UserNotification adds.
- msg = Message.UserNotification(
- newaddr, self.GetRequestEmail(cookie),
- text=text, lang=lang)
- del msg['subject']
- msg['Subject'] = self.GetConfirmJoinSubject(realname, cookie)
- msg['Reply-To'] = self.GetRequestEmail(cookie)
- msg.send(self)
-
- def ApprovedChangeMemberAddress(self, oldaddr, newaddr, globally):
- # Check here for banned address in case address was banned after
- # confirmation was mailed. MAS: If it's global change should we just
- # skip this list and proceed to the others? For now we'll throw the
- # exception.
- pattern = Utils.get_pattern(newaddr, self.ban_list)
- if pattern:
- raise Errors.MembershipIsBanned, pattern
- # It's possible they were a member of this list, but choose to change
- # their membership globally. In that case, we simply remove the old
- # address.
- if self.getMemberCPAddress(oldaddr) == newaddr:
- self.removeMember(oldaddr)
- else:
- self.changeMemberAddress(oldaddr, newaddr)
- self.log_and_notify_admin(oldaddr, newaddr)
- # If globally is true, then we also include every list for which
- # oldaddr is a member.
- if not globally:
- return
- for listname in config.list_manager.names:
- # Don't bother with ourselves
- if listname == self.internal_name():
- continue
- mlist = MailList(listname, lock=0)
- if mlist.host_name <> self.host_name:
- continue
- if not mlist.isMember(oldaddr):
- continue
- # If new address is banned from this list, just skip it.
- if Utils.get_pattern(newaddr, mlist.ban_list):
- continue
- mlist.Lock()
- try:
- # Same logic as above, re newaddr is already a member
- if mlist.getMemberCPAddress(oldaddr) == newaddr:
- mlist.removeMember(oldaddr)
- else:
- mlist.changeMemberAddress(oldaddr, newaddr)
- mlist.log_and_notify_admin(oldaddr, newaddr)
- mlist.Save()
- finally:
- mlist.Unlock()
-
- def log_and_notify_admin(self, oldaddr, newaddr):
- """Log member address change and notify admin if requested."""
- slog.info('%s: changed member address from %s to %s',
- self.internal_name(), oldaddr, newaddr)
- if self.admin_notify_mchanges:
- with i18n.using_language(self.preferred_language):
- realname = self.real_name
- subject = _('%(realname)s address change notification')
- name = self.getMemberName(newaddr)
- if name is None:
- name = ''
- if isinstance(name, unicode):
- name = name.encode(Utils.GetCharSet(self.preferred_language),
- 'replace')
- text = Utils.maketext(
- 'adminaddrchgack.txt',
- {'name' : name,
- 'oldaddr' : oldaddr,
- 'newaddr' : newaddr,
- 'listname': self.real_name,
- }, mlist=self)
- msg = Message.OwnerNotification(self, subject, text)
- msg.send(self)
-
-
- #
- # Confirmation processing
- #
- def ProcessConfirmation(self, cookie, context=None):
- rec = self.pend_confirm(cookie)
- if rec is None:
- raise Errors.MMBadConfirmation, 'No cookie record for %s' % cookie
- try:
- op = rec[0]
- data = rec[1:]
- except ValueError:
- raise Errors.MMBadConfirmation, 'op-less data %s' % (rec,)
- if op == Pending.SUBSCRIPTION:
- whence = 'via email confirmation'
- try:
- userdesc = data[0]
- # If confirmation comes from the web, context should be a
- # UserDesc instance which contains overrides of the original
- # subscription information. If it comes from email, then
- # context is a Message and isn't relevant, so ignore it.
- if isinstance(context, UserDesc):
- userdesc += context
- whence = 'via web confirmation'
- addr = userdesc.address
- fullname = userdesc.fullname
- password = userdesc.password
- digest = userdesc.digest
- lang = userdesc.language
- except ValueError:
- raise Errors.MMBadConfirmation, 'bad subscr data %s' % (data,)
- # Hack alert! Was this a confirmation of an invitation?
- invitation = getattr(userdesc, 'invitation', False)
- # We check for both 2 (approval required) and 3 (confirm +
- # approval) because the policy could have been changed in the
- # middle of the confirmation dance.
- if invitation:
- if invitation <> self.internal_name():
- # Not cool. The invitee was trying to subscribe to a
- # different list than they were invited to. Alert both
- # list administrators.
- self.SendHostileSubscriptionNotice(invitation, addr)
- raise Errors.HostileSubscriptionError
- elif self.subscribe_policy in (2, 3) and \
- not self.HasAutoApprovedSender(addr):
- self.HoldSubscription(addr, fullname, password, digest, lang)
- name = self.real_name
- raise Errors.MMNeedApproval, _(
- 'subscriptions to %(name)s require administrator approval')
- self.ApprovedAddMember(userdesc, whence=whence)
- return op, addr, password, digest, lang
- elif op == Pending.UNSUBSCRIPTION:
- addr = data[0]
- # Log file messages don't need to be i18n'd
- if isinstance(context, Message.Message):
- whence = 'email confirmation'
- else:
- whence = 'web confirmation'
- # Can raise NotAMemberError if they unsub'd via other means
- self.ApprovedDeleteMember(addr, whence=whence)
- return op, addr
- elif op == Pending.CHANGE_OF_ADDRESS:
- oldaddr, newaddr, globally = data
- self.ApprovedChangeMemberAddress(oldaddr, newaddr, globally)
- return op, oldaddr, newaddr
- elif op == Pending.HELD_MESSAGE:
- id = data[0]
- approved = None
- # Confirmation should be coming from email, where context should
- # be the confirming message. If the message does not have an
- # Approved: header, this is a discard. If it has an Approved:
- # header that does not match the list password, then we'll notify
- # the list administrator that they used the wrong password.
- # Otherwise it's an approval.
- if isinstance(context, Message.Message):
- # See if it's got an Approved: header, either in the headers,
- # or in the first text/plain section of the response. For
- # robustness, we'll accept Approve: as well.
- approved = context.get('Approved', context.get('Approve'))
- if not approved:
- try:
- subpart = list(email.Iterators.typed_subpart_iterator(
- context, 'text', 'plain'))[0]
- except IndexError:
- subpart = None
- if subpart:
- s = StringIO(subpart.get_payload())
- while True:
- line = s.readline()
- if not line:
- break
- if not line.strip():
- continue
- i = line.find(':')
- if i > 0:
- if (line[:i].lower() == 'approve' or
- line[:i].lower() == 'approved'):
- # then
- approved = line[i+1:].strip()
- break
- # Is there an approved header?
- if approved is not None:
- # Does it match the list password? Note that we purposefully
- # do not allow the site password here.
- if self.Authenticate([config.AuthListAdmin,
- config.AuthListModerator],
- approved) <> config.UnAuthorized:
- action = config.APPROVE
- else:
- # The password didn't match. Re-pend the message and
- # inform the list moderators about the problem.
- self.pend_repend(cookie, rec)
- raise Errors.MMBadPasswordError
- else:
- action = config.DISCARD
- try:
- self.HandleRequest(id, action)
- except KeyError:
- # Most likely because the message has already been disposed of
- # via the admindb page.
- elog.error('Could not process HELD_MESSAGE: %s', id)
- return (op,)
- elif op == Pending.RE_ENABLE:
- member = data[1]
- self.setDeliveryStatus(member, MemberAdaptor.ENABLED)
- return op, member
- else:
- assert 0, 'Bad op: %s' % op
-
- def ConfirmUnsubscription(self, addr, lang=None, remote=None):
- if lang is None:
- lang = self.getMemberLanguage(addr)
- cookie = self.pend_new(Pending.UNSUBSCRIPTION, addr)
- confirmurl = '%s/%s' % (self.GetScriptURL('confirm', absolute=1),
- cookie)
- realname = self.real_name
- if remote is not None:
- by = " " + remote
- remote = _(" from %(remote)s")
- else:
- by = ""
- remote = ""
- text = Utils.maketext(
- 'unsub.txt',
- {'email' : addr,
- 'listaddr' : self.GetListEmail(),
- 'listname' : realname,
- 'cookie' : cookie,
- 'requestaddr' : self.getListAddress('request'),
- 'remote' : remote,
- 'listadmin' : self.GetOwnerEmail(),
- 'confirmurl' : confirmurl,
- }, lang=lang, mlist=self)
- msg = Message.UserNotification(
- addr, self.GetRequestEmail(cookie),
- text=text, lang=lang)
- # BAW: See ChangeMemberAddress() for why we do it this way...
- del msg['subject']
- msg['Subject'] = self.GetConfirmLeaveSubject(realname, cookie)
- msg['Reply-To'] = self.GetRequestEmail(cookie)
- msg.send(self)
-
-
- #
- # Miscellaneous stuff
- #
-
- def HasAutoApprovedSender(self, sender):
- """Returns True and logs if sender matches address or pattern
- in subscribe_auto_approval. Otherwise returns False.
- """
- auto_approve = False
- if Utils.get_pattern(sender, self.subscribe_auto_approval):
- auto_approve = True
- vlog.info('%s: auto approved subscribe from %s',
- self.internal_name(), sender)
- return auto_approve
-
-
- #
- # Multilingual (i18n) support
- #
- def set_languages(self, *language_codes):
- # XXX FIXME not to use a database entity directly.
- from Mailman.database.model import Language
- # Don't use the language_codes property because that will add the
- # default server language. The effect would be that the default
- # server language would never get added to the list's list of
- # languages.
- requested_codes = set(language_codes)
- enabled_codes = set(config.languages.enabled_codes)
- self.available_languages = [
- Language(code) for code in requested_codes & enabled_codes]
-
- def add_language(self, language_code):
- self.available_languages.append(Language(language_code))
-
- @property
- def language_codes(self):
- # Callers of this method expect a list of language codes
- available_codes = set(self.available_languages)
- enabled_codes = set(config.languages.enabled_codes)
- codes = available_codes & enabled_codes
- # If we don't add this, and the site admin has never added any
- # language support to the list, then the general admin page may have a
- # blank field where the list owner is supposed to chose the list's
- # preferred language.
- if config.DEFAULT_SERVER_LANGUAGE not in codes:
- codes.add(config.DEFAULT_SERVER_LANGUAGE)
- return list(codes)