# HG changeset patch # User Pascal Volk # Date 1270369006 0 # Node ID 084331dd1e4c6ed1c4589a7f2d6147045483245f # Parent 9d3405ed08e56688d8bc9f58d54b026b9b44af24 VMM/Domain: reworked Domain class. Adjusted classes Account, AliasDomain and Handler to changes in the Domain class. diff -r 9d3405ed08e5 -r 084331dd1e4c VirtualMailManager/Account.py --- a/VirtualMailManager/Account.py Sat Apr 03 02:14:13 2010 +0000 +++ b/VirtualMailManager/Account.py Sun Apr 04 08:16:46 2010 +0000 @@ -61,12 +61,12 @@ def _setAddr(self): dom = Domain(self._dbh, self._addr.domainname) - self._gid = dom.getID() + self._gid = dom.gid if self._gid == 0: raise AccE(_(u"The domain “%s” doesn't exist.") % self._addr.domainname, ERR.NO_SUCH_DOMAIN) - self._base = dom.getDir() - self._tid = dom.getTransportID() + self._base = dom.directory + self._tid = dom.transport.tid def _setID(self): dbc = self._dbh.cursor() diff -r 9d3405ed08e5 -r 084331dd1e4c VirtualMailManager/AliasDomain.py --- a/VirtualMailManager/AliasDomain.py Sat Apr 03 02:14:13 2010 +0000 +++ b/VirtualMailManager/AliasDomain.py Sun Apr 04 08:16:46 2010 +0000 @@ -37,12 +37,12 @@ if self._domain is None: raise ADE(_(u'No destination domain specified for alias domain.'), ERR.ALIASDOMAIN_NO_DOMDEST) - if self._domain._id < 1: + if self._domain.gid < 1: raise ADE (_(u"The target domain “%s” doesn't exist.") % - self._domain._name, ERR.NO_SUCH_DOMAIN) + self._domain.name, ERR.NO_SUCH_DOMAIN) dbc = self._dbh.cursor() dbc.execute('INSERT INTO domain_name (domainname, gid, is_primary)\ - VALUES (%s, %s, FALSE)', self.__name, self._domain._id) + VALUES (%s, %s, FALSE)', self.__name, self._domain.gid) self._dbh.commit() dbc.close() @@ -67,21 +67,21 @@ if self._domain is None: raise ADE(_(u'No destination domain specified for alias domain.'), ERR.ALIASDOMAIN_NO_DOMDEST) - if self._domain._id < 1: + if self._domain.gid < 1: raise ADE (_(u"The target domain “%s” doesn't exist.") % - self._domain._name, ERR.NO_SUCH_DOMAIN) + self._domain.name, ERR.NO_SUCH_DOMAIN) if self.__gid < 1: raise ADE(_(u"The alias domain “%s” doesn't exist.") % self.__name, ERR.NO_SUCH_ALIASDOMAIN) - if self.__gid == self._domain._id: + if self.__gid == self._domain.gid: raise ADE(_(u"The alias domain “%(alias)s” is already assigned to\ the domain “%(domain)s”.") % - {'alias': self.__name, 'domain': self._domain._name}, + {'alias': self.__name, 'domain': self._domain.name}, ERR.ALIASDOMAIN_EXISTS) dbc = self._dbh.cursor() dbc.execute('UPDATE domain_name SET gid = %s WHERE gid = %s\ AND domainname = %s AND NOT is_primary', - self._domain._id, self.__gid, self.__name) + self._domain.gid, self.__gid, self.__name) self._dbh.commit() dbc.close() diff -r 9d3405ed08e5 -r 084331dd1e4c VirtualMailManager/Domain.py --- a/VirtualMailManager/Domain.py Sat Apr 03 02:14:13 2010 +0000 +++ b/VirtualMailManager/Domain.py Sun Apr 04 08:16:46 2010 +0000 @@ -2,8 +2,13 @@ # Copyright (c) 2007 - 2010, Pascal Volk # See COPYING for distribution information. -"""Virtual Mail Manager's Domain class to manage e-mail domains.""" +""" + VirtualMailManager.Domain + Virtual Mail Manager's Domain class to manage e-mail domains. +""" + +import os from random import choice from VirtualMailManager import check_domainname @@ -20,219 +25,246 @@ class Domain(object): """Class to manage e-mail domains.""" - __slots__ = ('_basedir', '_domaindir', '_id', '_name', '_transport', - '_dbh') + __slots__ = ('_directory', '_gid', '_name', '_transport', '_dbh', '_new') - def __init__(self, dbh, domainname, basedir=None, transport=None): + def __init__(self, dbh, domainname): """Creates a new Domain instance. - Keyword arguments: - dbh -- a pyPgSQL.PgSQL.connection - domainname -- name of the domain (str) - transport -- default vmm.cfg/misc/transport (str) + Loads all relevant data from the database, if the domain could be + found. To create a new domain call the methods set_directory() and + set_transport() before save(). + + A DomainError will be thrown when the *domainname* is the name of + an alias domain. + + Arguments: + + `dbh` : pyPgSQL.PgSQL.Connection + a database connection for the database access + `domainname` : basestring + The name of the domain """ - self._dbh = dbh self._name = check_domainname(domainname) - self._basedir = basedir - if transport is not None: - self._transport = Transport(self._dbh, transport=transport) - else: - self._transport = transport - self._id = 0 - self._domaindir = None - if not self._exists() and self._isAlias(): - raise DomErr(_(u"The domain “%s” is an alias domain.") % - self._name, DOMAIN_ALIAS_EXISTS) + self._dbh = dbh + self._gid = 0 + self._transport = None + self._directory = None + self._new = True + self._load() - def _exists(self): - """Checks if the domain already exists. + def _load(self): + """Load information from the database and checks if the domain name + is the primary one. - If the domain exists _id will be set and returns True, otherwise False - will be returned. + Raises a DomainError if Domain._name isn't the primary name of the + domain. """ dbc = self._dbh.cursor() - dbc.execute("SELECT gid, tid, domaindir FROM domain_data WHERE gid =\ - (SELECT gid FROM domain_name WHERE domainname = %s AND is_primary)", - self._name) + dbc.execute('SELECT dd.gid, tid, domaindir, is_primary FROM \ + domain_data dd, domain_name dn WHERE domainname = %s AND dn.gid = dd.gid', + self._name) result = dbc.fetchone() dbc.close() - if result is not None: - self._id, self._domaindir = result[0], result[2] + if result: + if not result[3]: + raise DomErr(_(u"The domain '%s' is an alias domain.") % + self._name, DOMAIN_ALIAS_EXISTS) + self._gid, self._directory = result[0], result[2] self._transport = Transport(self._dbh, tid=result[1]) - return True - else: - return False + self._new = False - def _isAlias(self): - """Checks if self._name is known for an alias domain.""" - dbc = self._dbh.cursor() - dbc.execute('SELECT is_primary FROM domain_name WHERE domainname = %s', - self._name) - result = dbc.fetchone() - dbc.close() - if result is not None and not result[0]: - return True - else: - return False - - def _setID(self): - """Sets the ID of the domain.""" + def _set_gid(self): + """Sets the ID of the domain - if not set yet.""" + assert self._gid == 0 dbc = self._dbh.cursor() dbc.execute("SELECT nextval('domain_gid')") - self._id = dbc.fetchone()[0] + self._gid = dbc.fetchone()[0] dbc.close() - def _prepare(self): - self._setID() - self._domaindir = "%s/%s/%i" % (self._basedir, choice(MAILDIR_CHARS), - self._id) - def _has(self, what): """Checks if aliases or accounts are assigned to the domain. If there are assigned accounts or aliases True will be returned, otherwise False will be returned. - Keyword arguments: - what -- 'alias' or 'users' (strings) + Argument: + + `what` : basestring + "alias" or "users" """ - if what not in ['alias', 'users']: - return False + assert what in ('alias', 'users') dbc = self._dbh.cursor() if what == 'users': - dbc.execute("SELECT count(gid) FROM users WHERE gid=%s", self._id) + dbc.execute("SELECT count(gid) FROM users WHERE gid=%s", self._gid) else: - dbc.execute("SELECT count(gid) FROM alias WHERE gid=%s", self._id) + dbc.execute("SELECT count(gid) FROM alias WHERE gid=%s", self._gid) count = dbc.fetchone() dbc.close() - if count[0] > 0: - return True - else: - return False + return count[0] > 0 - def _chkDelete(self, delUser, delAlias): + def _chk_delete(self, deluser, delalias): """Checks dependencies for deletion. - Keyword arguments: - delUser -- ignore available accounts (bool) - delAlias -- ignore available aliases (bool) + Arguments: + deluser -- ignore available accounts (bool) + delalias -- ignore available aliases (bool) """ - if not delUser: - hasUser = self._has('users') + if not deluser: + hasuser = self._has('users') else: - hasUser = False - if not delAlias: - hasAlias = self._has('alias') + hasuser = False + if not delalias: + hasalias = self._has('alias') else: - hasAlias = False - if hasUser and hasAlias: + hasalias = False + if hasuser and hasalias: raise DomErr(_(u'There are accounts and aliases.'), ACCOUNT_AND_ALIAS_PRESENT) - elif hasUser: + elif hasuser: raise DomErr(_(u'There are accounts.'), ACCOUNT_PRESENT) - elif hasAlias: + elif hasalias: raise DomErr(_(u'There are aliases.'), ALIAS_PRESENT) + def _chk_state(self): + """Throws a DomainError if the Domain is new - not saved in the + database.""" + if self._new: + raise DomErr(_(u"The domain '%s' doesn't exist.") % self._name, + NO_SUCH_DOMAIN) + + @property + def gid(self): + """The GID of the Domain.""" + return self._gid + + @property + def name(self): + """The Domain's name.""" + return self._name + + @property + def directory(self): + """The Domain's directory.""" + return self._directory + + def set_directory(self, basedir): + """Set the path value of the Domain's directory, inside *basedir*. + + Argument: + + `basedir` : basestring + The base directory of all domains + """ + assert self._new and self._directory is None + self._set_gid() + self._directory = os.path.join(basedir, choice(MAILDIR_CHARS), + str(self._gid)) + + @property + def transport(self): + """The Domain's transport.""" + return self._transport + + def set_transport(self, transport): + """Set the transport for the new Domain. + + Argument: + + `transport` : VirtualMailManager.Transport + The transport of the new Domain + """ + assert self._new and isinstance(transport, Transport) + self._transport = transport + def save(self): """Stores the new domain in the database.""" - if self._id < 1: - self._prepare() - dbc = self._dbh.cursor() - dbc.execute("INSERT INTO domain_data (gid, tid, domaindir)\ - VALUES (%s, %s, %s)", self._id, self._transport.id, self._domaindir) - dbc.execute("INSERT INTO domain_name (domainname, gid, is_primary)\ - VALUES (%s, %s, %s)", self._name, self._id, True) - self._dbh.commit() - dbc.close() - else: - raise DomErr(_(u'The domain “%s” already exists.') % self._name, + if not self._new: + raise DomErr(_(u"The domain '%s' already exists.") % self._name, DOMAIN_EXISTS) + assert self._directory is not None and self._transport is not None + dbc = self._dbh.cursor() + dbc.execute("INSERT INTO domain_data VALUES (%s, %s, %s)", self._gid, + self._transport.tid, self._directory) + dbc.execute("INSERT INTO domain_name VALUES (%s, %s, %s)", self._name, + self._gid, True) + self._dbh.commit() + dbc.close() + self._new = False - def delete(self, delUser=False, delAlias=False): + def delete(self, deluser=False, delalias=False): """Deletes the domain. - Keyword arguments: - delUser -- force deletion of available accounts (bool) - delAlias -- force deletion of available aliases (bool) + Arguments: + + `deluser` : bool + force deletion of all available accounts, default `False` + `delalias` : bool + force deletion of all available aliases, default `False` """ - if self._id > 0: - self._chkDelete(delUser, delAlias) - dbc = self._dbh.cursor() - for tbl in ('alias', 'users', 'relocated', 'domain_name', - 'domain_data'): - dbc.execute("DELETE FROM %s WHERE gid = %d" % (tbl, self._id)) + self._chk_state() + self._chk_delete(deluser, delalias) + dbc = self._dbh.cursor() + for tbl in ('alias', 'users', 'relocated', 'domain_name', + 'domain_data'): + dbc.execute("DELETE FROM %s WHERE gid = %d" % (tbl, self._gid)) + self._dbh.commit() + dbc.close() + self._gid = 0 + self._directory = self._transport = None + self._new = True + + def update_transport(self, transport, force=False): + """Sets a new transport for the Domain. + + If *force* is `True` the new *transport* will be assigned to all + existing accounts. Otherwise the *transport* will be only used for + accounts created from now on. + + Arguments: + + `transport` : VirtualMailManager.Transport + the new transport + `force` : bool + enforce new transport setting for all accounts, default `False` + """ + self._chk_state() + assert isinstance(transport, Transport) + if transport == self._transport: + return + dbc = self._dbh.cursor() + dbc.execute("UPDATE domain_data SET tid = %s WHERE gid = %s", + transport.tid, self._gid) + if dbc.rowcount > 0: self._dbh.commit() - dbc.close() - else: - raise DomErr(_(u"The domain “%s” doesn't exist.") % self._name, - NO_SUCH_DOMAIN) - - def updateTransport(self, transport, force=False): - """Sets a new transport for the domain. - - Keyword arguments: - transport -- the new transport (str) - force -- True/False force new transport for all accounts (bool) - """ - if self._id > 0: - if transport == self._transport.transport: - return - trsp = Transport(self._dbh, transport=transport) - dbc = self._dbh.cursor() - dbc.execute("UPDATE domain_data SET tid = %s WHERE gid = %s", - trsp.id, self._id) + if force: + dbc.execute("UPDATE users SET tid = %s WHERE gid = %s", + transport.tid, self._gid) if dbc.rowcount > 0: self._dbh.commit() - if force: - dbc.execute("UPDATE users SET tid = %s WHERE gid = %s", - trsp.id, self._id) - if dbc.rowcount > 0: - self._dbh.commit() - dbc.close() - else: - raise DomErr(_(u"The domain “%s” doesn't exist.") % self._name, - NO_SUCH_DOMAIN) - - def getID(self): - """Returns the ID of the domain.""" - return self._id + dbc.close() + self._transport = transport - def getDir(self): - """Returns the directory of the domain.""" - return self._domaindir - - def getTransport(self): - """Returns domain's transport.""" - return self._transport.transport - - def getTransportID(self): - """Returns the ID from the domain's transport.""" - return self._transport.id - - def getInfo(self): + def get_info(self): """Returns a dictionary with information about the domain.""" - sql = """\ -SELECT gid, domainname, transport, domaindir, aliasdomains, accounts, - aliases, relocated + self._chk_state() + sql = """SELECT gid, domainname, transport, domaindir, aliasdomains, + accounts, aliases, relocated FROM vmm_domain_info - WHERE gid = %i""" % self._id + WHERE gid = %i""" % self._gid dbc = self._dbh.cursor() dbc.execute(sql) info = dbc.fetchone() dbc.close() - if info is None: - raise DomErr(_(u"The domain “%s” doesn't exist.") % self._name, - NO_SUCH_DOMAIN) - else: - keys = ['gid', 'domainname', 'transport', 'domaindir', - 'aliasdomains', 'accounts', 'aliases', 'relocated'] - return dict(zip(keys, info)) + keys = ('gid', 'domainname', 'transport', 'domaindir', 'aliasdomains', + 'accounts', 'aliases', 'relocated') + return dict(zip(keys, info)) - def getAccounts(self): - """Returns a list with all accounts from the domain.""" + def get_accounts(self): + """Returns a list with all accounts of the domain.""" + self._chk_state() dbc = self._dbh.cursor() dbc.execute("SELECT local_part from users where gid = %s ORDER BY\ - local_part", self._id) + local_part", self._gid) users = dbc.fetchall() dbc.close() accounts = [] @@ -242,48 +274,72 @@ accounts = [addr((account[0], _dom)) for account in users] return accounts - def getAliases(self): - """Returns a list with all aliases from the domain.""" + def get_aliases(self): + """Returns a list with all aliases e-mail addresses of the domain.""" + self._chk_state() dbc = self._dbh.cursor() dbc.execute("SELECT DISTINCT address FROM alias WHERE gid = %s\ - ORDER BY address", self._id) + ORDER BY address", self._gid) addresses = dbc.fetchall() dbc.close() aliases = [] - if len(addresses) > 0: + if addresses: addr = u'@'.join _dom = self._name aliases = [addr((alias[0], _dom)) for alias in addresses] return aliases - def getRelocated(self): - """Returns a list with all addresses from relocated users.""" + def get_relocated(self): + """Returns a list with all addresses of relocated users.""" + self._chk_state() dbc = self._dbh.cursor() dbc.execute("SELECT address FROM relocated WHERE gid = %s\ - ORDER BY address", self._id) + ORDER BY address", self._gid) addresses = dbc.fetchall() dbc.close() relocated = [] - if len(addresses) > 0: + if addresses: addr = u'@'.join _dom = self._name relocated = [addr((address[0], _dom)) for address in addresses] return relocated - def getAliaseNames(self): - """Returns a list with all alias names from the domain.""" + def get_aliase_names(self): + """Returns a list with all alias domain names of the domain.""" + self._chk_state() dbc = self._dbh.cursor() dbc.execute("SELECT domainname FROM domain_name WHERE gid = %s\ - AND NOT is_primary ORDER BY domainname", self._id) + AND NOT is_primary ORDER BY domainname", self._gid) anames = dbc.fetchall() dbc.close() aliasdomains = [] - if len(anames) > 0: + if anames: aliasdomains = [aname[0] for aname in anames] return aliasdomains def search(dbh, pattern=None, like=False): + """'Search' for domains by *pattern* in the database. + + *pattern* may be a domain name or a partial domain name - starting + and/or ending with a '%' sign. When the *pattern* starts or ends with + a '%' sign *like* has to be `True` to perform a wildcard search. + To retrieve all available domains use the arguments' default values. + + This function returns a tuple with a list and a dict: (order, domains). + The order list contains the domains' gid, alphabetical sorted by the + primary domain name. The domains dict's keys are the gids of the + domains. The value of item is a list. The first list element contains + the primary domain name or `None`. The elements [1:] contains the + names of alias domains. + + Arguments: + + `pattern` : basestring + a (partial) domain name (starting and/or ending with a "%" sign) + `like` : bool + should be `True` when *pattern* starts/ends with a "%" sign + """ if pattern is not None and like is False: pattern = check_domainname(pattern) sql = 'SELECT gid, domainname, is_primary FROM domain_name' @@ -301,7 +357,7 @@ domdict = {} order = [dom[0] for dom in doms if dom[2]] - if len(order) == 0: + if not order: for dom in doms: if dom[0] not in order: order.append(dom[0]) @@ -321,7 +377,6 @@ """Returns the group id of the domain *domainname*. If the domain couldn't be found in the database 0 will be returned. - """ domainname = check_domainname(domainname) dbc = dbh.cursor() diff -r 9d3405ed08e5 -r 084331dd1e4c VirtualMailManager/Handler.py --- a/VirtualMailManager/Handler.py Sat Apr 03 02:14:13 2010 +0000 +++ b/VirtualMailManager/Handler.py Sun Apr 04 08:16:46 2010 +0000 @@ -30,6 +30,7 @@ from VirtualMailManager.EmailAddress import EmailAddress from VirtualMailManager.errors import VMMError, AliasError, DomainError from VirtualMailManager.Relocated import Relocated +from VirtualMailManager.Transport import Transport from VirtualMailManager.ext.Postconf import Postconf @@ -191,12 +192,9 @@ self.__dbConnect() return Relocated(self._dbh, address) - def __getDomain(self, domainname, transport=None): - if transport is None: - transport = self._Cfg.dget('misc.transport') + def __getDomain(self, domainname): self.__dbConnect() - return Domain(self._dbh, domainname, - self._Cfg.dget('misc.base_directory'), transport) + return Domain(self._dbh, domainname) def __getDiskUsage(self, directory): """Estimate file space usage for the given directory. @@ -227,6 +225,7 @@ os.chown(directory, uid, gid) def __domDirMake(self, domdir, gid): + #TODO: clenaup! os.umask(0006) oldpwd = os.getcwd() basedir = self._Cfg.dget('misc.base_directory') @@ -411,19 +410,26 @@ return self._Cfg.pget(option) def domainAdd(self, domainname, transport=None): - dom = self.__getDomain(domainname, transport) + dom = self.__getDomain(domainname) + if transport is None: + dom.set_transport(Transport(self._dbh, + transport=self._Cfg.dget('misc.transport'))) + else: + dom.set_transport(Transport(self._dbh, transport=transport)) + dom.set_directory(self._Cfg.dget('misc.base_directory')) dom.save() - self.__domDirMake(dom.getDir(), dom.getID()) + self.__domDirMake(dom.directory, dom.gid) def domainTransport(self, domainname, transport, force=None): if force is not None and force != 'force': raise DomainError(_(u"Invalid argument: “%s”") % force, ERR.INVALID_OPTION) - dom = self.__getDomain(domainname, None) + dom = self.__getDomain(domainname) + trsp = Transport(self._dbh, transport=transport) if force is None: - dom.updateTransport(transport) + dom.update_transport(trsp) else: - dom.updateTransport(transport, force=True) + dom.update_transport(trsp, force=True) def domainDelete(self, domainname, force=None): if not force is None and force not in ['deluser', 'delalias', @@ -431,14 +437,14 @@ raise DomainError(_(u'Invalid argument: “%s”') % force, ERR.INVALID_OPTION) dom = self.__getDomain(domainname) - gid = dom.getID() - domdir = dom.getDir() + gid = dom.gid + domdir = dom.directory if self._Cfg.dget('domain.force_deletion') or force == 'delall': dom.delete(True, True) elif force == 'deluser': - dom.delete(delUser=True) + dom.delete(deluser=True) elif force == 'delalias': - dom.delete(delAlias=True) + dom.delete(delalias=True) else: dom.delete() if self._Cfg.dget('domain.delete_directory'): @@ -450,22 +456,22 @@ raise VMMError(_(u'Invalid argument: “%s”') % details, ERR.INVALID_AGUMENT) dom = self.__getDomain(domainname) - dominfo = dom.getInfo() + dominfo = dom.get_info() if dominfo['domainname'].startswith('xn--'): dominfo['domainname'] += ' (%s)' % ace2idna(dominfo['domainname']) if details is None: return dominfo elif details == 'accounts': - return (dominfo, dom.getAccounts()) + return (dominfo, dom.get_accounts()) elif details == 'aliasdomains': - return (dominfo, dom.getAliaseNames()) + return (dominfo, dom.get_aliase_names()) elif details == 'aliases': - return (dominfo, dom.getAliases()) + return (dominfo, dom.get_aliases()) elif details == 'relocated': - return(dominfo, dom.getRelocated()) + return(dominfo, dom.get_relocated()) else: - return (dominfo, dom.getAliaseNames(), dom.getAccounts(), - dom.getAliases(), dom.getRelocated()) + return (dominfo, dom.get_aliase_names(), dom.get_accounts(), + dom.get_aliases(), dom.get_relocated()) def aliasDomainAdd(self, aliasname, domainname): """Adds an alias domain to the domain.