1
0
Fork 0

add PasteDeploy dependency, remove pudge dependency

* upgrade migrate repository structure (fixes #32, #27)
 * switch to PasteDeploy (fixes #31)
 * update for SQLAlchemy 0.5 compatibility
 * add python-gnutls dependency (addresses #35)
This commit is contained in:
Jan Dittberner 2009-07-19 01:03:23 +02:00
parent 483c1f9038
commit 222b35b033
24 changed files with 247 additions and 177 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
*.pyc
*.egg/
*.log
gva.db

50
bin/gva
View file

@ -21,42 +21,20 @@
#
# Version: $Id$
import gnuviechadmin.cli.client
import gnuviechadmin.cli.sysuser
import gnuviechadmin.cli.domain
import gnuviechadmin.cli.record
import sys, os, logging.config
from paste.deploy import appconfig
from sys import argv
from os import getcwd
from os.path import isfile
from logging.config import fileConfig
from gnuviechadmin.cli import CommandLineInterface
logcfgs = ('gnuviechadmin/logging.cfg', '/etc/gnuviechadmin/logging.cfg',
os.path.expanduser('~/.gva-logging.cfg'))
for cfg in [x for x in logcfgs if os.path.exists(x)]:
logging.config.fileConfig(cfg)
if len(argv) > 1 and isfile(argv[1]):
configfile = argv[1]
del argv[1]
else:
configfile = 'development.ini'
commands = [gnuviechadmin.cli.client.ClientCli,
gnuviechadmin.cli.sysuser.SysuserCli,
gnuviechadmin.cli.domain.DomainCli,
gnuviechadmin.cli.record.RecordCli]
config = appconfig('config:%s' % configfile, relative_to=getcwd())
fileConfig(configfile, config)
def usage():
print """%s <command> [commandargs]
where command is one of
""" % sys.argv[0]
for command in commands:
print "%10s - %s" % (command.name, command.description)
def main():
if (sys.argv.__len__() < 2):
usage()
sys.exit()
command = sys.argv[1]
commargs = sys.argv[2:]
if command in [cmd.name for cmd in commands]:
for cmd in commands:
if cmd.name == command:
cmd(commargs)
else:
usage()
if __name__ == '__main__':
main()
CommandLineInterface(config, argv).run()

62
development.ini Normal file
View file

@ -0,0 +1,62 @@
[DEFAULT]
mailfrom = gva@gnuviech.info
mailto = jan@dittberner.info
suwrapper = sudo
backupdir = /var/backups/gnuviechadmin
[app:main]
use = egg:gnuviechadmin#cli
# The database connection string in a format usable for
# sqlalchemy. The default is an sqlite in memory database which is not
# very usable for a real installation.
#
sqlalchemy.uri = sqlite:///%(here)s/gva.db
sqlalchemy.echo = false
database.repository = %(here)s/data/dbrepo
migrate.required_version = 3
templatedir = %(here)s/data/templates
mailtemplates = %(templatedir)s/mails
client.defaultcountry = de
client.create.mail = create_client.txt
client.create_subject = A new client ${firstname} ${lastname} has been created.
# Logging configuration
[loggers]
keys = root, gnuviechadmin, sqlalchemy
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = INFO
handlers = console
[logger_gnuviechadmin]
level = DEBUG
handlers =
qualname = gnuviechadmin
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
# "level = INFO" logs SQL queries.
# "level = DEBUG" logs SQL queries and results.
# "level = WARN" logs neither. (Recommended for production systems.)
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(asctime)s,%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View file

@ -1,6 +1,6 @@
Metadata-Version: 1.0
Name: gnuviechadmin
Version: 0.1.dev-20090718
Version: 0.1.dev-20090719
Summary: gnuviechadmin server administration suite
Home-page: http://www.gnuviech-server.de/projects/gnuviechadmin
Author: Jan Dittberner

View file

@ -7,6 +7,7 @@ gnuviechadmin/exceptions.py
gnuviechadmin.egg-info/PKG-INFO
gnuviechadmin.egg-info/SOURCES.txt
gnuviechadmin.egg-info/dependency_links.txt
gnuviechadmin.egg-info/entry_points.txt
gnuviechadmin.egg-info/requires.txt
gnuviechadmin.egg-info/top_level.txt
gnuviechadmin/backend/BackendEntity.py

View file

@ -0,0 +1,3 @@
[paste.app_factory]
cli = gnuviechadmin.cli.client

View file

@ -1,3 +1,5 @@
SQLAlchemy>=0.5
sqlalchemy-migrate>=0.5
AuthKit>=0.4
AuthKit>=0.4
PasteDeploy>=1.3.3
python-gnutls>=1.1

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# 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
@ -25,7 +25,6 @@ import logging
import tempfile
from gnuviechadmin.exceptions import MissingFieldsError
from gnuviechadmin.backend.settings import config
from gnuviechadmin.util import gpgmail
from subprocess import Popen, PIPE
from sqlalchemy.orm import object_mapper
@ -34,9 +33,10 @@ from sqlalchemy.orm import object_mapper
class BackendEntity(object):
"""This is the abstract base class for all backend entity classes."""
def __init__(self, delegateto, verbose = False):
def __init__(self, config, delegateto, verbose = False):
self.logger = logging.getLogger("%s.%s" % (
self.__class__.__module__, self.__class__.__name__))
self.config = config
self.delegateto = delegateto
self.verbose = verbose
@ -49,7 +49,7 @@ class BackendEntity(object):
subprocess."""
self.logger.debug("sucommand called: %s (pipedata=%s)", cmdline,
str(pipedata))
suwrapper = config.get('common', 'suwrapper')
suwrapper = self.config['suwrapper']
toexec = "%s %s" % (suwrapper, cmdline)
if pipedata:
pipeproc = Popen(toexec, shell = True, stdin=PIPE)
@ -72,7 +72,7 @@ class BackendEntity(object):
"""Executes multiple commands as root and pipes the output of
the commands to the input of the next commands."""
self.logger.debug("supipe called: %s", " | ".join(cmdlines))
suwrapper = config.get('common', 'suwrapper')
suwrapper = self.config['suwrapper']
predecessor = None
for cmdline in cmdlines:
toexec = "%s %s" % (suwrapper, cmdline)
@ -89,7 +89,7 @@ class BackendEntity(object):
"""This method sends a mail with the given text and subject
and signs it usign GnuPG. If a public key of the recipient is
available the mail is encrypted."""
gpgmail.send_mail(subject, text)
gpgmail.send_mail(self.config, subject, text)
def validate(self):
"""Validates whether all mandatory fields of the entity have

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# 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
@ -21,38 +21,41 @@
"""This module defines the BackendEntityHandler class."""
import logging
from sqlalchemy.orm import create_session
from sqlalchemy.orm import create_session, mapper, relation
from gnuviechadmin.backend.tables import dbsetup
class BackendEntityHandler(object):
"""This class is a handler for BackendEntity instances."""
def __init__(self, entityclass, toclass, verbose = False):
def __init__(self, entityclass, toclass, config, verbose = False):
"""Initialize the handler with a specific entity class,
transfer object class and verbosity flag."""
self.logger = logging.getLogger("%s.%s" % (
self.__class__.__module__, self.__class__.__name__))
dbsetup(config)
self.entityclass = entityclass
self.toclass = toclass
self.config = config
self.verbose = verbose
def create(self, **kwargs):
"""Create a new entity of the managed type with the fields set
to the values in kwargs."""
self.logger.debug("create with params %s", str(kwargs))
delegate = self.toclass(self.config, **kwargs)
entity = self.entityclass(self.config, delegate, self.verbose)
sess = create_session()
transaction = sess.create_transaction()
delegate = self.toclass(**kwargs)
entity = self.entityclass(delegate, self.verbose)
try:
sess.save(delegate)
sess.begin()
sess.add(delegate)
sess.flush()
sess.refresh(delegate)
entity.create_hook(sess)
sess.flush()
transaction.commit()
sess.commit()
except:
transaction.rollback()
sess.rollback()
self.logger.exception("Exception in create.")
raise
@ -65,7 +68,7 @@ class BackendEntityHandler(object):
allentities = query.filter_by(**kwargs).all()
else:
allentities = query.all()
return [self.entityclass(entity, self.verbose) \
return [self.entityclass(self.config, entity, self.verbose) \
for entity in allentities]
def delete(self, pkvalue):
@ -73,19 +76,18 @@ class BackendEntityHandler(object):
specified primary key value."""
self.logger.debug("delete with primary key %s", str(pkvalue))
sess = create_session()
transaction = sess.create_transaction()
try:
sess.begin()
tobj = sess.query(self.toclass).get(pkvalue)
if tobj:
entity = self.entityclass(tobj, self.verbose)
entity = self.entityclass(self.config, tobj, self.verbose)
self.logger.info("delete %s", str(entity))
if self.verbose:
print "delete %s" % (str(entity))
entity.delete_hook(sess)
sess.delete(tobj)
sess.flush()
transaction.commit()
sess.commit()
except Exception:
transaction.rollback()
sess.rollback()
self.logger.exception("Exception in delete.")
raise

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# 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
@ -19,16 +19,15 @@
#
# Version: $Id$
from sqlalchemy.orm import object_mapper, mapper, relation
from tables import *
from sqlalchemy.orm import object_mapper
class BackendTo(object):
"""Backend transfer object class."""
def __init__(self, **kwargs):
def __init__(self, config, **kwargs):
for (key, value) in kwargs.items():
self.__setattr__(key, value)
self.__setattr__(key, unicode(value, 'utf8'))
def __repr__(self, **kwargs):
if 'verbose' in kwargs and kwargs['verbose']:
@ -64,12 +63,3 @@ class Domain(BackendTo):
class Record(BackendTo):
"""Transfer object class for DNS domain records."""
_shortkeys = ("recordid", "domainid", "name", "type", "content")
client_mapper = mapper(Client, client_table, {
'sysusers': relation(Sysuser, backref = 'client')})
sysuser_mapper = mapper(Sysuser, sysuser_table, {
'domains': relation(Domain, backref = 'sysuser')})
domain_mapper = mapper(Domain, domain_table, {
'records': relation(Record, cascade = 'all', backref = 'domain')})
record_mapper = mapper(Record, record_table)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# 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
@ -20,8 +20,7 @@
# Version: $Id$
"""This module defines the ClientEntity class."""
from gnuviechadmin.backend.settings import config, get_template, \
get_template_string
from gnuviechadmin.backend.settings import get_template, get_template_string
from gnuviechadmin.exceptions import CannotDeleteError
from gnuviechadmin.backend.BackendTo import Client
from gnuviechadmin.backend.BackendEntity import BackendEntity
@ -31,9 +30,9 @@ from gnuviechadmin.backend.BackendEntityHandler import BackendEntityHandler
class ClientEntity(BackendEntity):
"""Entity class for clients."""
def __init__(self, delegate, verbose = False, **kwargs):
def __init__(self, config, delegate, verbose = False, **kwargs):
"""Initializes the client entity instance."""
BackendEntity.__init__(self, delegate, verbose)
BackendEntity.__init__(self, config, delegate, verbose)
for (key, value) in kwargs.items():
self.__setattr__(key, value)
if not self.delegateto.country:
@ -42,8 +41,8 @@ class ClientEntity(BackendEntity):
def _client_mail(self):
"""Mails a summary about the creation of the client."""
text = get_template(config.get('common', 'mailtemplates'),
config.get('client', 'create.mail')).substitute({
text = get_template(self.config['mailtemplates'],
self.config['client.create.mail']).substitute({
'firstname': self.delegateto.firstname,
'lastname': self.delegateto.lastname,
'email': self.delegateto.email,
@ -52,11 +51,11 @@ class ClientEntity(BackendEntity):
'city': self.delegateto.city,
'phone': self.delegateto.phone})
subject = get_template_string(
config.get('client', 'create_subject')).substitute({
self.config['client.create_subject']).substitute({
'firstname': self.delegateto.firstname,
'lastname': self.delegateto.lastname})
self.send_mail(subject, text)
def create_hook(self, session):
"""Actions to perform when a client is created."""
self._client_mail()
@ -72,11 +71,12 @@ class ClientEntity(BackendEntity):
def _get_default_country(self):
"""Gets the default country."""
return config.get('common', 'defaultcountry')
return self.config['client.defaultcountry']
class ClientHandler(BackendEntityHandler):
"""BackendEntityHandler for Client entities."""
def __init__(self, verbose = False):
BackendEntityHandler.__init__(self, ClientEntity, Client, verbose)
def __init__(self, config, verbose = False):
BackendEntityHandler.__init__(self, ClientEntity, Client, config,
verbose)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# 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
@ -25,32 +25,13 @@ This module handles all central configuration of Gnuviech Admin. It
parses configuration files and provides functions for reading
templates."""
import ConfigParser
import os
from string import Template
# global settings which must not be user configurable
required_version = 3
# load user configuration
config = ConfigParser.ConfigParser()
config.readfp(open('gnuviechadmin/defaults.cfg'))
config.read(['gnuviechadmin/gva.cfg', os.path.expanduser('~/.gva.cfg')])
dbschema = None
if config.get('database', 'uri').startswith('postgres://'):
dbschema = 'gva'
def get_template_dir(dirname):
"""Returns the template directory for the given directory."""
templatepath = config.get('common', 'templatedir')
return os.path.join(templatepath, dirname)
def get_template(dirname, filename):
def get_template(templatedir, filename):
"""Returns the template data from the given template file."""
templatefile = file(os.path.join(get_template_dir(dirname),
templatefile = file(os.path.join(templatedir,
filename))
templatedata = templatefile.read()
return Template(templatedata.decode('utf_8'))

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# 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
@ -19,57 +19,69 @@
#
# Version: $Id$
from sqlalchemy import *
from sqlalchemy import MetaData, Table
from sqlalchemy.orm import mapper, relation
from sqlalchemy.exceptions import NoSuchTableError
import sys
import migrate.versioning.api
from settings import *
import logging
from gnuviechadmin.backend.BackendTo import Client, Sysuser, Domain, Record
try:
dbversion = migrate.versioning.api.db_version(
config.get('database', 'uri'),
config.get('database', 'repository'))
if dbversion < required_version:
print("""Database version is %d but required version is %d. Trying
automatic upgrade.""" %
(dbversion, required_version))
def dbsetup(config):
logger = logging.getLogger(__name__)
required_version = int(config['migrate.required_version'])
try:
dbversion = migrate.versioning.api.db_version(
config['sqlalchemy.uri'], config['database.repository'])
if dbversion < required_version:
logger.info("""Database version is %d but required version \
is %d. Trying automatic upgrade.""" % (dbversion, required_version))
try:
migrate.versioning.api.upgrade(
config['sqlalchemy.uri'], config['database.repository'],
required_version)
except e:
logger.error("Automatic upgrade failed.", e)
raise
elif dbversion > required_version:
logger.error("""Database version is %d which is higher than \
the required version %d. I cannot handle this situation without possible \
data loss.""" % (dbversion, required_version))
sys.exit(1)
except NoSuchTableError, nste:
logger.info("""The database is not versioned. \
Trying automatic versioning.""")
try:
migrate.versioning.api.version_control(
config['sqlalchemy.uri'], config['database.repository'])
migrate.versioning.api.upgrade(
config.get('database', 'uri'),
config.get('database', 'repository'),
config['sqlalchemy.uri'], config['database.repository'],
required_version)
except:
print "Automatic upgrade failed."
logger.error("Automatic setup failed.")
raise
elif dbversion > required_version:
print("""Database version is %d which is higher than the required
version %d. I cannot handle this situation without possible data loss.""" %
(dbversion, required_version))
sys.exit(1)
except NoSuchTableError, nste:
print """The database is not versioned. Trying automatic versioning."""
try:
migrate.versioning.api.version_control(
config.get('database', 'uri'),
config.get('database', 'repository'))
migrate.versioning.api.upgrade(
config.get('database', 'uri'),
config.get('database', 'repository', required_version))
except:
print "Automatic setup failed."
raise
meta = MetaData(config.get('database', 'uri'))
#meta.engine.echo = True
client_table = Table('client', meta, schema = dbschema, autoload = True)
sysuser_table = Table('sysuser', meta, schema = dbschema, autoload = True)
domain_table = Table('domain', meta, schema = dbschema, autoload = True)
record_table = Table('record', meta, schema = dbschema, autoload = True)
supermaster_table = Table('supermaster', meta, schema = dbschema,
autoload = True)
mailaccount_table = Table('mailaccount', meta, schema = dbschema,
autoload = True)
mailaddress_table = Table('mailaddress', meta, schema = dbschema,
autoload = True)
mailtarget_table = Table('mailtarget', meta, schema = dbschema,
autoload = True)
meta = MetaData(config['sqlalchemy.uri'])
meta.bind.engine.echo = config['sqlalchemy.echo']
dbschema = None
if 'database.schema' in config:
dbschema = config['database.schema']
(client_table, sysuser_table, domain_table, \
record_table, supermaster_table, mailaccount_table, \
mailaddress_table, mailtarget_table) = \
[Table(tabname, meta, schema = dbschema,
autoload = True) for tabname in \
('client', 'sysuser', 'domain', 'record',
'supermaster', 'mailaccount', 'mailaddress', 'mailtarget')]
client_mapper = mapper(Client, client_table, {
'sysusers': relation(Sysuser, backref = 'client')})
sysuser_mapper = mapper(Sysuser, sysuser_table, {
'domains': relation(Domain, backref = 'sysuser')})
domain_mapper = mapper(Domain, domain_table, {
'records': relation(Record, cascade = 'all',
backref = 'domain')})
record_mapper = mapper(Record, record_table)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# 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
@ -197,9 +197,10 @@ Common options:
sys.exit()
self._handleoption(subcommand, o, a)
def __init__(self, args):
def __init__(self, args, config):
"""This initializes the command with the given command line
arguments and executes it."""
self.config = config
self.logger = logging.getLogger("%s.%s" % (
self.__class__.__module__, self.__class__.__name__))
self._data = {}

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# 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
@ -24,3 +24,39 @@ This package provides modules for the command line interface of the
gnuviechadmin server administration suite."""
__all__ = ["client", "sysuser", "domain", "record"]
from logging import getLogger
from sys import exit
class CommandLineInterface(object):
def __init__(self, config, args):
self.log = getLogger(__name__)
self.config = config
if len(args) < 2:
self._usage(args[0])
exit(1)
self.commands = [command for command in self._get_commands() \
if command.name == args[1]]
self.cmdargs = args[2:]
def _usage(self, callee):
print """%s <command> [commandargs]
where command is one of
""" % callee
for command in self._get_commands():
print "%10s - %s" % (command.name, command.description)
def run(self):
for cmd in self.commands:
cmd(self.cmdargs, self.config)
def _get_commands(self):
from gnuviechadmin.cli.client import ClientCli
from gnuviechadmin.cli.sysuser import SysuserCli
from gnuviechadmin.cli.domain import DomainCli
from gnuviechadmin.cli.record import RecordCli
return [ClientCli, SysuserCli, DomainCli, RecordCli]

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# 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
@ -61,24 +61,25 @@ class ClientCli(CliCommand.CliCommand):
def _execute(self, subcommand):
self.logger.debug("execute %s with data %s", subcommand,
str(self._data))
from gnuviechadmin.backend import client
from gnuviechadmin import exceptions
from gnuviechadmin.backend.client import ClientHandler
from gnuviechadmin.exceptions import CreationFailedError
if subcommand == "create":
try:
myclient = client.ClientHandler(self._verbose).create(
**self._data)
myclient = ClientHandler(self.config,
self._verbose).create(**self._data)
if self._verbose:
print myclient
except exceptions.CreationFailedError, cfe:
except CreationFailedError, cfe:
self._usage()
print cfe
sys.exit(2)
elif subcommand == "list":
clients = client.ClientHandler(self._verbose).fetchall()
clients = ClientHandler(self.config, self._verbose).fetchall()
for client in clients:
print client
elif subcommand == "delete":
client.ClientHandler(self._verbose).delete(self._data["clientid"])
ClientHandler(self.config,
self._verbose).delete(self._data["clientid"])
def __init__(self, argv):
CliCommand.CliCommand.__init__(self, argv)
def __init__(self, argv, config):
CliCommand.CliCommand.__init__(self, argv, config)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2007, 2008 by Jan Dittberner.
# Copyright (C) 2007, 2008, 2009 by Jan Dittberner.
#
# 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
@ -30,10 +30,8 @@ from email.MIMEText import MIMEText
from pyme import core
from pyme.constants.sig import mode
from gnuviechadmin.backend.settings import config
def send_mail(subject, text):
def send_mail(config, subject, text):
"""Send a signed and possibly encrypted mail.
This method sends a mail with the given text and subject and signs
@ -52,8 +50,8 @@ def send_mail(subject, text):
cipher = core.Data()
c = core.Context()
c.set_armor(1)
signer = config.get('common', 'mailfrom')
rcpt = config.get('common', 'mailto')
signer = config['mailfrom']
rcpt = config['mailto']
c.signers_clear()
for sigkey in [x for x in c.op_keylist_all(signer, 1)]:
if sigkey.can_sign:
@ -64,14 +62,12 @@ def send_mail(subject, text):
keylist = []
for key in c.op_keylist_all(rcpt, 0):
valid = 0
subkey = key.subkeys
while subkey:
for subkey in key.subkeys:
keyid = subkey.keyid
if keyid == None:
break
can_encrypt = subkey.can_encrypt
valid += can_encrypt
subkey = subkey.next
if valid:
keylist.append(key)
if keylist:

View file

@ -22,7 +22,6 @@
from setuptools import setup, find_packages
try:
import pudge
import buildutils
except ImportError:
pass
@ -35,7 +34,8 @@ setup(
author_email = 'jan@dittberner.info',
url = 'http://www.gnuviech-server.de/projects/gnuviechadmin',
install_requires = ['SQLAlchemy>=0.5', 'sqlalchemy-migrate>=0.5',
'AuthKit>=0.4'],
'AuthKit>=0.4', 'PasteDeploy>=1.3.3',
'python-gnutls>=1.1'],
packages = find_packages(),
include_package_data = True,
exclude_package_data = {'': ['gva.cfg']},
@ -46,4 +46,8 @@ it contains tools for maintaining e.g. clients, domains, users, mail
accounts""",
license = 'GPL',
keywords = 'administration backend frontend',
entry_points = """
[paste.app_factory]
cli = gnuviechadmin.cli.client
""",
)