# HG changeset patch # User Sandro Knauß # Date 1348758946 -7200 # Node ID 3f4bdea2abbf6e5226e7d029dca5adf1a6fd8acd # Parent eb04ac3a83279a97910c19d7d433427fa1572aa7# Parent d5ebbcccc41be4614fd581ce11fa45e7cd79ce2a merging from devel diff -r eb04ac3a8327 -r 3f4bdea2abbf .hgignore --- a/.hgignore Wed Dec 21 22:07:48 2011 +0100 +++ b/.hgignore Thu Sep 27 17:15:46 2012 +0200 @@ -3,6 +3,8 @@ *.pyc *.conf .*.swp +.*.swo +.coverage *orig *.log MyIro @@ -12,3 +14,14 @@ iro.egg-info/* dist/* build/* +_trial_temp/* +*.egg/* +doc/_build/* +twisted/plugins/dropin.cache +*.db +*.old +_build/ +web/dev/ +web/*.html +web/files/*.tar.gz +web.tar.gz diff -r eb04ac3a8327 -r 3f4bdea2abbf LICENSE.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/LICENSE.txt Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,20 @@ +Copyright (c) 2012 netzguerilla.net + +This file is part of Iro. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +#Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -r eb04ac3a8327 -r 3f4bdea2abbf MANIFEST.in --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MANIFEST.in Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,6 @@ +include *.txt +include MANIFEST.in +include iro.conf.inst +recursive-include extras * +recursive-include bin * + diff -r eb04ac3a8327 -r 3f4bdea2abbf MyIro.inst --- a/MyIro.inst Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -#!/usr/bin/python -from iro import start,User,Admin -import logging -from logging.handlers import WatchedFileHandler -format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s' -fileHandler=WatchedFileHandler("/var/log/MyIro.log") -fileHandler.setFormatter(logging.Formatter(format)) -fileHandler.setLevel(logging.INFO) -logging.getLogger().setLevel(logging.INFO) -logging.getLogger().addHandler(fileHandler) - -userlist=[{"name":"blablub","password":"foo", "class":User}, - {"name":"admin","password":"admin", "class": Admin}, - ] -start(userlist) diff -r eb04ac3a8327 -r 3f4bdea2abbf README --- a/README Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -Zum installieren braucht Iro noch: - -- Diese wird in ./iro.conf, ~/.iro.conf und /etc/iro/iro.conf gesucht. -Ein Beispiel ist in iro.conf.inst abgelegt. -- Desweitern muss noch ein Script da sein (Bespiel in MyIro.inst), in dem die Benutzerdatenbank angegeben wird). -- zuguter letzt muss noch eine Datenbank angelegt werden. Das Schema ist in iro.sql abgelegt. diff -r eb04ac3a8327 -r 3f4bdea2abbf README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.txt Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,44 @@ +Iro is a non blocking server for sending a message to a bunsh of recipients. It can handle different types of message and can be extended easially. +It was initially written for sms backend of http://castorticker.de. There are different backends are supported by Iro. +Iro helps you for these kinds of tasks: You want to send a message to some recipient over a different backend, than for other recipients. +Or you want to define fallback strategies: If one messages can't be send over this backend use another. +Because Iro also knows users, you can create bills for different clients. + +Installation of Iro +=================== + +Install the module via ``setup.py install`` und ran ``iro-install``. That will create a sample configuration file named ``iro.conf``. +Update the configuration file and run ``iro-install install`` afterwards. That will create the Database and Offers for you. +After that you have to `adding_user`. Now you are ready to start Iro ``twisted iro``. + + +.. _adding_user: + +Add User and Userrights +----------------------- + +You'll need user and right to spefific Offers, that a user can send with a specific Offer. Till now, it is the only way to add/modify user/userrights. + + +You can add users and userright via SQL:: + + INSERT INTO user (name,apikey,ng_kunde) VALUES("test","a1b2c3"); + INSERT INTO userrights (user,offer,default) VALUES ("test",OFFERNAME,NULL); + +or use python/ipython:: + + >>> import iro.model.schema + >>> from iro.model.schema import * + >>> from sqlalchemy import create_engine + >>> from iro.model.utils import WithSession + >>> engine = create_engine(DBURL) + >>> with WithSession(engine) as session: + ... u = User(name="test",apikey="a1a2c3") + ... session.add(u) + ... o = session.query(Offer).filter_by(name=OFFERNAME).first() + ... u.rights.append(Userright(o, default=None)) + ... session.commit() + >>> + +.. note:: + Please make sure that, the apikey only using *hex digest [0-9a-f]* diff -r eb04ac3a8327 -r 3f4bdea2abbf bin/iro-adduser --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/iro-adduser Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,104 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.python import log + +from sqlalchemy import create_engine, pool +from sqlalchemy.exc import ArgumentError +import sys,os +import random + +import logging + +from iro.controller.pool import dbPool +from iro import config, __version__, install +from iro.model.schema import Offer, User, Userright +from iro.model.utils import WithSession + +observer = log.PythonLoggingObserver() +observer.start() + +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s') + +import argparse + +parser = argparse.ArgumentParser(description='Iro adduser.') +parser.add_argument('-v', '--version', action='version', version="%s %s"%(os.path.basename(sys.argv[0]),__version__)) +parser.add_argument('name', help="username") +parser.add_argument('offers', nargs='+', help="offers for user") +args = parser.parse_args() + +if not install.checkConfig(): + logging.error("iro.conf is not in right format.") + logging.info("Please edit iro.conf") + sys.exit(1) + +config.init() + +try: + engine = create_engine(config.main.dburl, + poolclass = pool.SingletonThreadPool, pool_size=dbPool.maxthreads, ) +except ArgumentError: + logging.error("Can't connect to database") + logging.info("Please edit iro.conf") + sys.exit(1) + + +if not install.checkDatabaseConnection(): + logging.error("Can't connect to database") + logging.info("Please edit iro.conf") + sys.exit(1) + +if not install.checkDatabase(): + logging.error("Database not in right format.") + logging.info("Please edit iro.conf and run iro-install --install") + sys.exit(1) + + +#test offers +with WithSession(engine) as session: + available_offers = [i[0] for i in session.query(Offer.name)] + +error = False +for o in args.offers: + if o not in available_offers: + logging.error("Unknown offer: '%s'"%o) + error = True + +if error: + sys.exit(1) + +#create apikey +apikey = "".join([random.choice("0123456789abcdef") for i in range(15)]) + +#adduser to database +with WithSession(engine, False) as session: + u = User(name=args.name, apikey=apikey) + session.add(u) + for i,offer in enumerate(args.offers): + o = session.query(Offer).filter_by(name=offer).first() + u.rights.append(Userright(o,default=i+1)) + session.commit() +logging.info("created user %s:"% args.name) +logging.info("apikey = %s"% apikey) +logging.info("offers = %s"% ", ".join(args.offers)) diff -r eb04ac3a8327 -r 3f4bdea2abbf bin/iro-install --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/iro-install Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,97 @@ +#!/usr/bin/env python2.7 + +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.python import log + +from sqlalchemy import create_engine, pool +from sqlalchemy.exc import ArgumentError +import sys,os + +import logging + +from iro.controller.pool import dbPool +from iro import config, __version__, install + +observer = log.PythonLoggingObserver() +observer.start() + +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s') + +import argparse + +parser = argparse.ArgumentParser(description='Iro installer.') +parser.add_argument('-v', '--version', action='version', version="%s %s"%(os.path.basename(sys.argv[0]),__version__)) +parser.add_argument('--install', action='store_true', + help='will create the right database layout.') +parser.add_argument('--update', action='store_true', + help='will install all routes and providers.') +args = parser.parse_args() + +if not install.checkConfig(): + install.createSampleConfig() + logging.info("Please edit iro.conf") + sys.exit(1) + +config.init() + +try: + engine = create_engine(config.main.dburl, + poolclass = pool.SingletonThreadPool, pool_size=dbPool.maxthreads, ) +except ArgumentError: + logging.error("Can't connect to database") + logging.info("Please edit iro.conf") + sys.exit(1) + + +if not install.checkDatabaseConnection(): + logging.error("Can't connect to database") + logging.info("Please edit iro.conf") + sys.exit(1) + +if not install.checkDatabase(): + logging.error("Database not in right format.") + if args.install: + install.createDatabase() + logging.info("Database layout created.") + else: + logging.info("Please edit iro.conf and run %s --install"%os.path.basename(sys.argv[0])) + sys.exit(1) + +routes = [ s for s in config.configParser.sections() if not s in ["main",]] +ao = install.getAllRoutes(routes, False) +for o in ao["orphand"]: + logging.info("Offer(%s) is orphand (no route using this offer)."%o) +if ao["added"]: + if args.install or args.update: + ao = install.getAllRoutes(routes,True) + for a in ao["added"]: + logging.info("Added Offer(%s)"%a) + logging.info('Updated offerlist.') + else: + logging.warning('offerlist is not up-to-date.') + logging.info("Please run %s --update"%os.path.basename(sys.argv[0])) + sys.exit(1) + +logging.info("You can just start your iro server.") + + diff -r eb04ac3a8327 -r 3f4bdea2abbf createdoc.py --- a/createdoc.py Wed Dec 21 22:07:48 2011 +0100 +++ b/createdoc.py Thu Sep 27 17:15:46 2012 +0200 @@ -1,14 +1,73 @@ #!/usr/bin/env python2.7 + +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + # -*- coding: utf-8 -*- from genshi.template import TemplateLoader - +from genshi import Markup loader = TemplateLoader('doc/tmpl', auto_reload=True) + +from glob import glob import re +import os.path import inspect -from iro.user import User as Current -from iro.newuser import User as New +from pkg_resources import parse_version + +from docutils.core import publish_doctree, publish_from_doctree, publish_string +from docutils.utils import new_document +from docutils.writers.html4css1 import Writer,HTMLTranslator + +from iro.view.xmlrpc import TwistedInterface as Current +from iro import __version__ +from createerm import createSchemaPlot, tables, tables_cls + +#redering text with rest syntax +class NoHeaderHTMLTranslator(HTMLTranslator): + def __init__(self, document): + HTMLTranslator.__init__(self,document) + self.body_prefix = [] + self.body_suffix = [] + +_w = Writer() +_w.translator_class = NoHeaderHTMLTranslator +_w.visitor_attributes = ("html_body",) + +def d(): + subs = _w.interpolation_dict() + return "%(html_body)s"%subs + +_w.apply_template = d + +def reSTify(s): + d = new_document("") + if s.tagname == "paragraph": + d.append(s[0]) + else: + d.append(s) + + return publish_from_doctree(d, writer=_w) + @@ -30,108 +89,145 @@ class Keyword(): - def __init__(self,name,typ,description): + def __init__(self, name=None, typ=None, description=None): self.name=name self.typ=typ self.description=description -def section(text): - ret={} - li=[] - kw=None - for line in text.split("\n"): - if re.match("^\s*$",line): - continue - - if line[0] not in (" ","\t"): - if kw: - ret[kw.name]=kw - li.append(kw) - l=re.match(r"^(?P[a-zA-Z0-9-_.]*)\[(?P[a-zA-Z0-9-_|]*)\]:(?P.*)$",line) - kw=Keyword(name=l.group("name"),typ=l.group("typ"),description=l.group("d")) - else: - kw.description+="\n"+line.strip() - if kw: - ret[kw.name]=kw - li.append(kw) - return ret,li - - + def __repr__(self): + return ''%(self.name, self.typ, self.description) def keywords(f): - doc=f.__doc__ - kwds=re.search("Keywords:\n(?P(?P\s*)(.+\n)*)\n",doc) - k=kwds.group("keywords") - #get rid of beginning whitespaces - k=re.sub(re.compile(r"^"+kwds.group("whitespace"),re.M),"",k) - return section(k) + NORMAL = 0 + TYPE = 1 + pd = publish_doctree(f.__doc__.decode('utf8')) + + kws={} + for child in pd[1][0]: + kw = Keyword() + ftyp = NORMAL + for sc in child: + if sc.tagname == "field_name": + p = sc.astext().split() + if p[0] in ["param",]: + if len(p) == 3: #param typ name + kw.name = p[2] + kw.typ = p[1] + if len(p) == 2: + kw.name = p[1] + elif p[0] == "type": + kw = kws[p[1]] + ftyp=TYPE + elif p[0] in ["return","rtyp"]: + break + else: + raise Exception("Unknown field_name: %s"%(p[0])) + if sc.tagname == "field_body": + if ftyp == NORMAL: + kw.description = Markup(reSTify(sc[0])) + if ftyp == TYPE: + kw.typ = sc[0][0] + else: + kws[kw.name] = kw + return kws def ret(f): - doc=f.__doc__ - kwds=re.search("Return:\n(?P(?P\s*)(.+\n)*)\n",doc) - k=kwds.group("ret") - #get rid of beginning whitespaces - k=re.sub(re.compile(r"^"+kwds.group("whitespace"),re.M),"",k) - return section(k) - - - + NORMAL = 0 + TYPE = 1 + + pd = publish_doctree(f.__doc__.decode('utf8')) + for child in pd[1][0]: + kw = Keyword(name="return") + ftyp = NORMAL + for sc in child: + if sc.tagname == "field_name": + p = sc.astext().split() + if p[0] == "return": + if len(p) == 2: + kw.typ = p[1] + elif p[0] == "rtype": + ftyp=TYPE + elif p[0] in ["param","type"]: + break + else: + raise Exception("Unknown field_name: %s"%(p[0])) + if sc.tagname == "field_body": + if ftyp == NORMAL: + kw.description = Markup(reSTify(sc[0])) + if ftyp == TYPE: + kw.typ = sc[0][0] + else: + return kw + + raise Exception("no return description") + class Arg(): def __init__(self,name,f): self.name=name - k,_ = keywords(f) + k = keywords(f) kwd=k[name] self.typ=kwd.typ self.description=kwd.description - - class Method(Link): def __init__(self,name,methods): title=name[0].upper()+name[1:] Link.__init__(self,name,title) m=methods[name] (args, varargs, keywords, defaults)=inspect.getargspec(m) - args= [b for b in args if b is not "self"] + a=[] + for b in args: + if b in ("self","session"): + continue + else: + a.append(b) + + args = a self.func_line=inspect.formatargspec(args, varargs, keywords, defaults) - self.description = m.__doc__.split("\n")[0] + pd = publish_doctree(m.__doc__) + if pd[0].tagname == "paragraph": + self.description = pd[0].astext() self.args=[Arg(a,m) for a in args] - _, self.rets=ret(m) + self.rets=[ret(m)] + +class Table(Link): + def __init__(self,cls): + name=cls.__name__ + self.tablename=cls.__tablename__ + title=self.tablename[0].upper()+self.tablename[1:] + Link.__init__(self,name,title) + + self.description = Markup(publish_string(cls.__doc__,writer=_w)) + +class File: + def __init__(self,path): + self.version = re.search("/iro-(.*).tar.gz",path).group(1) + self.name = os.path.basename(path) def main(): sites=[Site("index.html","Iro"), Site("current.html","API Documentation"), - Site("new.html","geplante API Documentation"), - Site("impressum.html","Impressum"), + Site("database.html","Datenbase Schema"), + Site("about.html","About us"), ] - current_methods = dict(inspect.getmembers(Current(None,None))) - current=[ - Method("startSMS",current_methods), - Method("startFAX",current_methods), - Method("startMail",current_methods), - - Method("status",current_methods), - Method("stop",current_methods), - - Method("getProvider",current_methods), - Method("getDefaultProvider",current_methods), - ] + files=[File(f) for f in glob("web/files/*tar.gz")] + files.sort(key=lambda a:parse_version(a.version),reverse=True) + code ="files/iro-%s.tar.gz"%__version__ - new_methods = dict(inspect.getmembers(New())) - newm=[ - Method("sms",new_methods), - Method("fax",new_methods), - Method("mail",new_methods), - - Method("status",new_methods), - Method("stop",new_methods), - - Method("routes",new_methods), - Method("defaultRoute",new_methods), - ] + current_methods = dict(inspect.getmembers(Current())) + current=[ Method(i,current_methods) for i in Current().listMethods() if i != "listMethods" ] + + t = [Table(tables_cls[str(f)]) for f in tables] + createSchemaPlot('web/images/db-schema.svg') + gd = {"sites":sites, + "current":current, + "tables": t, + "code":code, + "files":files + } for site in sites: print("generiere %s" % site.name) @@ -139,8 +235,8 @@ def a(s): if s == site: return {"class":"menu active"} - stream = tmpl.generate(sites=sites,active=a,current=current,new=newm) - with open('doc/'+site.name, "w") as g: + stream = tmpl.generate(active=a, **gd) + with open('web/'+site.name, "w") as g: g.write(stream.render('html', doctype='html')) if __name__ == '__main__': diff -r eb04ac3a8327 -r 3f4bdea2abbf createerm.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/createerm.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,71 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from iro.model import schema + +tables = schema.Base.metadata.sorted_tables + +tables_cls={} +for i in dir(schema): + if i.startswith("__") or i == "Base": + continue + try: + a = getattr(schema,i) + tables_cls[a.__tablename__] = a + except AttributeError: + pass + + +#schema plot +def createSchemaPlot(fname): + from sqlalchemy_schemadisplay3 import create_schema_graph + graph = create_schema_graph(metadata=schema.Base.metadata, + show_datatypes=True, # The image too large if datatypes shown + show_indexes=True, # ditto for indexes + rankdir='LR', # From left to right (instead of top to bottom) + concentrate=True, # Don't try to join the relation lines together + ) + + graph.set_size('6.5,10') + #graph.set_ratio("fill") + graph.write_svg(fname) + +#umlplot +def createUMLPlot(fname): + from sqlalchemy_schemadisplay3 import create_uml_graph + from sqlalchemy.orm import class_mapper + mappers = [] + for attr in dir(schema.model): + if attr[0] == '_': continue + try: + cls = getattr(schema.model, attr) + mappers.append(class_mapper(cls)) + except: + pass + #pass them to the function and set some formatting options + graph = create_uml_graph(mappers, + show_operations=False, # not necessary in this case + show_multiplicity_one=True, # some people like to see the ones + show_attributes=True, + ) + graph.set_size('6,5') + graph.set_ratio("fill") + graph.write_png('test.png') diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/conf.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/conf.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,267 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- +# +# Iro documentation build configuration file, created by +# sphinx-quickstart on Sat Mar 24 12:06:24 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os, re + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('..')) +from iro import __version__ +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode','sphinx.ext.extlinks'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Iro' +copyright = u'2012, netzguerilla.net' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = re.match("^([0-9\.]+)",__version__).groups()[0] +# The full version, including alpha/beta/rc tags. +release = __version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Irodoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Iro.tex', u'Iro Documentation', + u'Sandro Knauß', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'iro', u'Iro Documentation', + [u'Sandro Knauß'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Iro', u'Iro Documentation', + u'Sandro Knauß', 'Iro', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +autoclass_content = 'both' + +extlinks = {'irofile': ("../files/iro-%s.tar.gz","Iro ")} diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/css/960.css --- a/doc/css/960.css Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -.container_12,.container_16{margin-left:auto;margin-right:auto;width:960px}.grid_1,.grid_2,.grid_3,.grid_4,.grid_5,.grid_6,.grid_7,.grid_8,.grid_9,.grid_10,.grid_11,.grid_12,.grid_13,.grid_14,.grid_15,.grid_16{display:inline;float:left;margin-left:10px;margin-right:10px}.push_1,.pull_1,.push_2,.pull_2,.push_3,.pull_3,.push_4,.pull_4,.push_5,.pull_5,.push_6,.pull_6,.push_7,.pull_7,.push_8,.pull_8,.push_9,.pull_9,.push_10,.pull_10,.push_11,.pull_11,.push_12,.pull_12,.push_13,.pull_13,.push_14,.pull_14,.push_15,.pull_15{position:relative}.container_12 .grid_3,.container_16 .grid_4{width:220px}.container_12 .grid_6,.container_16 .grid_8{width:460px}.container_12 .grid_9,.container_16 .grid_12{width:700px}.container_12 .grid_12,.container_16 .grid_16{width:940px}.alpha{margin-left:0}.omega{margin-right:0}.container_12 .grid_1{width:60px}.container_12 .grid_2{width:140px}.container_12 .grid_4{width:300px}.container_12 .grid_5{width:380px}.container_12 .grid_7{width:540px}.container_12 .grid_8{width:620px}.container_12 .grid_10{width:780px}.container_12 .grid_11{width:860px}.container_16 .grid_1{width:40px}.container_16 .grid_2{width:100px}.container_16 .grid_3{width:160px}.container_16 .grid_5{width:280px}.container_16 .grid_6{width:340px}.container_16 .grid_7{width:400px}.container_16 .grid_9{width:520px}.container_16 .grid_10{width:580px}.container_16 .grid_11{width:640px}.container_16 .grid_13{width:760px}.container_16 .grid_14{width:820px}.container_16 .grid_15{width:880px}.container_12 .prefix_3,.container_16 .prefix_4{padding-left:240px}.container_12 .prefix_6,.container_16 .prefix_8{padding-left:480px}.container_12 .prefix_9,.container_16 .prefix_12{padding-left:720px}.container_12 .prefix_1{padding-left:80px}.container_12 .prefix_2{padding-left:160px}.container_12 .prefix_4{padding-left:320px}.container_12 .prefix_5{padding-left:400px}.container_12 .prefix_7{padding-left:560px}.container_12 .prefix_8{padding-left:640px}.container_12 .prefix_10{padding-left:800px}.container_12 .prefix_11{padding-left:880px}.container_16 .prefix_1{padding-left:60px}.container_16 .prefix_2{padding-left:120px}.container_16 .prefix_3{padding-left:180px}.container_16 .prefix_5{padding-left:300px}.container_16 .prefix_6{padding-left:360px}.container_16 .prefix_7{padding-left:420px}.container_16 .prefix_9{padding-left:540px}.container_16 .prefix_10{padding-left:600px}.container_16 .prefix_11{padding-left:660px}.container_16 .prefix_13{padding-left:780px}.container_16 .prefix_14{padding-left:840px}.container_16 .prefix_15{padding-left:900px}.container_12 .suffix_3,.container_16 .suffix_4{padding-right:240px}.container_12 .suffix_6,.container_16 .suffix_8{padding-right:480px}.container_12 .suffix_9,.container_16 .suffix_12{padding-right:720px}.container_12 .suffix_1{padding-right:80px}.container_12 .suffix_2{padding-right:160px}.container_12 .suffix_4{padding-right:320px}.container_12 .suffix_5{padding-right:400px}.container_12 .suffix_7{padding-right:560px}.container_12 .suffix_8{padding-right:640px}.container_12 .suffix_10{padding-right:800px}.container_12 .suffix_11{padding-right:880px}.container_16 .suffix_1{padding-right:60px}.container_16 .suffix_2{padding-right:120px}.container_16 .suffix_3{padding-right:180px}.container_16 .suffix_5{padding-right:300px}.container_16 .suffix_6{padding-right:360px}.container_16 .suffix_7{padding-right:420px}.container_16 .suffix_9{padding-right:540px}.container_16 .suffix_10{padding-right:600px}.container_16 .suffix_11{padding-right:660px}.container_16 .suffix_13{padding-right:780px}.container_16 .suffix_14{padding-right:840px}.container_16 .suffix_15{padding-right:900px}.container_12 .push_3,.container_16 .push_4{left:240px}.container_12 .push_6,.container_16 .push_8{left:480px}.container_12 .push_9,.container_16 .push_12{left:720px}.container_12 .push_1{left:80px}.container_12 .push_2{left:160px}.container_12 .push_4{left:320px}.container_12 .push_5{left:400px}.container_12 .push_7{left:560px}.container_12 .push_8{left:640px}.container_12 .push_10{left:800px}.container_12 .push_11{left:880px}.container_16 .push_1{left:60px}.container_16 .push_2{left:120px}.container_16 .push_3{left:180px}.container_16 .push_5{left:300px}.container_16 .push_6{left:360px}.container_16 .push_7{left:420px}.container_16 .push_9{left:540px}.container_16 .push_10{left:600px}.container_16 .push_11{left:660px}.container_16 .push_13{left:780px}.container_16 .push_14{left:840px}.container_16 .push_15{left:900px}.container_12 .pull_3,.container_16 .pull_4{left:-240px}.container_12 .pull_6,.container_16 .pull_8{left:-480px}.container_12 .pull_9,.container_16 .pull_12{left:-720px}.container_12 .pull_1{left:-80px}.container_12 .pull_2{left:-160px}.container_12 .pull_4{left:-320px}.container_12 .pull_5{left:-400px}.container_12 .pull_7{left:-560px}.container_12 .pull_8{left:-640px}.container_12 .pull_10{left:-800px}.container_12 .pull_11{left:-880px}.container_16 .pull_1{left:-60px}.container_16 .pull_2{left:-120px}.container_16 .pull_3{left:-180px}.container_16 .pull_5{left:-300px}.container_16 .pull_6{left:-360px}.container_16 .pull_7{left:-420px}.container_16 .pull_9{left:-540px}.container_16 .pull_10{left:-600px}.container_16 .pull_11{left:-660px}.container_16 .pull_13{left:-780px}.container_16 .pull_14{left:-840px}.container_16 .pull_15{left:-900px}.clear{clear:both;display:block;overflow:hidden;visibility:hidden;width:0;height:0}.clearfix:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0}* html .clearfix,*:first-child+html .clearfix{zoom:1} \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/css/reset.css --- a/doc/css/reset.css Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0} \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/css/style-ie.css --- a/doc/css/style-ie.css Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -#head #menu { - - width: 750px; - -} - - #head #menu li { - - display: inline; - position: relative; - float: left; - margin: 0px; - padding: 0px; - - } \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/css/style.css --- a/doc/css/style.css Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,975 +0,0 @@ - -body { - - background-color: #eee; - color: #111; - margin: 0px; - padding: 0px; - font-family: "DroidSans", "Trebuchet MS", sans-serif; - line-height: 1.6em; - font-size: 1.3em; - -} - - #head-container { - - display: block; - position: relative; - width: 100%; - height: 90px; - - background: #111 url(../images/head.png) top left repeat-x; - - } - - #head { - - display: block; - position: relative; - width: 960px; - height: 90px; - margin: 0px auto; - - } - - #head #logo { - - display: block; - position: absolute; - left: 0px; - bottom: 10px; - width: 200px; - height: 70px; - - } - - #head #logo a { - - display: block; - position: relative; - width: 200px; - padding-left: 70px; - height: 70px; - background: url(../images/netzguerilla-3.png) 0px 0px no-repeat; - text-decoration: none; - - } - - #head #logo a span { - - display: none; - - } - - #head #menu { - - display: block; - position: absolute; - right: 0px; - bottom: 0px; - margin: 0px; - padding: 0px; - - } - - #head #menu li { - - display: inline-block; - position: relative; - margin: 0px; - padding: 0px; - - } - - #head #menu li a { - - display: block; - position: relative; - - color: #c03; - text-decoration: none; - text-transform: uppercase; - padding: 0px 7px; - background: #333 url(../images/fade-top.png) bottom left repeat-x; - margin-left: 5px; - font-size: .8em; - font-weight: bold; - - -webkit-border-top-left-radius: 4px; - -webkit-border-top-right-radius: 4px; - -moz-border-radius-topleft: 4px; - -moz-border-radius-topright: 4px; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - - } - - #head #menu li a:hover { - - background: #c03 url(../images/fade-top.png) bottom left repeat-x; - color: #fff; - - } - - #head #menu li a.active { - - background-color: #eee; - background-image: none; - color: #000; - - } - - #content-container { - - display: block; - position: relative; - - } - #content { - - padding: 20px 0px; - - } - - #main {} - - #main h2 { - - display: block; - position: relative; - color: #a01; - font-size: 1.8em; - line-height: 1.4em; - margin-bottom: 15px; - text-shadow: 0px 0px 5px #fff; - - } - - #main h3 { - - display: block; - position: relative; - color: #b02; - font-size: 1.4em; - line-height: 1.4em; - margin: 10px 0px; - - } - - #main h4 { - - display: block; - position: relative; - color: #a01; - font-size: 1.3em; - line-height: 1.4em; - margin: 10px 0px; - - } - - #main h5 { - - display: block; - position: relative; - color: #900; - font-size: 1.2em; - line-height: 1.4em; - margin: 10px 0px; - - } - - #main .item { - - display: block; - position: relative; - - padding: 30px 40px; - - border-radius: 15px; - -o-border-radius: 15px; - -moz-border-radius: 15px; - -webkit-border-radius: 15px; - - -moz-box-shadow: 0px 0px 20px #eee; - -o-box-shadow: 0px 0px 20px #eee; - -webkit-box-shadow: 0px 0px 20px #eee; - box-shadow: 0px 0px 20px #eee; - - background-color: #fff; - margin-bottom: 20px; - font-size: .9em; - - } - - #main .item p { - - margin: 5px 0px; - - } - - #main .item a { - - text-decoration: none; - color: #b02; - - } - - #main .item ol { - - list-style-type: none; - list-style-position: inside; - - } - - #main .item ol li { - - margin-left: 2em; - - } - - #main .item table { - - width: 100%; - margin: 0px; - - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; - - -moz-box-shadow: 0px 0px 15px #ccc; - -o-box-shadow: 0px 0px 15px #ccc; - -webkit-box-shadow: 0px 0px 15px #ccc; - box-shadow: 0px 0px 15px #ccc; - - - } - - #main .item table.docs { - - font-size: .8em; - - } - - #main .item table.docs td { - - font-weight: bold; - padding-right: 0.2em; - min-width: 120px; - - } - - #main .item table.docs td+td { - - font-weight: normal; - padding-right: 0.2em; - min-width: 80px; - - } - - #main .item table.docs td+td+td { - - font-weight: normal; - padding-right: 0em; - white-space: normal; - - } - - #main .item table td { - - background-color: #eee; - line-height: 1.7em; - - } - - #main .item table thead tr td { - - background-color: #c03; - color: #fff; - font-size: .8em; - padding: 5px; - - } - - #main .item table tbody tr td { - - color: #222; - vertical-align: top; - - } - - #main .item table tbody tr td.content { - - padding: 10px; - font-size: .8em; - - } - - #main .item table tbody tr td.content strong.title { - - display: inline-block; - position: relative; - max-width: 140px; - overflow: hidden; - - } - - #main .item table tbody tr td.content span.meta { - - display: block; - position: relative; - max-width: 140px; - overflow: hidden; - line-height: 1.4em; - font-size: .8em; - - } - - #main .item table tbody tr td.content span.hate { - - display: block; - position: relative; - line-height: 1.4em; - border: 1px solid #ccc; - padding: 10px; - text-align: justify; - - } - #main .item table tbody tr:nth-child(odd) td { - - background-color: #f9f9f9; - - } - - #main .item table tbody tr:nth-child(even) td { - - background-color: #f6f6f6; - - } - - #main .item table tr td:first-child { - - padding-left: 5px; - - } - - #main .item table tr td:last-child { - - padding-right: 5px; - - } - - #main .item table tr td.controls { - - width: 80px; - text-align: right; - - } - - #main .item table tr td.controls a { - - display: inline-block; - width: 20px; - height: 20px; - overflow: hidden; - text-indent: 30px; - background-color: #eee; - - -moz-box-shadow: 0px 0px 5px #ccc; - -o-box-shadow: 0px 0px 5px #ccc; - -webkit-box-shadow: 0px 0px 5px #ccc; - box-shadow: 0px 0px 5px #ccc; - - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - - vertical-align: bottom; - margin: 0px 0px 4px 2px; - - background-image: url(../images/icons/bug.png); - background-position: center center; - background-repeat: no-repeat; - - } - - #main .item table tr td.controls a:hover { - - background-color: #c03; - - } - - #main .item table tr td.controls a.edit { - - background-image: url(../images/icons/pencil.png); - - } - - #main .item table tr td.controls a.delete { - - background-image: url(../images/icons/bin.png); - - } - - #main .item table tr td.controls a.details { - - background-image: url(../images/icons/zoom.png); - - } - - #main .item table tr td.controls a.accept { - - background-image: url(../images/icons/accept.png); - - } - - /* round corners */ - - #main .item table thead tr:first-child td:first-child { - - -webkit-border-top-left-radius: 5px; - -moz-border-radius-topleft: 5px; - border-top-left-radius: 5px; - - } - - #main .item table thead tr:first-child td:last-child { - - -webkit-border-top-right-radius: 5px; - -moz-border-radius-topright: 5px; - border-top-right-radius: 5px; - - } - - #main .item table tbody tr:last-child td:first-child { - - -webkit-border-bottom-left-radius: 5px; - -moz-border-radius-bottomleft: 5px; - border-bottom-left-radius: 5px; - - } - - #main .item table tbody tr:last-child td:last-child { - - -webkit-border-bottom-right-radius: 5px; - -moz-border-radius-bottomright: 5px; - border-bottom-right-radius: 5px; - - } - - #main .item code.copyme { - - display: block; - margin: 0px 0px 10px; - padding: 10px; - line-height: 1em; - border: 1px solid #ccc; - background-color: #eee; - - } - - /* main view */ - - #main .hate-pagination { - - display: block; - position: relative; - line-height: 1.2em; - padding: 10px 0px; - - } - - #main .hate-pagination-back { - - display: block; - position: relative; - width: 300px; - text-align: left; - float: left; - - } - - #main .hate-pagination-forward { - - display: block; - position: relative; - width: 300px; - text-align: right; - float: right; - - } - - #main .ad-item { - - display: block; - position: relative; - border-bottom: 1px solid #ccc; - line-height: 1.2em; - padding: 20px 0px; - text-align: center; - - } - - #main .hate-item { - - display: block; - position: relative; - border-bottom: 1px solid #ccc; - line-height: 1.2em; - padding: 10px 0px; - - } - - #main .hate-item .hate-item-icon { - - display: block; - position: relative; - width: 48px; - padding: 5px 0px 5px 10px; - float: left; - - } - - #main .hate-item .hate-item-main { - - display: block; - position: relative; - width: 550px; - float: right; - - } - - #main .hate-item .hate-item-author { - - display: block; - position: relative; - font-size: .8em; - font-weight: bold; - color: #222; - - } - - #main .hate-item .hate-item-author a { - - color: #222; - - } - - #main .hate-item .hate-item-content { - - display: block; - position: relative; - font-size: .8em; - font-weight: normal; - color: #444; - - } - - #main .hate-item .hate-item-meta { - - display: block; - position: relative; - font-size: .6em; - color: #666; - - } - - #main .hate-item .hate-item-meta a { - - color: #666; - - } - - #main .hate-item .hate-item-meta a.facepalm { - - display: inline-block; - padding-left: 20px; - line-height: 16px; - background: url(../images/icons/facepalm.png) top left no-repeat; - - } - - #main .hate-item:hover .hate-item-meta a.facepalm { - - background: url(../images/icons/facepalm-hl.png) top left no-repeat; - - } - - #main .hate-item .hate-item-meta span.godwin { - - display: inline-block; - padding-left: 20px; - line-height: 16px; - background: url(../images/icons/godwinbonus.png) top left no-repeat; - - } - - #main .hate-item:hover { - - background-color: #fce; - - } - - #main .hate-item:hover a { - - color: #c03; - - } - - #main .hate-detail { - - display: block; - position: relative; - border-bottom: 1px solid #ccc; - line-height: 1.2em; - padding: 10px 0px; - - } - - #main .hate-detail .hate-item-icon { - - display: block; - position: relative; - width: 48px; - padding: 5px 0px; - float: left; - - } - - #main .hate-detail .hate-item-main { - - display: block; - position: relative; - - } - - #main .hate-detail .hate-item-author { - - display: block; - position: relative; - font-size: 1.2em; - line-height: 50px; - font-weight: bold; - color: #222; - width: 560px; - float: right; - - } - - #main .hate-detail .hate-item-content { - - display: block; - position: relative; - font-size: 1.2em; - line-height: 1.4em; - font-weight: normal; - color: #444; - padding: 10px 0px; - - } - - #main .hate-detail .hate-item-meta { - - display: block; - position: relative; - font-size: .8em; - color: #666; - - } - - #main .hate-detail .hate-item-meta a { - - color: #666; - - } - - #main .hate-detail .hate-item-meta a.facepalm { - - display: inline-block; - padding-left: 20px; - line-height: 16px; - background: url(../images/icons/facepalm.png) top left no-repeat; - - } - - #main .hate-detail:hover .hate-item-meta a.facepalm { - - background: url(../images/icons/facepalm-hl.png) top left no-repeat; - - } - - #main .hate-detail .hate-item-meta span.godwin { - - display: inline-block; - padding-left: 20px; - line-height: 16px; - background: url(../images/icons/godwinbonus.png) top left no-repeat; - - } - - - /* special stuff */ - - #main span.redacted { - - display: inline-block; - background-color: #333; - color: #c03; - line-height: 1.1em; - padding: 0px 5px; - - } - - #main code.apikey { - - font-size: 1.1em; - background-color: #a01; - color: #fff; - display: inline-block; - padding: 0px 5px; - - } - - #main a.bookmarklet { - - font-size: 1.4em; - background-color: #ccc; - color: #666; - border: 1px solid #666; - display: inline-block; - padding: 5px 10px; - - -webkit-border-radius: 10px; - -moz-border-radius: 10px; - border-radius: 10px; - - } - - #main form input[type=text], - #main form input[type=password] { - - display: block; - position: relative; - border: 1px solid #999; - font-size: .9em; - padding: 3px; - - border-radius: 5px; - -o-border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - - } - - #main form input[type=submit] { - - display: block; - position: relative; - border: 1px solid #ccc; - color: #111; - font-size: .9em; - padding: 2px; - - border-radius: 5px; - -o-border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - - - } - - #main form span.input { - - display: inline-block; - position: relative; - border: 1px solid #999; - font-size: .9em; - padding: 0px 2px; - background-color: #fff; - border-radius: 5px; - -o-border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - - - } - - #main form span.input input[type=text] { - - display: inline-block; - position: relative; - border-width: 0px; - font-size: 1em; - padding: 0px; - background-color: #fff; - - } - - #main form span.input input[type=text].right { - - text-align: right; - - } - - #main form span.input input.size-2 { - - width: 1.5em !important; - - } - - #main form span.input input.size-4 { - - width: 2.5em !important; - - } - - #main form label { - - display: block; - position: relative; - font-size: .75em; - color: #666; - - } - - #main form textarea { - - display: block; - position: relative; - border: 1px solid #999; - font-size: .9em; - font-family: sans-serif; - padding: 3px; - - border-radius: 5px; - -o-border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - - } - - - #main form fieldset { - - border: 1px solid #ccc; - padding: 5px; - margin: 5px 0px; - - } - - #main form fieldset legend { - - font-size: .75em; - color: #666; - - } - - #sidebar {} - - #sidebar h3 { - - display: block; - position: relative; - color: #fff; - font-size: 1.2em; - line-height: 1.4em; - margin: 5px 0px; - - } - - #sidebar .item { - - display: block; - position: relative; - - padding: 0px 10px; - - border-radius: 10px; - -o-border-radius: 10px; - -moz-border-radius: 10px; - -webkit-border-radius: 10px; - - -moz-box-shadow: 0px 0px 20px #eee; - -o-box-shadow: 0px 0px 20px #eee; - -webkit-box-shadow: 0px 0px 20px #eee; - box-shadow: 0px 0px 20px #eee; - - background-color: #333; - margin-bottom: 20px; - font-size: .8em; - - } - - #sidebar .center { - - text-align: center; - - } - - #sidebar .counter-item .content { - - font-size: 3em; - color: #c03; - font-weight: bold; - line-height: 1em; - padding-bottom: 5px; - text-align: center; - - } - - #sidebar .submenu-item { - - background-color: #ddd; - - } - - #sidebar .submenu-item ul {} - - #sidebar .submenu-item ul li {} - - #sidebar .submenu-item ul li+li { - - border-top: 1px dotted #999; - - } - - #sidebar .submenu-item ul a { - - color: #c03; - text-decoration: none; - text-transform: uppercase; - - } - - #sidebar .submenu-item ul a:hover { - - color: #a01; - - } - - #foot-container { - - display: block; - position: relative; - background: #222; - color: #ddd; - - } - - #foot { - - display: block; - position: relative; - width: 960px; - margin: 0px auto; - padding: 20px; - font-size: .8em; - - } - - #foot a { - - text-decoration: none; - color: #c03; - - } - diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/current.html --- a/doc/current.html Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,380 +0,0 @@ - - - - - - Iro · API docs - - - - - - - - - - - - -
-
-
-

API Dokumentation

-
-

-

-
    -
  1. 1. Einführung
  2. -
  3. 2. - Interfaces -
      -
    1. 2.1 XML-RPC
    2. -
    -
  4. -
  5. 3. - Methoden -
      -
    1. 3.1 StartSMS
    2. 3.2 StartFAX
    3. 3.3 StartMail
    4. 3.4 Status
    5. 3.5 Stop
    6. 3.6 GetProvider
    7. 3.7 GetDefaultProvider
    8. -
    -
  6. -
-
-

Einführung

-

- Die Iro API enthält Funktion, die für den Massenversand nützlich sind. -

-
-

Interfaces

-

- Die Iro API stellt zur Zeit nur ein Interfaces bereit. -

-
-

XML-RPC

-

- Interface-URI: https://<benutzer>:<passwort>@localhost:8000 -

-

- Die aufgerufene Methode wird dabei im <methodName /> übergeben. -

-
-
-

Methoden

-
-

StartSMS

-

startSMS(message, recipients, provider='default')

-

Versendet eine SMS.

-
Parameter
- - - - - - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
messagestring Nachricht
recipientslist eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
providerstring Provider über den geschickt werden soll
-
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
idhash Die ID des Auftrages
-
-

StartFAX

-

startFAX(subject, fax, recipients, provider='default')

-

Versendet ein FAX.

-
Parameter
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
subjectstring der Betreff
faxstring das pdf base64 kodiert
recipientslist eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
providerstring Provider über den geschickt werden soll
-
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
idhash Die ID des Auftrages
-
-

StartMail

-

startMail(subject, body, recipients, frm, provider='default')

-

Versendet eine Email.

-
Parameter
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
subjectstring der Betreff
bodystring der Email Body
recipientslist eine Liste von Emailadressen
frmstring Die Absender Emailadresse
providerstring Provider über den geschickt werden soll
-
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
idhash Die ID des Auftrages
-
-

Status

-

status(id=None, detailed=False)

-

Gibt den aktuellen Status eines Auftrages zurück.

-
Parameter
- - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
idhash Eine Auftragsnummer
detailedboolean Details ausgeben
-
Ausgabe
- - - - - - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
jobslist Eine Liste der Aufträge.
job.namestring Angebener Name
job.statusstring Status des Auftrages
-
-

Stop

-

stop(id)

-

Stoppt den angegeben Auftrag.

-
Parameter
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
idhash Eine Auftragsnummer
-
-

GetProvider

-

getProvider(typ)

-

Gibt eine Liste aller verfügbaren Provider zurück.

-
Parameter
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
typstring Der Typ zu dem die Providerloste ausgeben werden soll -Einer der Liste ["sms","fax","mail"]
-
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
providerlistlist Eine Liste aller möglichen Provider
-
-

GetDefaultProvider

-

getDefaultProvider(typ)

-

Gibt den Standardprovider zurück.

-
Parameter
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
typstring Der Typ zu dem die Providerloste ausgeben werden soll -Einer der Liste ["sms","fax","mail"]
-
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
providerstring Der Standardprovider für den angeben Typ
-
-
-
-
-
-
-
- -
- - \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/images/favicon.png Binary file doc/images/favicon.png has changed diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/images/netzguerilla-3.png Binary file doc/images/netzguerilla-3.png has changed diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/impressum.html --- a/doc/impressum.html Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,62 +0,0 @@ - - - - - - Iro · Impressum - - - - - - - - - - - - -
-
-
-

Impressum

-
-

Kontakt

-

- Netzguerilla.net Clerc Knauß Vollnhals GbR
- Manteuffelstraße 40
- 10997 Berlin -

-

- WWW: netzguerilla.net
-

-

- E-Mail: iro@netzguerilla.net
-

-

- Telefon: +49 30 69201075
-

-

Inhaltlich verantwortlich

-

- Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV bzw. §5 TMG sowie beauftragt mit dem Jugendschutz gemäß TMG, JMStV sowie GjSM:
- Sandro Knauß, Anschrift wie oben, iro@netzguerilla.net -

-
-
-
-
-
-
- -
- - \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/index.html --- a/doc/index.html Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,45 +0,0 @@ - - - - - - Iro · work in progress - - - - - - - - - - - - -
-
-
-

Iro

-
-

- Iro ist toll, ist aber noch nicht fertig. -

-
-
-
-
-
-
- -
- - \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/index.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/index.rst Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,27 @@ +.. Iro documentation master file, created by + sphinx-quickstart on Sat Mar 24 12:06:24 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to Iro's documentation! +=============================== + +Contents: + +.. toctree:: + + install + +Extend Iro: + +.. toctree:: + + provider + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/install.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/install.rst Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,45 @@ +Installation of Iro +=================== + +Start with downloading the `source <../index.html#files>`_. +Afterwards install the module via ``setup.py install`` und ran ``iro-install``. That will create a sample configuration file named ``iro.conf``. +Update the configuration file and run ``iro-install install`` afterwards. That will create the Database and Offers for you. +After that you have to :ref:`adding_user`. +Now you are ready to start Iro ``twisted iro``. + + +.. _adding_user: + +Add User and Userrights +----------------------- + +You'll need user and right to spefific Offers, that a user can send with a specific Offer. Till now, it is the only way to add/modify user/userrights. + + +You can add users and userright via SQL:: + + INSERT INTO user (name,apikey,ng_kunde) VALUES("test","a1b2c3"); + INSERT INTO userrights (user,offer,default) VALUES ("test",OFFERNAME,NULL); + +or use python/ipython:: + + >>> import iro.model.schema + >>> from iro.model.schema import * + >>> from sqlalchemy import create_engine + >>> from iro.model.utils import WithSession + >>> engine = create_engine(DBURL) + >>> with WithSession(engine) as session: + ... u = User(name="test",apikey="a1a2c3") + ... session.add(u) + ... o = session.query(Offer).filter_by(name=OFFERNAME).first() + ... u.rights.append(Userright(o, default=None)) + ... session.commit() + >>> + +.. note:: + Please make sure that, the apikey only using *hex digest [0-9a-f]* + +Sample Configuration +==================== + +.. literalinclude:: ../iro.conf.inst diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/iro.controller.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/iro.controller.rst Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,27 @@ +controller Package +================== + +:mod:`pool` Module +------------------ + +.. automodule:: iro.controller.pool + :members: + :undoc-members: + :show-inheritance: + +:mod:`task` Module +------------------ + +.. automodule:: iro.controller.task + :members: + :undoc-members: + :show-inheritance: + +:mod:`viewinterface` Module +--------------------------- + +.. automodule:: iro.controller.viewinterface + :members: + :undoc-members: + :show-inheritance: + diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/iro.model.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/iro.model.rst Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,91 @@ +model Package +============= + +:mod:`model` Package +-------------------- + +.. automodule:: iro.model + :members: + :undoc-members: + :show-inheritance: + +:mod:`dbdefer` Module +--------------------- + +.. automodule:: iro.model.dbdefer + :members: + :undoc-members: + :show-inheritance: + +:mod:`decorators` Module +------------------------ + +.. automodule:: iro.model.decorators + :members: + :undoc-members: + :show-inheritance: + +:mod:`job` Module +----------------- + +.. automodule:: iro.model.job + :members: + :undoc-members: + :show-inheritance: + +:mod:`message` Module +--------------------- + +.. automodule:: iro.model.message + :members: + :undoc-members: + :show-inheritance: + +:mod:`offer` Module +------------------- + +.. automodule:: iro.model.offer + :members: + :undoc-members: + :show-inheritance: + +:mod:`pool` Module +------------------ + +.. automodule:: iro.model.pool + :members: + :undoc-members: + :show-inheritance: + +:mod:`schema` Module +-------------------- + +.. automodule:: iro.model.schema + :members: + :undoc-members: + :show-inheritance: + +:mod:`status` Module +-------------------- + +.. automodule:: iro.model.status + :members: + :undoc-members: + :show-inheritance: + +:mod:`user` Module +------------------ + +.. automodule:: iro.model.user + :members: + :undoc-members: + :show-inheritance: + +:mod:`utils` Module +------------------- + +.. automodule:: iro.model.utils + :members: + :undoc-members: + :show-inheritance: + diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/iro.offer.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/iro.offer.rst Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,51 @@ +offer Package +============= + +:mod:`offer` Package +-------------------- + +.. automodule:: iro.offer + :members: + :undoc-members: + :show-inheritance: + +:mod:`offer` Module +------------------- + +.. automodule:: iro.offer.offer + :members: + :undoc-members: + :show-inheritance: + +:mod:`provider` Module +---------------------- + +.. automodule:: iro.offer.provider + :members: + :undoc-members: + :show-inheritance: + +:mod:`sipgate` Module +---------------------- + +.. automodule:: iro.offer.sipgate + :members: + :undoc-members: + :show-inheritance: + +:mod:`smstrade` Module +---------------------- + +.. automodule:: iro.offer.smstrade + :members: + :undoc-members: + :show-inheritance: + +:mod:`smtp` Module +------------------ + +.. automodule:: iro.offer.smtp + :members: + :undoc-members: + :show-inheritance: + diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/iro.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/iro.rst Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,69 @@ +iro Package +=========== + +:mod:`iro` Package +------------------ + +.. automodule:: iro.__init__ + :members: + :undoc-members: + :show-inheritance: + +:mod:`config` Module +-------------------- + +.. automodule:: iro.config + :members: + :undoc-members: + :show-inheritance: + +:mod:`error` Module +------------------- + +.. automodule:: iro.error + :members: + :undoc-members: + :show-inheritance: + +:mod:`install` Module +--------------------- + +.. automodule:: iro.install + :members: + :undoc-members: + :show-inheritance: + +:mod:`main` Module +------------------ + +.. automodule:: iro.main + :members: + :undoc-members: + :show-inheritance: + +:mod:`telnumber` Module +----------------------- + +.. automodule:: iro.telnumber + :members: + :undoc-members: + :show-inheritance: + +:mod:`validate` Module +---------------------- + +.. automodule:: iro.validate + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + iro.controller + iro.model + iro.offer + iro.view + diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/iro.view.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/iro.view.rst Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,19 @@ +view Package +============ + +:mod:`xmlrpc` Module +-------------------- + +.. automodule:: iro.view.xmlrpc + :members: + :undoc-members: + :show-inheritance: + +:mod:`xmlrpc_old` Module +------------------------ + +.. automodule:: iro.view.xmlrpc_old + :members: + :undoc-members: + :show-inheritance: + diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/new.html --- a/doc/new.html Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,473 +0,0 @@ - - - - - - Iro · api docs new - - - - - - - - - - - - -
-
-
-

geplante API Dokumentation

-
-

-

-
    -
  1. 1. Einführung
  2. -
  3. 2. - Interfaces -
      -
    1. 2.1 XML-RPC
    2. -
    3. 2.1 SOAP
    4. -
    5. 2.2 XML
    6. -
    7. 2.3 JSON
    8. -
    9. 2.4 JSONP
    10. -
    11. 2.5 PHP
    12. -
    -
  4. -
  5. 3. - Methoden -
      -
    1. 3.1 Sms
    2. 3.2 Fax
    3. 3.3 Mail
    4. 3.4 Status
    5. 3.5 Stop
    6. 3.6 Routes
    7. 3.7 DefaultRoute
    8. -
    -
  6. -
-
-

Einführung

-

- Die Iro API enthält Funktion, die für den Massenversand nützlich sind. -

-
-

Interfaces

-

- Die Iro API wird über verschiedene Interfaces bereit gestellt, die unterschiedlich angesprochen werden, aber das selbe tun. -

-
-

XML-RPC

-

- Interface-URI: http://localhost:8000/xmlrpc -

-

- Die aufgerufene Methode wird dabei im <methodName /> übergeben. -

-
-
-

SOAP

-

- Interface-URI: http://localhost:8000/soap -

-

- Die aufgerufene Methode wird dabei im <methodName /> übergeben. -

-
-
-

XML

-

- Interface-URI: http://localhost:8000/xml/<methode> -

-

- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben. -

-

- Parameter können via HTTP per GET oder POST im Format application/x-www-form-urlencoded übergeben werden. -

-

- Die Ausgabe erfolgt als XML Markup. -

-
-
-

JSON

-

- Interface-URI: http://localhost:8000/json/<methode> -

-

- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben. -

-

- Parameter können via HTTP per GET oder POST im Format application/x-www-form-urlencoded oder JSON-Objekt übergeben werden. -

-

- Die Ausgabe erfolgt als JSON-Objekt. -

-
-
-

JSONP

-

- Interface-URI: http://localhost:8000/jsonp/<methode>?callback=<callback> -

-

- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben. -

-

- Der Name für die Callback-Methode wird als Parameter Callback übergeben. -

-

- Parameter können via HTTP per GET im Format application/x-www-form-urlencoded übergeben werden. -

-

- Die Ausgabe erfolgt als Javascript-Funktionsaufruf mit einem JSON-Objekt als Parameter. -

-
-
-

Methoden

-
-

Sms

-

sms(apikey, message, recipients, route='default')

-

Versendet eine SMS.

-
Parameter
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
apikeystring Der API Key
messagestring Nachricht
recipientslist eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
routestring|list Route über den geschickt werden soll, -oder eine Liste von Routen, um Fallbacks anzugeben
-
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
idhash Die ID des Auftrages
-
-

Fax

-

fax(apikey, subject, fax, recipients, route='default')

-

Versendet ein FAX.

-
Parameter
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
apikeystring Der API Key
subjectstring Der Betreff
faxstring Das PDF base64 kodiert
recipientslist Eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)
routestring|list Route über den geschickt werden soll, -oder eine Liste von Routen, um Fallbacks anzugeben
-
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
idhash Die ID des Auftrages
-
-

Mail

-

mail(apikey, subject, body, recipients, frm, route='default')

-

Versendet eine Email.

-
Parameter
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
apikeystring Der API Key
subjectstring Der Betreff
bodystring Der Email Body
recipientslist Eine Liste von Emailadressen
frmstring Die Absender Emailadresse
routestring|list Route über den geschickt werden soll, -oder eine Liste von Routen, um Fallbacks anzugeben
-
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
idhash Die ID des Auftrages
-
-

Status

-

status(apikey, id=None, detailed=False)

-

Gibt den aktuellen Status eines Auftrages zurück.

-
Parameter
- - - - - - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
apikeystring Der API Key
idhash Eine Auftragsnummer
detailedboolean Details ausgeben
-
Ausgabe
- - - - - - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
jobslist Eine Liste der Aufträge.
job.namestring Angebener Name
job.statusstring Status des Auftrages
-
-

Stop

-

stop(apikey, id)

-

Stoppt den angegeben Auftrag.

-
Parameter
- - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
apikeystring Der API Key
idhash Eine Auftragsnummer
-
-

Routes

-

routes(apikey, typ)

-

Gibt eine Liste aller verfügbaren Provider zurück.

-
Parameter
- - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
apikeystring Der API Key
typstring Der Typ zu dem die Providerloste ausgeben werden soll -Einer der Liste ["sms","fax","mail"]
-
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
providerlistlist Eine Liste aller möglichen Provider
-
-

DefaultRoute

-

defaultRoute(apikey, typ)

-

Gibt den Standardprovider zurück.

-
Parameter
- - - - - - - - - - - - - - - - - - - -
ParameterTypBeschreibung
apikeystring Der API Key
typstring Der Typ zu dem die Providerloste ausgeben werden soll -Einer der Liste ["sms","fax","mail"]
-
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
providerstring Der Standardprovider für den angeben Typ
-
-
-
-
-
-
-
- -
- - \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/provider.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/provider.rst Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,143 @@ +Creating a new Providerbackend for Iro +====================================== + +See also class documentation :class:`iro.offer.provider.Provider`. + +A very simple provider +---------------------- + +For testing purpose it is nice to create a small provider. + +.. code-block:: python + :linenos: + + from iro.offer import providers, Provider + + class TestProvider(Provider): + def __init__(self,name): + Provider.__init__(self, name, {"sms" : ["a",]}) + + providers["myveryspecialProvider"] = TestProvider + +- *line 3* -- a Provider that supports message type **sms**, and has one route named **a**. +- *line 5* -- register the provider type **TestProvider** in the global **providers** dict. Following section in configuraton file will create a new TestProvider object, with ``name="blablub"``:: + + [blablub] + #see line 5 + typ = myveryspecialProvider + + +Normally a new Provider wants to have extra options for configuration file: + +.. code-block:: python + :linenos: + + from iro.offer import providers, Provider + from iro.config import Option + + def validater(value, field): + return value + + class TestProvider(Provider): + def __init__(self,name): + options =[("key", Option(validater,long="My Option explanation", must=True)),] + Provider.__init__(self, name, {"sms" : ["a",]}, options) + + providers["myveryspecialProvider"] = TestProvider + + +in *line 9* we create a item list ( ``[(name,Option),...]`` -- more information about :class:`iro.config.Option`). **validater** have to be a function that returns value, if the value is valid. With this following section in configuration file is possible:: + + [balblub] + typ = myveryspecialProvider + #My Option explanation + key = mykey + +Ok, now we know to get settings into the provider. But we have to do anything, when user want to send anything. So we have to create a send function. + +Creating sipgate provider +------------------------- + +Sipgate supports sending sms and faxes via XML-RPC. so it is easy to create a new providerbackend for iro via sipgate. First we get the XML-RPC Api documention for sipgate (http://www.sipgate.de/beta/public/static/downloads/basic/api/sipgate_api_documentation.pdf). Sipgate uses HTTP Basic Authentification, that's we he create to options for our sipgate provider: + +.. code-block:: python + :linenos: + + from iro.offer import providers, Provider + from iro.config import Option + + class Sipgate(Provider): + def __init__(self,name): + options =[("username", Option(lambda x,y: x,long="Loginname for sipgate", must=True)), + ("password", Option(lambda x,y: x,long="Password for sipgate", must=True)),] + Provider.__init__(self, name, {"sms" : [None], "fax":[None]}, options) + + providers["sipgate"] = Sipgate + +- *line 6/7* -- we don't have any ideas what is allowed as username/password, so we create a validator that accepts everything. +- *line 8* -- sipgate supports fax and sms, but now diffrent routes, that's we use ``None``. + +Now we have to possible options to implement the send function. either we implement a blocking interface or use the recommended solution: twisted non blocking solution. We show here the recommended version. + +The Twisted Way (recommended solution) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +First we start to implement the ``fax`` and ``sms`` methods: + +.. code-block:: python + :linenos: + + def proxy(self): + return Proxy("https://%s:%s@samurai.sipgate.net/RPC2"%(self.username, self.password)) + + def sms(self, recipient, sms): + args={ + "TOS" : "text", + "Content" : sms.getContent(), + "RemoteUri" : "sip:%s%s@sipgate.net"%(recipient.land, recipient.number), + } + return self.proxy().callRemote("samurai.SessionInitiate",args) + + def fax(self, recipient, fax): + args={ + "TOS" : "fax", + "Content" : xmlrpclib.Binary(fax.getAttachment(0)), + "RemoteUri" : "sip:%s%s@sipgate.net"%(recipient.land, recipient.number), + } + return self.proxy().callRemote("samurai.SessionInitiate",args) + +The code is straight forward with the API documentation from sipgate. Now we have to implement the heat of the provider the ``send`` method: + +.. code-block:: python + :linenos: + + def _status(self,value,typ): + if typ not in self.typs.keys(): + raise NoTyp(typ) + return Status(self, None, Decimal("1.00"), 1, value["SessionID"]) + + def send(self, typ, recipient, msg): + if typ not in self.typs.keys(): + raise NoTyp(typ) + d = getattr(self,typ)(recipient, msg) + d.addCallback(self._status, typ) + return d + + def getSendFunc(self, typ, route): + """returns :meth:`send` method, if typ and route is valid.""" + + Provider.getSendFunc(self, typ, route) + return partial(self.send, typ) + +Because sipgate doesn't support different routes, we implement a send function without route argument. +That's why we have to rewrite the ``getSendFunc`` method. It now returns a partial function with only a binded ``typ``. + +The ``send`` method first test the for a valid typ (*line 7/8*), than it execute the ``sms`` or ``fax`` method. +For a valid provider we have to return a :class:`~iro.model.status.Status` object. +There for we add a callback that returns a :class:`~iro.model.status.Status` object (see ``_status`` method). + +Unfortunatelly sipgate doesn't support methods to get the price for one action. +So we have to set set a fixed price here ``Decimal('1.00')``. +In the wild we implement new configuration parameters for priceing. + +Now the provider is ready to use. For complete source of this tutorial see :class:`iro.offer.sipgate`. diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/tmpl/about.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/tmpl/about.html Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,36 @@ + + + + + About us + + + About us + +
+

Contact

+

+ Netzguerilla.net Clerc Knauß Vollnhals GbR
+ Manteuffelstraße 40
+ 10997 Berlin +

+

+ WWW: netzguerilla.net
+

+

+ E-Mail: iro@netzguerilla.net
+

+

+ Telefon: +49 30 69201075
+

+

German legal notice - Inhaltlich verantwortlich

+

+ Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV bzw. §5 TMG sowie beauftragt mit dem Jugendschutz gemäß TMG, JMStV sowie GjSM:
+ Sandro Knauß, Anschrift wie oben, iro@netzguerilla.net +

+
+ + diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/tmpl/current.html --- a/doc/tmpl/current.html Wed Dec 21 22:07:48 2011 +0100 +++ b/doc/tmpl/current.html Thu Sep 27 17:15:46 2012 +0200 @@ -5,66 +5,91 @@ xmlns:py="http://genshi.edgewall.org/"> - API docs + api docs current - API Dokumentation + API Documentation

- +

    -
  1. 1. Einführung
  2. +
  3. 1. Intro
  4. 2. - Interfaces -
      -
    1. 2.1 XML-RPC
    2. -
    + Interfaces +
      +
    1. 2.1 XML-RPC
    2. +
    3. 2.2 SOAP
    4. +
    5. 2.3 JSON
    6. +
    7. 2.4 JSONP
    8. +
  5. 3. - Methoden + Methods
    1. 3.${key+1} ${method.title}
  6. +
-

Einführung

+

Intro

- Die Iro API enthält Funktion, die für den Massenversand nützlich sind. + Iro API has many methods, that are usefull if you want to send a bunch of messages. This Site describes the API for Iro 1.0.

Interfaces

- Die Iro API stellt zur Zeit nur ein Interfaces bereit. + You can use diffrent interfaces to get to same result.

XML-RPC

- Interface-URI: https://<benutzer>:<passwort>@localhost:8000 + Interface-URI: http://localhost:8000/xmlrpc +

+
+
+

SOAP

+

+ Interface-URI: http://localhost:8000/soap +

+
+
+

JSON

+

+ Interface-URI: http://localhost:8000/json/<methode>

- Die aufgerufene Methode wird dabei im <methodName /> übergeben. + Not yet implementet +

+
+
+

JSONP

+

+ Interface-URI: http://localhost:8000/jsonp/<methode>?callback=<callback> +

+

+ Not yet implementet

-

Methoden

+

Methods

${method.title}

${method.name}${method.func_line}

- Diese Methode at bis jetzt noch keine Beschreibung. + No description

Parameter
- - - + + + @@ -76,13 +101,13 @@
ParameterTypBeschreibungparametertypedescription
-
Ausgabe
+
Return
- - - + + + diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/tmpl/database.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/tmpl/database.html Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,43 @@ + + + + + Datenbase + + + Datenbase Schema +
+

+ +

+
    +
  1. 1. Datenbase schema
  2. +
  3. 3. + Tables +
      +
    1. 2.${key+1} ${table.title}
    2. +
    +
  4. +
+
+ + +
+

Schema

+ +

Overview of used tables.

+
+
+

Tables

+
+

${table.title}

+

+ No description available. +

+
+
+ + diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/tmpl/impressum.html --- a/doc/tmpl/impressum.html Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ - - - - - Impressum - - - Impressum - -
-

Kontakt

-

- Netzguerilla.net Clerc Knauß Vollnhals GbR
- Manteuffelstraße 40
- 10997 Berlin -

-

- WWW: netzguerilla.net
-

-

- E-Mail: iro@netzguerilla.net
-

-

- Telefon: +49 30 69201075
-

-

Inhaltlich verantwortlich

-

- Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV bzw. §5 TMG sowie beauftragt mit dem Jugendschutz gemäß TMG, JMStV sowie GjSM:
- Sandro Knauß, Anschrift wie oben, iro@netzguerilla.net -

-
- - diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/tmpl/index.html --- a/doc/tmpl/index.html Wed Dec 21 22:07:48 2011 +0100 +++ b/doc/tmpl/index.html Thu Sep 27 17:15:46 2012 +0200 @@ -10,11 +10,59 @@ Iro
+

What it is all about?

- Iro ist toll, ist aber noch nicht fertig. + Iro is a non blocking server for sending a message to a bunsh of recipients. It can handle different types of message and can be extended easially. + It was initially written for sms backend of castorticker.de. There are diffrent backends are supported by Iro. + Iro helps you for these kinds of tasks: You want to send a message to some recipient over a different backend, than for other recipients. + Or you want to define fallback strategies: If one messages can't be send over this backend use another. + Because Iro also knows users, you can create bills for different clients.

- +
+

Supported Backends

+

+ A backend is a provider, that actually sends the message. +

    +
  • smtp with TLS and SSL
  • +
  • smstrade all diffrent routes are selectable
  • +
  • sipgate fax and sms
  • +
+

+ +
+
+

Installation

+

+ Just download ${code} see also installtion instruction under Installing Iro. +

+ +

Documentation

+

+ If you want just use iro see API Documentation. + For extending iro see the developer documenation. +

+
+
+

Files

+

+

+ You can also get the code from our repository, bitbucket, or github. +

+
+
+

Planned features for 1.0

+

+

    +
  • admin interface for creating and modifying users
  • +
  • heartbeat
  • +
+ For any further ideas, bugs or anything - send at iro@netzgerilla.net. +

+
+ diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/tmpl/new.html --- a/doc/tmpl/new.html Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,164 +0,0 @@ - - - - - api docs new - - - geplante API Dokumentation -
-

- -

-
    -
  1. 1. Einführung
  2. -
  3. 2. - Interfaces -
      -
    1. 2.1 XML-RPC
    2. -
    3. 2.1 SOAP
    4. -
    5. 2.2 XML
    6. -
    7. 2.3 JSON
    8. -
    9. 2.4 JSONP
    10. -
    11. 2.5 PHP
    12. -
    -
  4. -
  5. 3. - Methoden -
      -
    1. 3.${key+1} ${method.title}
    2. -
    -
  6. - -
-
-
-

Einführung

-

- Die Iro API enthält Funktion, die für den Massenversand nützlich sind. -

-
-
-

Interfaces

-

- - Die Iro API wird über verschiedene Interfaces bereit gestellt, die unterschiedlich angesprochen werden, aber das selbe tun. -

-
-

XML-RPC

-

- Interface-URI: http://localhost:8000/xmlrpc -

-

- Die aufgerufene Methode wird dabei im <methodName /> übergeben. -

-
-
-

SOAP

-

- Interface-URI: http://localhost:8000/soap -

-

- Die aufgerufene Methode wird dabei im <methodName /> übergeben. -

-
-
-

XML

-

- Interface-URI: http://localhost:8000/xml/<methode> -

-

- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben. -

-

- Parameter können via HTTP per GET oder POST im Format application/x-www-form-urlencoded übergeben werden. -

-

- Die Ausgabe erfolgt als XML Markup. -

-
-
-

JSON

-

- Interface-URI: http://localhost:8000/json/<methode> -

-

- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben. -

-

- Parameter können via HTTP per GET oder POST im Format application/x-www-form-urlencoded oder JSON-Objekt übergeben werden. -

-

- Die Ausgabe erfolgt als JSON-Objekt. -

-
-
-

JSONP

-

- Interface-URI: http://localhost:8000/jsonp/<methode>?callback=<callback> -

-

- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben. -

-

- Der Name für die Callback-Methode wird als Parameter Callback übergeben. -

-

- Parameter können via HTTP per GET im Format application/x-www-form-urlencoded übergeben werden. -

-

- Die Ausgabe erfolgt als Javascript-Funktionsaufruf mit einem JSON-Objekt als Parameter. -

-
-
-
-

Methoden

-
-

${method.title}

-

${method.name}${method.func_line}

-

- Diese Methode at bis jetzt noch keine Beschreibung. -

-
Parameter
-
ParameterTypBeschreibungparametertypedescription
- - - - - - - - - - - - - - -
ParameterTypBeschreibung
${arg.name}${arg.typ}${arg.description}
- -
Ausgabe
- - - - - - - - - - - - - - - -
ParameterTypBeschreibung
${arg.name}${arg.typ}${arg.description}
-
-
-
- - diff -r eb04ac3a8327 -r 3f4bdea2abbf doc/tmpl/old.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/tmpl/old.html Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,100 @@ + + + + + API docs old + + + Alte API Dokumentation +
+

+ +

+
    +
  1. 1. Einführung
  2. +
  3. 2. + Interfaces +
      +
    1. 2.1 XML-RPC
    2. +
    +
  4. +
  5. 3. + Methoden +
      +
    1. 3.${key+1} ${method.title}
    2. +
    +
  6. +
+
+
+

Einführung

+

+ Die Iro API enthält Funktion, die für den Massenversand nützlich sind. +

+
+
+

Interfaces

+

+ Die Iro API stellt zur Zeit nur ein Interfaces bereit. +

+
+

XML-RPC

+

+ Interface-URI: https://<benutzer>:<passwort>@localhost:8000 +

+

+ Die aufgerufene Methode wird dabei im <methodName /> übergeben. +

+
+
+
+

Methoden

+
+

${method.title}

+

${method.name}${method.func_line}

+

+ Diese Methode at bis jetzt noch keine Beschreibung. +

+
Parameter
+ + + + + + + + + + + + + + + +
ParameterTypBeschreibung
${arg.name}${arg.typ}${arg.description}
+ +
Ausgabe
+ + + + + + + + + + + + + + + +
ParameterTypBeschreibung
${arg.name}${arg.typ}${arg.description}
+
+
+
+ + diff -r eb04ac3a8327 -r 3f4bdea2abbf extras/iro.tac --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/extras/iro.tac Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,32 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.application.service import Application + +from iro import iro + +def get_application(): + app = Application("Iro") + cfg={"config":"iro.conf"} + iro.makeService(cfg).setServiceParent(app) + return app + +application = get_application() diff -r eb04ac3a8327 -r 3f4bdea2abbf fabfile.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/fabfile.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,102 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from fabric.api import local, run, env +from fabric.operations import put + +from iro import __version__ +import ngdatabase + +ngmodules = {"directory": "/home/hefee/hg/ngmod/init", + "version":ngdatabase.__version__} + +env.use_ssh_config = True + +def vbox(): + env.hosts = ['192.168.56.101'] + env.directory = '/home/hefee/iro/' + env.activate = 'source /home/hefee/iro/virtualenv/bin/activate' + env.deploy_user = 'hefee' + + +def hal(): + env.hosts = ['sandy@hal.netzguerilla.net'] + env.directory = "iro" + +def iro(): + env.hosts = ['hal'] + env.directory = '/home/sandy/virtualenv/iro2/' + env.activate = 'source /home/sandy/virtualenv/iro2/bin/activate' + env.deploy_user = 'sandy' + + +def prepare_deploy(): + local("python setup.py sdist") + +def virtualenv(cmd): + run("%s && %s" % (env.activate, cmd)) + +def deploy(): + prepare_deploy() + put("dist/iro-%s.tar.gz"%__version__,"%s/dist/"%env.directory) + put("%(directory)s/dist/ngmodules-%(version)s.tar.gz"%ngmodules,"%s/dist/"%env.directory) + virtualenv("pip install %s/dist/ngmodules-%s.tar.gz"%(env.directory,ngmodules["version"])) + virtualenv("pip install -e 'git+git://github.com/hefee/txjsonrpc.git#egg=txjsonrpc'") + virtualenv("pip uninstall -y iro") + virtualenv("pip install %s/dist/iro-%s.tar.gz"%(env.directory,__version__)) + +def prepare_tests(): + put("tests/*","%s/tests/"%env.directory) + +def startserver(): + run("cd %s && tar -xzf dist/iro-%s.tar.gz"%(env.directory,__version__)) + virtualenv("twistd -ny %s/iro-%s/extras/iro.tac"%(env.directory,__version__)) + +def testserver(): + prepare_tests() + virtualenv("python %s/tests/xmlrpc.py"%env.directory) + +def cleandoc(): + local("rm -rf _build/* web/dev/* web/*.html") + +def sphinxdoc(): + local("sphinx-build -b html -d _build/doctrees doc web/dev/") + +def createweb(): + prepare_deploy() + cleandoc() + local("mv dist/iro-%s.tar.gz web/files/"%__version__) + local("python createdoc.py") + sphinxdoc() + local("tar -czf web.tar.gz web") + +def pushweb(): + put("web.tar.gz","/tmp/") + run("tar -czf backup/iro.tar.gz iro/*") + run("tar -xzf /tmp/web.tar.gz") + run("rm -r iro/*") + run("mv web/* %s/"%env.directory) + run("rmdir web") + run("rm /tmp/web.tar.gz") + +def web(): + createweb() + pushweb() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro.conf.inst --- a/iro.conf.inst Wed Dec 21 22:07:48 2011 +0100 +++ b/iro.conf.inst Thu Sep 27 17:15:46 2012 +0200 @@ -1,37 +1,55 @@ -[geonet] -host = localhost -port = 25 -send_from = -user = -password = - - -[FAX_de] -host = localhost -port = 25 -send_from = -user = -password = - -[sipgate] -user = -password = - -[smtp] -host = localhost -port = 25 -send_from = -user = -password = -TLS=No -SSL=No - -[server] -key= -cert= - -[smstrade] -key= -route=basic -debug=1 -from= +[main] +# Connection URL to database +dburl = + +# Port under that twisted is running +port = + +[sipgate] +# One available provider typ. +typ = sipgate + +# Loginname for sipgate +username = + +# Password for sipgate +password = + +# price for one sms +# price_sms = 0.079 + +# price for one fax +# price_fax = 0.03 + +[smstrade] +# One available provider typ. +typ = smstrade + +# smstrade Gateway Key https://login.smstrade.de/index.php?gateway +key = + +[smtp] +# One available provider typ. +typ = smtp + +# Hostname of MTA +host = + +# Port of the MTA +# port = 25 + +# username to login into MTA. +# user = + +# password to login into MTA. +# password = + +# use SSL for connection to MTA +# SSL = False + +# use TLS for connection to MTA +# TLS = False + +# Emailaddress from which mail will be sended. +send_from = + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/.eric4project/iro.e4p --- a/iro/.eric4project/iro.e4p Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ - - - - - - - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/.eric4project/iro.e4q --- a/iro/.eric4project/iro.e4q Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/.eric4project/iro.e4t --- a/iro/.eric4project/iro.e4t Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/MyIro_daemon.inst --- a/iro/MyIro_daemon.inst Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -#! /bin/sh -NAME="Iro" -USER=sandy -HOMEDIR=/home/${USER} -DEAMON=${HOMEDIR}/bin/MyIro -DEAMON_OPTS="" -PID=${HOMEDIR}/var/run/$NAME.pid - -test -x $DEAMON || exit 0 - -. /lib/lsb/init-functions - -case "$1" in - start) - log_daemon_msg "Starting $NAME" $NAME - if start-stop-daemon --start --quiet --background --oknodo --pidfile $PID --make-pidfile --user ${USER} --group ${USER} --chdir ${HOMEDIR} --startas $DEAMON -- $DEAMON_OPTS; then - log_end_msg 0 - else - log_end_msg 1 - fi - ;; - stop) - log_daemon_msg "Stopping $NAME" $NAME - if start-stop-daemon --stop --quiet --oknodo --pidfile $PID; then - log_end_msg 0 - else - log_end_msg 1 - fi - ;; - - restart) - $0 stop - sleep 1 - $0 start - ;; - - status) - status_of_proc -p $PID $DEAMON $NAME && exit 0 || exit $? - ;; - - *) - log_action_msg "Usage: $0 {start|stop|restart|status}" - exit 1 -esac - -exit 1 diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/__init__.py --- a/iro/__init__.py Wed Dec 21 22:07:48 2011 +0100 +++ b/iro/__init__.py Thu Sep 27 17:15:46 2012 +0200 @@ -1,1 +1,22 @@ - +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__version__='2.0rc0' diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/acounting.py --- a/iro/acounting.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- - -from database import Database -import logging -logger=logging.getLogger("iro.acounting"); - -class Acounting(Database): - def __init__(self,id, connection): - Database.__init__(self,connection) - self.id=id - - def setId(self,id, user): - self.id=id - if user: - self.connect() - self.cursor.execute ("INSERT INTO %s (id,user) VALUES ('%s','%s')" % (self.connection['overview'], self.id, user)) - self.disconnect() - - - def getStatus(self): - self.connect() - self.cursor.execute ("SELECT status,tel FROM %s WHERE id='%s'" % (self.connection['table'], self.id)) - ret= self.cursor.fetchall() - self.disconnect() - return ret - - def addGood(self, good,disconnect=True): - if type(good) == list: - for i in good: - self.addGood(i) - if disconnect: - self.disconnect() - else: - self.connect() - self.cursor.execute("INSERT INTO %s (id,tel,status) VALUES('%s','%s','sended')" % (self.connection['table'], self.id, good)) - if disconnect: - self.disconnect() - - - def addFailed(self, failed,disconnect=True): - if type(failed) == list: - for i in failed: - self.addFailed(i,False) - if disconnect: - self.disconnect() - else: - self.connect() - self.cursor.execute ("INSERT INTO %s (id,tel,status) VALUES('%s','%s','failed')"%(self.connection['table'], self.id, failed)) - if disconnect: - self.disconnect() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/FAX_de.py --- a/iro/anbieter/FAX_de.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -from smtp import SMTP -from geonet import geonet -from telnumber import telnumber - -class FAX_de(geonet): - section="FAX_de" - default_conf="iro.conf" - max_recipients=50 - - def __init__(self): - self.smtp=SMTP(self.default_conf,self.section) - - def createMailaddress(produkt,number): - try: - tel=telnumber(number) - return "%s00%s%s@fax.de" %(produkt,tel.land,tel.number) - except: - return number diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/__init__.py --- a/iro/anbieter/__init__.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -from sipgate import sipgate -from geonet import geonet -from FAX_de import FAX_de -from smtp import SMTP -from smstrade import smstrade -import content diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/anbieter.py --- a/iro/anbieter/anbieter.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -class anbieter: - default_conf = '' # override this - section = 'anbieter' # override this - - def sendSMS(self,sms,recipients): - pass - def sendFAX(self,fax,recipients): - pass - def sendMail(self,mail,recipients): - pass - - def setJob(self, job): - self.job=job diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/content.py --- a/iro/anbieter/content.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -from email.mime.text import MIMEText -from email.header import Header - -class content: - def __init__(self,content): - self.content=content - - def sendto(self,anbieter,recipients): - pass - - def getContent(self): - return self.content - - def __eq__(self,other): - return self.content == other.content - - def __ne__(self,other): - return not self.__eq__(other) - -class SMS(content): - def __init__(self, cont): - content.__init__(self,cont) - - def sendto(self,anbieter,recipients): - anbieter.sendSMS(self,recipients) - - -class FAX(content): - def __init__(self,header,cont,attachments): - content.__init__(self,cont) - self.header=header - self.attachments=attachments - - def sendto(self,anbieter,recipients): - anbieter.sendFAX(self,recipients) - - def getAttachment(self,i): - return self.attachments[i] - - def __eq__(self,other): - if not content.__eq__(self,other): - return False - - if self.header != other.header: - return False - - if len(self.attachments) != len(other.attachments): - return False - - for i in range(len(self.attachments)): - if self.attachments[i] != other.attachments[i]: - return False - - return True - - - -class Mail(content): - def __init__(self, subject, body, frm): - con=MIMEText(body.encode("utf-8"), _charset='utf-8') - sub=Header(subject.encode('utf-8'), 'utf-8') - con['Subject']=sub - self.frm=frm - content.__init__(self, con) - - def sendto(self,anbieter,recipients): - anbieter.sendMail(self,recipients) - - def as_string(self): - return self.content.as_string() - - def getFrom(self): - return self.frm - - def __eq__(self,other): - if self.as_string() != other.as_string(): - return False - - if self.frm != other.frm: - return False - - return True diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/geonet.py --- a/iro/anbieter/geonet.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,61 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -from smtp import SMTP -from anbieter import anbieter -from telnumber import telnumber - -from email import Encoders -from email.MIMEMultipart import MIMEMultipart -from email.MIMEBase import MIMEBase -from email.MIMEText import MIMEText - -class Mail(MIMEMultipart): - def __init__(self,send_from,header,cont,attachments): - MIMEMultipart.__init__(self) - self['From'] = send_from - self['Subject']=header - self.attach(MIMEText(cont)) - for attachment in attachments: - part = MIMEBase('application', mimetypes.guess_type(attachment)[0]) - part.set_payload( open(attachment,"rb").read() ) - Encoders.encode_base64(part) - art.add_header('Content-Disposition', 'attachment; filename="%s"' % os.path.basename(f)) - self.attach(part) - -class geonet(anbieter): - section="geonet" - default_conf="iro.conf" - max_recipients=50 - - def __init__(self): - self.smtp=SMTP(self.default_conf,self.section) - - def createMailaddress(produkt,number): - try: - tel=telnum(number) - return "%s%s@%s.geonet.de" %(tel.land,tel.number,produkt) - except: - return number - - def sendSMS(self,sms,recipients): - recps=[] - for recpipient in recipients: - recps.append(self.createMailadress(recpipient)) - self.sendMail(Mail(self.smtp.send_from,sms.content,"",[]),recps) - - def sendFAX(self,fax,recipients): - recps=[] - for recpipient in recipients: - recps.append(self.createMailadress(recpipient)) - self.sendMail(Mail(self.smtp.send_from,"","",fax.attachments),recps) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/gsm0338.py --- a/iro/anbieter/gsm0338.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,192 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -import codecs -import _multibytecodec as mbc - -ENCODING_NAME = "gsm0338" - -decoding_map= { -unichr(0x00):unichr(0x0040), # COMMERCIAL AT -unichr(0x01):unichr(0x00A3), # POUND SIGN -unichr(0x02):unichr(0x0024), # DOLLAR SIGN -unichr(0x03):unichr(0x00A5), # YEN SIGN -unichr(0x04):unichr(0x00E8), # LATIN SMALL LETTER E WITH GRAVE -unichr(0x05):unichr(0x00E9), # LATIN SMALL LETTER E WITH ACUTE -unichr(0x06):unichr(0x00F9), # LATIN SMALL LETTER U WITH GRAVE -unichr(0x07):unichr(0x00EC), # LATIN SMALL LETTER I WITH GRAVE -unichr(0x08):unichr(0x00F2), # LATIN SMALL LETTER O WITH GRAVE -unichr(0x09):unichr(0x00E7), # LATIN SMALL LETTER C WITH CEDILLA -unichr(0x0A):unichr(0x000A), # LINE FEED -unichr(0x0B):unichr(0x00D8), # LATIN CAPITAL LETTER O WITH STROKE -unichr(0x0C):unichr(0x00F8), # LATIN SMALL LETTER O WITH STROKE -unichr(0x0D):unichr(0x000D), # CARRIAGE RETURN -unichr(0x0E):unichr(0x00C5), # LATIN CAPITAL LETTER A WITH RING ABOVE -unichr(0x0F):unichr(0x00E5), # LATIN SMALL LETTER A WITH RING ABOVE -unichr(0x10):unichr(0x0394), # GREEK CAPITAL LETTER DELTA -unichr(0x11):unichr(0x005F), # LOW LINE -unichr(0x12):unichr(0x03A6), # GREEK CAPITAL LETTER PHI -unichr(0x13):unichr(0x0393), # GREEK CAPITAL LETTER GAMMA -unichr(0x14):unichr(0x039B), # GREEK CAPITAL LETTER LAMDA -unichr(0x15):unichr(0x03A9), # GREEK CAPITAL LETTER OMEGA -unichr(0x16):unichr(0x03A0), # GREEK CAPITAL LETTER PI -unichr(0x17):unichr(0x03A8), # GREEK CAPITAL LETTER PSI -unichr(0x18):unichr(0x03A3), # GREEK CAPITAL LETTER SIGMA -unichr(0x19):unichr(0x0398), # GREEK CAPITAL LETTER THETA -unichr(0x1A):unichr(0x039E), # GREEK CAPITAL LETTER XI -unichr(0x1B):unichr(0x00A0), # ESCAPE TO EXTENSION TABLE (or displayed as NBSP, see note above) -unichr(0x1B0A):unichr(0x000C), # FORM FEED -unichr(0x1B14):unichr(0x005E), # CIRCUMFLEX ACCENT -unichr(0x1B28):unichr(0x007B), # LEFT CURLY BRACKET -unichr(0x1B29):unichr(0x007D), # RIGHT CURLY BRACKET -unichr(0x1B2F):unichr(0x005C), # REVERSE SOLIDUS -unichr(0x1B3C):unichr(0x005B), # LEFT SQUARE BRACKET -unichr(0x1B3D):unichr(0x007E), # TILDE -unichr(0x1B3E):unichr(0x005D), # RIGHT SQUARE BRACKET -unichr(0x1B40):unichr(0x007C), # VERTICAL LINE -unichr(0x1B65):unichr(0x20AC), # EURO SIGN -unichr(0x1C):unichr(0x00C6), # LATIN CAPITAL LETTER AE -unichr(0x1D):unichr(0x00E6), # LATIN SMALL LETTER AE -unichr(0x1E):unichr(0x00DF), # LATIN SMALL LETTER SHARP S (German) -unichr(0x1F):unichr(0x00C9), # LATIN CAPITAL LETTER E WITH ACUTE -unichr(0x20):unichr(0x0020), # SPACE -unichr(0x21):unichr(0x0021), # EXCLAMATION MARK -unichr(0x22):unichr(0x0022), # QUOTATION MARK -unichr(0x23):unichr(0x0023), # NUMBER SIGN -unichr(0x24):unichr(0x00A4), # CURRENCY SIGN -unichr(0x25):unichr(0x0025), # PERCENT SIGN -unichr(0x26):unichr(0x0026), # AMPERSAND -unichr(0x27):unichr(0x0027), # APOSTROPHE -unichr(0x28):unichr(0x0028), # LEFT PARENTHESIS -unichr(0x29):unichr(0x0029), # RIGHT PARENTHESIS -unichr(0x2A):unichr(0x002A), # ASTERISK -unichr(0x2B):unichr(0x002B), # PLUS SIGN -unichr(0x2C):unichr(0x002C), # COMMA -unichr(0x2D):unichr(0x002D), # HYPHEN-MINUS -unichr(0x2E):unichr(0x002E), # FULL STOP -unichr(0x2F):unichr(0x002F), # SOLIDUS -unichr(0x30):unichr(0x0030), # DIGIT ZERO -unichr(0x31):unichr(0x0031), # DIGIT ONE -unichr(0x32):unichr(0x0032), # DIGIT TWO -unichr(0x33):unichr(0x0033), # DIGIT THREE -unichr(0x34):unichr(0x0034), # DIGIT FOUR -unichr(0x35):unichr(0x0035), # DIGIT FIVE -unichr(0x36):unichr(0x0036), # DIGIT SIX -unichr(0x37):unichr(0x0037), # DIGIT SEVEN -unichr(0x38):unichr(0x0038), # DIGIT EIGHT -unichr(0x39):unichr(0x0039), # DIGIT NINE -unichr(0x3A):unichr(0x003A), # COLON -unichr(0x3B):unichr(0x003B), # SEMICOLON -unichr(0x3C):unichr(0x003C), # LESS-THAN SIGN -unichr(0x3D):unichr(0x003D), # EQUALS SIGN -unichr(0x3E):unichr(0x003E), # GREATER-THAN SIGN -unichr(0x3F):unichr(0x003F), # QUESTION MARK -unichr(0x40):unichr(0x00A1), # INVERTED EXCLAMATION MARK -unichr(0x41):unichr(0x0041), # LATIN CAPITAL LETTER A -unichr(0x42):unichr(0x0042), # LATIN CAPITAL LETTER B -unichr(0x43):unichr(0x0043), # LATIN CAPITAL LETTER C -unichr(0x44):unichr(0x0044), # LATIN CAPITAL LETTER D -unichr(0x45):unichr(0x0045), # LATIN CAPITAL LETTER E -unichr(0x46):unichr(0x0046), # LATIN CAPITAL LETTER F -unichr(0x47):unichr(0x0047), # LATIN CAPITAL LETTER G -unichr(0x48):unichr(0x0048), # LATIN CAPITAL LETTER H -unichr(0x49):unichr(0x0049), # LATIN CAPITAL LETTER I -unichr(0x4A):unichr(0x004A), # LATIN CAPITAL LETTER J -unichr(0x4B):unichr(0x004B), # LATIN CAPITAL LETTER K -unichr(0x4C):unichr(0x004C), # LATIN CAPITAL LETTER L -unichr(0x4D):unichr(0x004D), # LATIN CAPITAL LETTER M -unichr(0x4E):unichr(0x004E), # LATIN CAPITAL LETTER N -unichr(0x4F):unichr(0x004F), # LATIN CAPITAL LETTER O -unichr(0x50):unichr(0x0050), # LATIN CAPITAL LETTER P -unichr(0x51):unichr(0x0051), # LATIN CAPITAL LETTER Q -unichr(0x52):unichr(0x0052), # LATIN CAPITAL LETTER R -unichr(0x53):unichr(0x0053), # LATIN CAPITAL LETTER S -unichr(0x54):unichr(0x0054), # LATIN CAPITAL LETTER T -unichr(0x55):unichr(0x0055), # LATIN CAPITAL LETTER U -unichr(0x56):unichr(0x0056), # LATIN CAPITAL LETTER V -unichr(0x57):unichr(0x0057), # LATIN CAPITAL LETTER W -unichr(0x58):unichr(0x0058), # LATIN CAPITAL LETTER X -unichr(0x59):unichr(0x0059), # LATIN CAPITAL LETTER Y -unichr(0x5A):unichr(0x005A), # LATIN CAPITAL LETTER Z -unichr(0x5B):unichr(0x00C4), # LATIN CAPITAL LETTER A WITH DIAERESIS -unichr(0x5C):unichr(0x00D6), # LATIN CAPITAL LETTER O WITH DIAERESIS -unichr(0x5D):unichr(0x00D1), # LATIN CAPITAL LETTER N WITH TILDE -unichr(0x5E):unichr(0x00DC), # LATIN CAPITAL LETTER U WITH DIAERESIS -unichr(0x5F):unichr(0x00A7), # SECTION SIGN -unichr(0x60):unichr(0x00BF), # INVERTED QUESTION MARK -unichr(0x61):unichr(0x0061), # LATIN SMALL LETTER A -unichr(0x62):unichr(0x0062), # LATIN SMALL LETTER B -unichr(0x63):unichr(0x0063), # LATIN SMALL LETTER C -unichr(0x64):unichr(0x0064), # LATIN SMALL LETTER D -unichr(0x65):unichr(0x0065), # LATIN SMALL LETTER E -unichr(0x66):unichr(0x0066), # LATIN SMALL LETTER F -unichr(0x67):unichr(0x0067), # LATIN SMALL LETTER G -unichr(0x68):unichr(0x0068), # LATIN SMALL LETTER H -unichr(0x69):unichr(0x0069), # LATIN SMALL LETTER I -unichr(0x6A):unichr(0x006A), # LATIN SMALL LETTER J -unichr(0x6B):unichr(0x006B), # LATIN SMALL LETTER K -unichr(0x6C):unichr(0x006C), # LATIN SMALL LETTER L -unichr(0x6D):unichr(0x006D), # LATIN SMALL LETTER M -unichr(0x6E):unichr(0x006E), # LATIN SMALL LETTER N -unichr(0x6F):unichr(0x006F), # LATIN SMALL LETTER O -unichr(0x70):unichr(0x0070), # LATIN SMALL LETTER P -unichr(0x71):unichr(0x0071), # LATIN SMALL LETTER Q -unichr(0x72):unichr(0x0072), # LATIN SMALL LETTER R -unichr(0x73):unichr(0x0073), # LATIN SMALL LETTER S -unichr(0x74):unichr(0x0074), # LATIN SMALL LETTER T -unichr(0x75):unichr(0x0075), # LATIN SMALL LETTER U -unichr(0x76):unichr(0x0076), # LATIN SMALL LETTER V -unichr(0x77):unichr(0x0077), # LATIN SMALL LETTER W -unichr(0x78):unichr(0x0078), # LATIN SMALL LETTER X -unichr(0x79):unichr(0x0079), # LATIN SMALL LETTER Y -unichr(0x7A):unichr(0x007A), # LATIN SMALL LETTER Z -unichr(0x7B):unichr(0x00E4), # LATIN SMALL LETTER A WITH DIAERESIS -unichr(0x7C):unichr(0x00F6), # LATIN SMALL LETTER O WITH DIAERESIS -unichr(0x7D):unichr(0x00F1), # LATIN SMALL LETTER N WITH TILDE -unichr(0x7E):unichr(0x00FC), # LATIN SMALL LETTER U WITH DIAERESIS -unichr(0x7F):unichr(0x00E0), # LATIN SMALL LETTER A WITH GRAVE -} - -encoding_map=dict([(v,k) for (k,v) in decoding_map.items()]) - -class Codec(codecs.Codec): - def encode(self,input,errors='strict'): - ret="" - for i in input: - ret+=encoding_map[i] - return (ret,len(ret)) - def decode(self,input,errors='strict'): - ret="" - for i in input: - ret+=decoding_map[i] - return (ret,len(ret)) - - - -class StreamWriter(Codec,mbc.MultibyteStreamWriter,codecs.StreamWriter): - pass - -class StreamReader(Codec,mbc.MultibyteStreamReader,codecs.StreamReader): - pass - -### encodings module API - -def getregentry(): - return (Codec().encode,Codec().decode,StreamReader,StreamWriter) - - -def gsm_search(encoding): - if not encoding == ENCODING_NAME: - return - return getregentry() - -# Register our codec when we load the module -codecs.register(gsm_search) - -if __name__ == "__main__": - text = "€öäüß" - text2 = unicode(text,"utf-8").encode("gsm0338") - assert(text2==u"\u1B65\x7C\x7B\x7E\x1E") - text="" - text2 = unicode(text,"utf-8").encode("gsm0338") - assert(text==text2) - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/sipgate.py --- a/iro/anbieter/sipgate.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - - -from anbieter import anbieter -from telnumber import telnumber, NotATelNumber -import ConfigParser -import xmlrpclib -import logging -logger=logging.getLogger("sipgate") - -class NoValidStatusCode(Exception): - def __init__(self, value): - self.value = value - - def __str__(self): - return repr(self.value) - -class sipgate(anbieter): - """ - s. auch http://www.tuxad.com/sipgate.html - und http://lists.sipgate.net/pipermail/sipgate-developers/2007-September/000016.html - """ - section="sipgate" - url="https://%s:%s@samurai.sipgate.net/RPC2" - def __init__(self,user="",password=""): - self.user=user - self.password=password - - def read_basic_config(self,filenames): - """Read basic options from the config file""" - cp = ConfigParser.ConfigParser() - cp.read(filenames) - self.user=cp.get(self.section, 'user') - self.password=cp.get(self.section, 'password') - - def sendSMS(self,sms,recipients): - """send SMS with $sms to $recipients""" - logger.debug('sipgate.sendSMS(%s,%s)'%(sms.getContent(), str(recipients))) - args={ - "TOS" : "text", - "Content" : sms.getContent() - } - self.__send(args,recipients) - - def sendFAX(self,fax,recipients): - """send the PDF file $fax to $recipients""" - logger.debug('sipgate.sendFAX(%s,%s)'%(fax, str(recipients))) - args={ - "TOS" : "fax", - "Content" : xmlrpclib.Binary(fax.getAttachment(0)), - } - self.__send(args,recipients) - - def __connect(self): - """connect to sipgate XMLRPC Server""" - logger.debug("sipgate.__connect()-"+self.url%(self.user,"XXXXXXXX")) - - self.serv=xmlrpclib.ServerProxy(self.url%(self.user,self.password)) - self.samurai=self.serv.samurai - - args_identify = { - "ClientName" : "anbieter.py", - "ClientVersion" : "V1.0", - "ClientVendor" : "Sandro Knauss" - } - self.__send_method(self.samurai.ClientIdentify, args_identify) - return self.serv - - def __send_method(self, func, args=None): - """execute $func and test weather if the func ran successfully or not""" - logger.debug("sipgate.__send_method(func,%s)"%( args)) - - if args==None: - xmlrpc_result = func() - else: - xmlrpc_result = func(args) - if xmlrpc_result['StatusCode'] != 200: - raise NoValidStatusCode("There was an error during identification to the server! %d %s"% (xmlrpc_result['StatusCode'], xmlrpc_result['StatusString'])) - logger.debug("sipgate.__send_method(..):ok"); - return xmlrpc_result - - def __send(self,args,recipients): - """main sending method - sending the args to $recipients""" - sended=[] - - serv=self.__connect() - logger.debug('sipgate.__send(%s,%s)'%(args, recipients)) - for recipient in recipients: - try: - tel = telnumber(recipient) - - if tel in sended: #only send message once per recipient - continue - sended.append(tel) - - args["RemoteUri"]="sip:%s%s@sipgate.net"%(tel.land,tel.number) - self.__send_method(serv.samurai.SessionInitiate, args) - self.updateStatus(arranged=recipient) - - except (NotATelNumber, NoValidStatusCode): - self.updateStatus(failed=recipient) - - self.__disconnect() - - def updateStatus(self, arranged=None, failed=None): - """is a function that is called, if a new SMS/FAX was send - -arranged is non None, if SMS/FAX was sended successfully - -failed is non None, if SMS/FAX sending failed - the content will be the recipent""" - pass - - def BalanceGet(self): - """get the balance of sipgate""" - self.__connect() - ret = self.__send_method(self.samurai.BalanceGet ) - self.__disconnect() - return ret['CurrentBalance'] - - def getNewMessages(self): - """get new messages from inbox""" - self.__connect() - tmp = self.__send_method(self.samurai.UmSummaryGet) - self.__disconnect() - tmp=tmp['UmSummary'] - ret={} - for entry in tmp: - ret[entry['TOS']]={'read':entry["Read"],'unread':entry["Unread"]} - return ret - - def getRecommendedInterval(self,methods): - """how often you can call one $methods""" - self.__connect() - args = {"MethodList" : methods } - tmp = self.__send_method(self.samurai.RecommendedIntervalGet, args) - self. __disconnect() - ret={} - for entry in tmp['IntervalList']: - ret[entry['MethodName']]=entry['RecommendedInterval'] - return ret - - def __disconnect(self): - """disconnect xmlrpc client""" - logger.debug('sipgate.__disconnect()') - self.samurai=None - self.serv=None - self.xmlrpc=None diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/smstrade.py --- a/iro/anbieter/smstrade.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Georg Bischoff - -#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 3 of the License, or 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, see . - - -from anbieter import anbieter -from sipgate import NoValidStatusCode -from telnumber import telnumber, NotATelNumber -import ConfigParser -import urllib, httplib -from httplib import socket - -import logging -logger=logging.getLogger("smstrade") - -class UnknownStatusCode(Exception): - def __init__(self,code): - self.code=code - - def __str__(self): - return "StatusCode %i is unknown"%self.code - - -class StatusCode: - statusCodes = {10 : "Empfaengernummer nicht korrekt", - 20 : "Absenderkennung nicht korrekt", - 30 : "Nachrichtentext nicht korrekt", - 31 : "Messagetyp nicht korrekt", - 40 : "SMS Route nicht korrekt", - 50 : "Identifikation fehlgeschlagen", - 60 : "nicht genuegend Guthaben", - 70 : "Netz wird von Route nicht abgedeckt", - 71 : "Feature nicht ueber diese Route moeglich", - 80 : "Uebergabe an SMS-C fehlgeschlagen", - 90 : "Versand nicht moeglich", - 100 : "SMS wurde versendet", - 999 : "SMS wird zeitversetzt verschickt"} - - def __init__(self,code): - if code in self.statusCodes.keys(): - self.code=code - else: - raise UnknownStatusCode(code) - - def __str__(self): - try: - return self.statusCodes[self.code] - except IndexError: - raise UnknownStatusCode(self.code) - - def __int__(self): - if not self.code in self.statusCodes.keys(): - raise UnknownStatusCode(self.code) - return self.code - - - - -class smstrade(anbieter): - """ - s. auch http://kundencenter.smstrade.de/sites/smstrade.de.kundencenter/__pdf/SMS-Gateway_HTTP_API_v2.pdf - """ - section="smstrade" - url="https://gateway.smstrade.de" - def __init__(self): - self.domain = "smstrade.de" # website of the sms service - self.gateway = "gateway.smstrade.de" # gateway where the request will be sent - self.gatewayPort = 80 # port of the gateway - self.script = "/" # full path to the script that will handle the request - self.method = "POST" # method that will be used. Currently only POST is supported - - def read_basic_config(self, filenames): - """Read basic options from the config file""" - cp = ConfigParser.ConfigParser() - cp.read(filenames) - self.key=cp.get(self.section, 'key') - self.route=cp.get(self.section, 'route') - self.from_=cp.get(self.section, 'from') - self.debug=cp.get(self.section, 'debug') - - def sendSMS(self,sms,recipients): - """send SMS with $sms to $recipients""" - logger.debug('smstrade.sendSMS(%s,%s)'%(sms, str(recipients))) - sended = [] - route = unicode(self.route) - message = sms.content - timestamp = None - for recipient in recipients: - try: - tel = telnumber(recipient) - if tel in sended: #only send message once per recipient - continue - sended.append(tel) - to ='00'+tel.land+tel.number - if tel.land == '49': - route=unicode("basic") - else: - route=unicode("economy") - smsSendStatus = self.__send(route, to, message, timestamp) - logger.info('smstrade._send(...)=%i(%s)'%(int(smsSendStatus),str(smsSendStatus))) - if int(smsSendStatus) in(100, 999): - self.updateStatus(arranged=recipient) - else: - self.updateStatus(failed=recipient) - except (NotATelNumber,NoValidStatusCode,InternetConnectionError): - self.updateStatus(failed=recipient) - - def __send(self, route, to, message, timestamp=None): - """ This function is the main part of the request to the sms service. - The function has to return a unicode formated string that will represent the answer of the sms service - to the request.""" - logger.debug('smstrade._send(%s,%s,%s,%s)'%( route, to, message, timestamp)) - parameters= {"key": self.key, - "route": route, - "to": to, - "message": message, - "charset":"utf-8", - "debug": self.debug, - } - - if self.from_ is not None: - parameters["from"] = self.from_ - - if timestamp is not None: - parameters["senddate"] = unicode(timestamp) - - parameters["concat_sms"] = "1" if len(message) > 160 else "0" - params = "&".join( ["%s=%s" % (urllib.quote(k),urllib.quote(v.encode("utf-8"))) for (k, v) in parameters.items()]) - logger.debug('smstrade._send-parameters:%s\n\t->%s'%(str(parameters), str(params)) ) - headers = {"Content-type": "application/x-www-form-urlencoded", - "Accept": "text/plain"} - conn = httplib.HTTPConnection("%s:%i" % (self.gateway, self.gatewayPort)) - try: - conn.request(self.method, self.script, params, headers) - response = conn.getresponse() - data = response.read() - except socket.gaierror: - raise InternetConnectionError("%s:%i" % (self.gateway, self.gatewayPort)) - finally: - conn.close() - - try: - return StatusCode(int(data)) - except UnknownStatusCode: - # this happens if the sms will be send delayed - return StatusCode(999) - - def updateStatus(self, arranged=None, failed=None): - """is a function that is called, if a new SMS/FAX was send - -arranged is non None, if SMS/FAX was sended successfully - -failed is non None, if SMS/FAX sending failed - the content will be the recipent""" - pass - -class InternetConnectionError(Exception): - def __init__(self, url): - self.url = url - - def __str__(self): - return "InternetConnectionError: It is not possible to open 'http://%s'. Please check your connection to the Internet!" % self.url diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/smtp.py --- a/iro/anbieter/smtp.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,107 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -class anbieter: - default_conf = '' # override this -import smtplib -import ConfigParser -import logging -import copy - -logger=logging.getLogger("SMTP") - -class SMTP(): - def __init__(self,config_filenames=None,section="smtp"): - self.config_filenames=config_filenames - self.section=section - self.bStart=False - self.bTLS=False - self.bSSL=False - self.max_recipients=1 - - - def read_basic_config(self,config_filenames=None): - """Read basic options from the config file""" - if not (config_filenames is None): - self.config_filenames=config_filenames - - cp = ConfigParser.ConfigParser() - cp.read(self.config_filenames) - self.config_parser = cp - self.send_from=cp.get(self.section, 'send_from') - self.host=cp.get(self.section, 'host') - self.port=cp.get(self.section, 'port') - self.user=cp.get(self.section, 'user') - self.pw=cp.get(self.section, 'password') - - try: - self.bTLS=cp.getboolean(self.section, 'TLS') - except ValueError: - self.bTLS=False - - try: - self.bSSL=cp.getboolean(self.section, 'SSL') - except ValueError: - self.bSSL=False - - def prepareSMTP(self): - if self.bSSL: - self.smtp = smtplib.SMTP_SSL(self.host,self.port) - else: - self.smtp = smtplib.SMTP(self.host,self.port) - - if self.bTLS: - self.smtp.starttls() - - if not self.user == "": - self.smtp.login(self.user,self.pw) - - self.bStart=True - - def sendMail(self,mail,recipients): - logger.debug('SMTP.sendMail(%s,%s)'%(mail, str(recipients))) - if not self.bStart: - self.prepareSMTP() - - frm=self.send_from - - if mail.getFrom(): - frm = mail.getFrom() - - mail.content['From'] = frm - - - while len(recipients) > 0: - tmp_recipients=recipients[:self.max_recipients] - tmpmail=copy.deepcopy(mail) - tmpmail.content['To']=", ".join(tmp_recipients) - logger.debug('self.smtp.sendmail(%s,%s,%s)'%(frm, str(tmp_recipients), tmpmail.as_string())) - self.smtp.sendmail(frm, tmp_recipients, tmpmail.as_string()) - self.updateStatus( arranged=tmp_recipients) - recipients = recipients[self.max_recipients:] - - self.shutdownSMTP() - - - def updateStatus(self, arranged=None, failed=None): - """is a function that is called, if a new SMS/FAX was send - -arranged is non None, if SMS/FAX was sended successfully - -failed is non None, if SMS/FAX sending failed - the content will be the recipent""" - pass - - def shutdownSMTP(self): - self.smtp.quit() - self.bStart=False - - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/telnumber.py --- a/iro/anbieter/telnumber.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -class anbieter: - default_conf = '' # override this -import re - -class NotATelNumber(Exception): - def __init__(self, number): - self.number= number - - def __str__(self): - return ("This is not a telefonnumber:", selfnumber) - -class telnumber: - re_telnum=re.compile(r'^\s*(\+)?([0-9\s\-/\(\)])+\s*$') - re_land=re.compile(r'^\s*(\+|00)(?P[1-9]{2})') - re_number=re.compile(r'[^0-9]') - std_land="49" - - def __init__(self,number=None): - if not(number is None): - self.createNumber(number) - - def createNumber(self, number): - - if not self.re_telnum.match(number): - raise NotATelNumber(number) - - - self.land=self.std_land - land=self.re_land.match(number) - - if not(land is None): - self.land=land.group("land") - number=number[land.end("land"):] - - number=self.re_number.sub('',number) - - if number[0]=="0": - number=number[1:] - - self.number = number - - def __eq__(self, y): - return ((self.number == y.number) and ( self.land == y.land)) - - def __ne__(self, y): - return not self.__eq__(y) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/anbieter/tests/testTelnumber.py --- a/iro/anbieter/tests/testTelnumber.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -class anbieter: - default_conf = '' # override this - -import unittest -import iro.anbieter.telnumber as tn - -class TestTelnumber(unittest.TestCase): - def equalNumber(self, tel1, tel2): - self.assertEqual(tel1.number, tel2.number) - self.assertEqual(tel1.land, tel2.land) - - def testWrongNumber(self): - telnum=tn.telnumber() - self.assertRaises(tn.NotATelNumber, telnum.createNumber, "hallo") - self.assertRaises(tn.NotATelNumber, telnum.createNumber, "0?242") - - - def testNumber(self): - telnum=tn.telnumber("0551-28293640") - telnum2=tn.telnumber("+49551/28293640") - telnum3=tn.telnumber("00495512829364-0") - telnum4=tn.telnumber("+49(0)551-28293640") - - self.assertEqual(telnum.land, "49") - self.assertEqual(telnum.number, "55128293640") - - self.equalNumber(telnum, telnum2) - self.equalNumber(telnum, telnum3) - self.equalNumber(telnum, telnum4) - - def testEqual(self): - telnum=tn.telnumber("0551-28293640") - telnum2=tn.telnumber("+49551/28293640") - li=[] - self.assertEqual(telnum == telnum2, True) - self.assertEqual(telnum <> telnum2, False) - self.assertEqual(telnum, telnum2) - self.assertEqual(telnum in li,False) - li.append(telnum) - self.assertEqual(telnum in li,True) - self.assertEqual(telnum in li,True) - -if __name__ == "__main__": - unittest.main() - - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/config.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,219 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.python import log + +from ConfigParser import ConfigParser +import signal +from functools import partial +try: + from collections import OrderedDict +except ImportError: + from ordereddict import OrderedDict + +from validate import vInteger +from error import NeededOption + +class MyConfigParser(ConfigParser): + """Configparser that also validate configfile. + + It is possile to restiger function, that are called, when config file is reloaded + """ + def __init__(self): + ConfigParser.__init__(self) + self.reloadList=[] + + def read(self,files): + """reads an validate configuration file""" + from offer import getProvider + r = ConfigParser.read(self, files) + for s in self.sections(): + if s == "main": + main.validate(self.items(s)) + else: + getProvider("tmp", self.get(s,'typ'), self.items(s)) + return r + + def reload_(self): + """run all registered function.""" + for f in self.reloadList: + f() + + def registerReload(self, func): + """adds **func** to reloadList. + + func ist called with no arguments. + """ + self.reloadList.append(func) + +class Option(): + """One Option in the configuration file""" + def __init__(self, validate, long="", help="", must=False, default=None): + """ + :param func validate: a validate function, it has to return the value, if valid and raise an error if not. + :param string long: long description + :param string help: the help text + :param boolean must: Is this option nessasary + :param default: default value + """ + self.validate = validate + self.long=long + self.help = help + self.must = must + self.default = default + +class Config: + """Base class for all classes, that uses option from configfile. + + If one option is valid, the attribute is created with the value of the validate function. + """ + def __init__(self, name, options=None): + """ + :param string name: section name. + :param `collections.OrderedDict` options: Orderd Dict of the configuration options (see :attr:`options`) + """ + self.name = name + + self.options = options + """Options :class:`collections.OrderedDict` for Options used in configuration file (see :class:`iro.config.Option`). Ordering of configuration fields are done by :attr:`order`. + + Sample:: + + OrderedDict([ + ("dburl",Option(lambda x,y:x,long="Connection URL to database",must=True)), + ("port",Option(partial(vInteger,minv=0),long="Port under that twisted is running",must=True)), + ]) + + A child class typically use update to add more options. + """ + + self._init = True + """indecates, if the config is loaded with config values.""" + + + def _read(self, cfg, write=False): + """Test or set configuration options. + + :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``. + :param boolean write: test or set the option to actual object. + + :raises: :exc:`iro.error.NeededOption` + """ + c = dict(cfg) + + for o in self.options: + option = self.options[o] + try: + value = option.validate(c[o],o) + if write: + self._init = False + setattr(self,o,value) + except KeyError: + if option.must: + raise NeededOption(self.name, o) + elif write and option.default is not None: + setattr(self, o, option.default) + + def validate(self, cfg): + """Validate configuration. + + :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``. + """ + self._read(cfg, False) + + def load(self, cfg): + """Loads configuration into object. + + :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``. + """ + self._read(cfg, True) + + def same(self, other): + """returns ``True``, if the options of other object are the same""" + for o in self.options: + if getattr(self,o) != getattr(other,o): + return False + else: + return True + + def sampleConf(self): + """returns a sample Configuration section. + + This function also adds the long help text to the sample section. + + :return: a list of lines + """ + ret=[] + for o in self.options: + opt=self.options[o] + if opt.long: + ret.append("# "+opt.long) + if opt.must: + s= "%s = "%o + if opt.default is not None: + s += str(opt.default) + ret.append(s) + else: + ret.append("# %s = %s"%(o,opt.default)) + ret.append("") + + return ["[%s]"%self.name,]+ret + +def readConfig(): + """Read the configuration and update all registered object (see :meth:`MyConfigParser.reload_`).""" + log.msg("Reading configs.") + configParser.read(confFiles) + configParser.reload_() + if main._init: + main.load(configParser.items("main")) + else: + m = Config("main", main_options) + m.load(configParser.items("main")) + if not main.same(m): + raise Exception("Main options can't be reloaded, please restart your Application.") + +def init(): + """Load the main options.""" + configParser.read(confFiles) + main.load(configParser.items("main")) + +def registerSignal(): + '''register readConfig to SIGUSR2''' + def rC(signal, frame): + readConfig() + + signal.signal(signal.SIGUSR2,rC) + +configParser = MyConfigParser() +"""configParser to get configuration.""" + +confFiles=["~/iro.conf","/etc/iro/iro.conf"] +"""Configfile list """ + +main_options = OrderedDict([ + ("dburl",Option(lambda x,y:x,long="Connection URL to database",must=True)), + ("port",Option(partial(vInteger,minv=0),long="Port under that twisted is running",must=True)), + ]) + +"options for main section" + +main = Config("main", main_options) +"""Main config options""" diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/controller/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/controller/__init__.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,20 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/controller/pool.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/controller/pool.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,63 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.python.threadpool import ThreadPool +from twisted.internet import threads + +class Pool: + """wrapper class to handles a twisted threadpool""" + def __init__(self,name,maxthreads): + """ + :param string name: name of the threadpool + :param integer maxthreads: how many thread a allowed at maximum + """ + self.maxthreads = maxthreads + self.pool = ThreadPool(minthreads=1, maxthreads=maxthreads, name=name) + self.reactor = None + + def start(self, reactor): + """stats the pool and adds the pool.stop function to the stop procedure. + + :param reactor: a valid twisted reactor + """ + self.pool.start() + self.reactor = reactor + self.reactor.addSystemEventTrigger('before', 'shutdown', self.pool.stop) + + def run(self,f,*args,**kwargs): + """run a function in Twisted's thread pool""" + return threads.deferToThreadPool(self.reactor, self.pool, f, *args, **kwargs) + +taskPool = Pool('task',5) +"""taskpool to handle sending data""" + +dbPool = Pool('database',5) +"""pool to handle database connection via sqlalchemy""" + +pools=[taskPool,dbPool] +"""all available pools""" + +def startPool(reactor): + '''run start function for all items in :attr:`pools`''' + for pool in pools: + pool.start(reactor) + +__all__=["Pool", "startPool", "dbPool", "taskPool", "pools"] diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/controller/task.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/controller/task.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,108 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from functools import partial + +from twisted.python import log +from twisted.internet.defer import inlineCallbacks, returnValue, maybeDeferred, Deferred + +from ..error import NoRouteForTask, RejectRecipient + +from ..model.offer import offers +from ..model.job import exJobs + +from .pool import taskPool + +class Task: + '''A Task is one message to one recipient and is a part of one :class:`iro.model.job.ExJob`. + ''' + def __init__(self, recipient, job): + """ + :param recipient: a recipient + :param `iro.model.job.ExJob` job: connected job + """ + self.recipient = recipient + self.job = job + self.status = None + self.error = False + + def setStatus(self,status): + """callback, to set status of task""" + self.status = status + return status + + def setError(self, error): + """errback to set error of task""" + self.status = error + self.error = True + return error + + def start(self): + """Starting to send message to recipient. + + :return: a defer, that is fired, when message is sended successfully over any offer. + """ + self.d = Deferred() + self.d.addCallback(self.setStatus) + self.d.addCallback(partial(self.job.setStatus,self)) + self.d.addErrback(self.setError) + self.d.addErrback(partial(self.job.setError,self)) + taskPool.run(self._run) + return self.d + + def _run(self): + """sends the message to recipient, tries all possible offers.""" + os= iter(self.job.offers) + def n(): + try: + offer = os.next() + d = maybeDeferred(offers[offer],self.recipient,self.job.message) + d.addCallback(self.d.callback) + d.addErrback(addErr,offer) + d.addErrback(self.d.errback) + return d + except StopIteration: + self.d.errback(NoRouteForTask()) + + def addErr(failure, offer): + if not isinstance(failure.value, RejectRecipient): + log.err(_why="Job(%s): Send to '%s' failed via '%s'"%(self.job.dbjob, self.recipient, offer),_stuff=failure) + n() + + n() + + +@inlineCallbacks +def createJob(user,recipients, msg, offers, info=None): + """Creates a :class:`iro.model.job.ExJob` and start for all recipients one task. + + :param `iro.model.schema.User` user: the sender + :param `iro.model.message.Message` msg: the message + :param list offers: a list of possible offer and provider names, to try to send the message over. The first entry will be tried first. + :param string info: a bill group name + :return: the new :class:`iro.model.job.ExJob` object. + """ + job = yield exJobs.create(user, recipients, msg, offers, info) + for r in recipients: + task = Task(r,job) + job.addTask(task) + task.start() + returnValue(job) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/controller/viewinterface.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/controller/viewinterface.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,223 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- +from ..model.decorators import vUser, vRoute, dbdefer, vTyp +from ..model.message import SMS, Fax, Mail + +from ..validate import validate, vBool, vTel, vEmail, vInteger + +from .task import createJob + +class Interface(object): + '''Interface for views.''' + + @validate(kwd="detailed", func=vBool, need=False) + @validate(kwd="id", func=vInteger, minv=0, need=False, none_allowed=True) + @vUser + @dbdefer + def status(self, session, user, id=None, detailed=False): + '''Returns the status of one or more jobs. + + :param string user: apikey of a user + :param integer id: one job id + :param `boolean` detailed: return more details about the status + + :return dict: + - `key` -- is the job id + - [`key`][**'status'**] -- status of the job + + .. warning:: detailed is not used yet. + + >>> status(APIKEY) + {"1": {"status":"sended"}, + "2": {"status":"error"}, + "10": {"status":"sending"}} + + >>> status(APIKEY,10) + {"10": {"status":"sending"}} + ''' + user = session.merge(user) + ret={} + if not id: + for job in user.jobs: + ret[str(job.id)]={"status":job.status} + else: + ret[str(id)]={"status":user.job(id).status} + + return ret + + @validate(kwd="recipients",func=vTel) + @vUser + @validate(kwd="route", func=vRoute, typ="sms") + def sms(self, user, message, recipients, route="default", info=""): + '''Send a sms. + + :param string user: apikey of a user + :param string message: message + :param list recipients: a list of telefon numbers (use ITU-T E.123) + :param route: route to use to send, or a list of routes as fallback + :type route: string|list + :param string info: a name, to combine different jobs to one billing group + + :return integer: the job id + ''' + d = createJob(user, recipients, SMS(message), route, info) + def ret(job): + return job.dbjob + d.addCallback(ret) + return d + + @validate(kwd="recipients",func=vTel) + @vUser + @validate(kwd="route",func=vRoute, typ="fax") + def fax(self, user, subject, fax, recipients, route="default", info=""): + '''Send a fax. + + :param string user: apikey of a user + :param string subject: subject + :param string fax: content (base64 encoded) + :param list recipients: a list of telefon numbers (use ITU-T E.123) + :param route: route to use to send, or a list of routes as fallback + :type route: string|list + :param string info: a name, to combine different jobs to one billing group + + :return integer: the job id + ''' + d = createJob(user, recipients, Fax(subject, fax), route, info) + def ret(job): + return job.dbjob + d.addCallback(ret) + return d + + @validate(kwd="recipients",func=vEmail, allowString=False) + @validate(kwd="frm",func=vEmail, need=False, allowList=False) + @vUser + @validate(kwd="route",func=vRoute, typ="mail") + def mail(self, user, subject, body, recipients, frm=None, route="default", info=""): + '''Send a mail. + + :param string user: apikey of a user + :param string subject: subject + :param string body: mail body + :param list recipients: a list of email addresses + :param route: route to use to send, or a list of routes as fallback + :type route: string|list + :param string info: a name, to combine different jobs to one billing group + :param string frm: sender mail address + :return integer: the job id + ''' + d = createJob(user, recipients, Mail(subject, body, frm), route, info) + def ret(job): + return job.dbjob + d.addCallback(ret) + return d + + @validate(kwd="typ", func=vTyp) + @vUser + @dbdefer + def routes(self, session, user, typ): + '''Returns a list of all possible offernames. + + :param string user: apikey of a user + :param string typ: a typ of message -- one of in this list ["sms","fax","mail"] + + :return list: a list of all possible offer names for a typ + ''' + user = session.merge(user) + offers = user.routes(typ) + return [u[0] for u in offers] + + @validate(kwd="typ", func=vTyp) + @vUser + @dbdefer + def defaultRoute(self, session, user, typ): + '''Returns all default offernames. + + :param string user: apikey of a user + :param string typ: a typ of message -- one of in this list ["sms","fax","mail"] + + :return list: a list of all possible offer names for a typ + ''' + user = session.merge(user) + offers = user.routes(typ, default=True) + return [u[0] for u in offers] + + @vUser + @dbdefer + def bill(self, session, user): + '''Returns the bill, of not paid messages. + + :param string user: apikey of a user + + :return dict: + - `route` -- one offer name ; **"total"** complete sum + - [`route`][`info`][**anz**] -- Number of sended messages in one billing group + - [`route`][`info`][**price**] -- Price for one billing group + - [`route` | **total**][**anz**] -- Number of sended messages for one offer + - [`route` | **total**][**price**] -- Price for one offer + + >>> bill(APIKEY) + {"route1": {"info1":{"anz":1,"price":2.00}, + "info2":{"anz":2,"price":5.00}, + "anz":3,"price":7.00}, + "route2": {"info1":{"anz":3, "price":1.00}, + "info3":{"anz":4, "price":8.00}, + "anz":7, "price":9.00}, + "total": {"anz":10, "price":16.00} + } + + ''' + ret={'total':{'price':0, 'anz':0}} + user=session.merge(user) + for route in user.rights: + n=route.offer_name + ret[n]={'price':0, 'anz':0, 'info':{}} + for bill in route.bill: + ret[n]['info'][bill.info]={'price':float(bill.price),'anz':bill.anz} + ret[n]['price'] += bill.price + ret[n]['anz'] += bill.anz + ret['total']['price'] += ret[n]['price'] + ret['total']['anz'] += ret[n]['anz'] + ret[n]['price'] = float(ret[n]['price']) + + ret['total']['price'] = float(ret['total']['price']) + return ret + + @validate(kwd="recipients",func=vTel) + def telnumber(self,recipients): + '''Return True, if all telnumbers a vaild. + + :param list recipients: a list of telnumbers (use ITU-T E.123) + + :return boolean: True -- all numbers are valid + ''' + return True + + @validate(kwd="recipients",func=vEmail) + def email(self,recipients): + '''Return True, if all mailadresses a valid. + + :param list recipients: a list of mailadresses + + :return boolean: True -- all addresses are valid + ''' + return True diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/database.py --- a/iro/database.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import MySQLdb -import sqlite3 -class Database(object): - def __init__(self,connection): - self.conn=None - self.cursor=None - self.connection=connection - self.testConnection() - - def testConnection(self): - self.connect() - self.disconnect() - - def getConn(self): - return self.conn - - def getCursor(self): - return self.cursor - - def connect(self): - if not self.getConn(): - if (self.connection['type']=='mysql'): - self.conn=MySQLdb.connect( - host = self.connection["host"], - db = self.connection["db"], - user = self.connection["user"], - passwd = self.connection["passwd"], - ) - if (self.connection['type']=='sqlite'): - self.conn=sqlite3.connect(self.connection['path']) - - if not self.getCursor(): - self.cursor = self.conn.cursor() - - def disconnect(self): - if self.getCursor(): - self.cursor.close() - self.cursor=None - if self.getConn(): - self.conn.close() - self.conn=None - - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/dump_test_log.py --- a/iro/dump_test_log.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -import time, os, signal -LOG_FILE = 'test.log' - -log_file = open(LOG_FILE, 'a') - -def log(msg): - log_file.write(msg + '\n') - log_file.flush() - -def SigUSR1Handler(signum, frame): - print "Reacting on USR1 signal (signal 10)" - global log_file - log_file.close() - log_file = open(LOG_FILE, 'a') - return - -def init(): - if os.path.isfile('/var/usr/dump_test_log.pid'): - print 'Have to stop server first' - os.exit(1) - else: - print 'Starting server...' - #write process id file - f = open('/var/run/dump_test_log.pid', 'w') - f.write(str(os.getpid())) - f.flush() - f.close() - print 'Process start with pid ', os.getpid() - - signal.signal(signal.SIGUSR1, SigUSR1Handler) - -def main(): - init() - count = 1 - while True: - log('log line #%d, pid: %d' % (count, os.getpid())) - count = count + 1 - time.sleep(1) - -if __name__ == '__main__': - main() - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/error.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/error.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,146 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- +class InterfaceException(Exception): + """Exception, that should be reported to external client + + 999 -- unknown error + """ + def __init__(self, code=999, msg="Unknown error."): + """ + :param integer code: the error code + :param string msg: the error message + """ + self.code=code + self.msg=msg + + def dict(self): + """dict representation of the error""" + return {"code":self.code, + "msg":self.msg, + } + + def __str__(self): + return "%i: %s"%(self.code,self.msg) + +class UserNotFound(InterfaceException): + """ 901 -- apikey is unknown -- user not found""" + def __init__(self): + InterfaceException.__init__(self, 901, "Apikey is unknown.") + +class JobNotFound(InterfaceException): + """902 -- jobid is unknown""" + def __init__(self): + InterfaceException.__init__(self, 902, "Jobid is unknown.") + +class ExternalException(InterfaceException): + """950 -- error in external api""" + def __init__(self): + InterfaceException.__init__(self, 950, "Error in external API.") + +class ValidateException(Exception): + """700 -- validation failed.""" + def __init__(self, code=700, field=None, msg=None): + """ + :param integer code: the error code + :param string field: the field, that is not valid + :param string msg: the error message + """ + self.code=code + self.field=field + self.msg = msg + if not msg: + self.msg='Validation failed.' + if field and not msg: + self.msg="Validation of '%s' failed."%field + + def dict(self): + """dict representation of the error""" + return {"code":self.code, + "msg":self.msg, + "field":self.field, + } + def __str__(self): + return "%i: %s"%(self.code,self.msg) + +class InvalidTel(ValidateException): + """701 -- invalid telnumber""" + def __init__(self, number,field=None): + self.number = number + msg = "No valid telnumber: '%s'"%(number) + ValidateException.__init__(self, 701, field, msg) + +class InvalidMail(ValidateException): + """702 -- invalid mailaddress""" + def __init__(self, number,field=None): + self.number = number + msg = "No valid email: '%s'"%(number) + ValidateException.__init__(self, 702, field, msg) + +class OfferException(Exception): + """an Exception in Offer handling""" + def __init__(self, name): + self.name = name + +class NoRoute(OfferException): + """no valid route found""" + def __str__(self): + return "Not a valid route: %s"%self.name + +class NoProvider(OfferException): + """no provider found""" + def __str__(self): + return "Not a valid provider: %s"%self.name + +class NoTyp(OfferException): + """no typ found.""" + def __str__(self): + return "Not a valid Typ: %s"%self.name + +class RejectRecipient(Exception): + """can't handle the recipient in a route""" + def __init__(self,recipient, status=None): + self.recipient = recipient + self.status = status + + def __str__(self): + return "Reject recipient(%s): %s"%(str(self.recipient),str(self.status)) + +class ConfigException(Exception): + """Exception while loading configuration.""" + def __init__(self,section, name): + self.section = section + self.name = name + +class UnknownOption(ConfigException): + """Option is unknown""" + def __str__(self): + return "Unknown option '%s' in section '%s'."%(self.name, self.section) + +class NeededOption(ConfigException): + """Option is missing, but needed.""" + def __str__(self): + return "Option '%s' in section '%s' is missing."%(self.name, self.section) + +class NoRouteForTask(Exception): + """Can't send message to recipient with given offers""" + pass diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/inspect_getcallargs.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/inspect_getcallargs.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,116 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from inspect import getargspec, ismethod +import sys + +#code just copied form python 2.7.3 + +def getcallargs(func, *positional, **named): + """Get the mapping of arguments to values. + + A dict is returned, with keys the function argument names (including the + names of the * and ** arguments, if any), and values the respective bound + values from 'positional' and 'named'.""" + args, varargs, varkw, defaults = getargspec(func) + f_name = func.__name__ + arg2value = {} + + # The following closures are basically because of tuple parameter unpacking. + assigned_tuple_params = [] + def assign(arg, value): + if isinstance(arg, str): + arg2value[arg] = value + else: + assigned_tuple_params.append(arg) + value = iter(value) + for i, subarg in enumerate(arg): + try: + subvalue = next(value) + except StopIteration: + raise ValueError('need more than %d %s to unpack' % + (i, 'values' if i > 1 else 'value')) + assign(subarg,subvalue) + try: + next(value) + except StopIteration: + pass + else: + raise ValueError('too many values to unpack') + def is_assigned(arg): + if isinstance(arg,str): + return arg in arg2value + return arg in assigned_tuple_params + if ismethod(func) and func.im_self is not None: + # implicit 'self' (or 'cls' for classmethods) argument + positional = (func.im_self,) + positional + num_pos = len(positional) + num_total = num_pos + len(named) + num_args = len(args) + num_defaults = len(defaults) if defaults else 0 + for arg, value in zip(args, positional): + assign(arg, value) + if varargs: + if num_pos > num_args: + assign(varargs, positional[-(num_pos-num_args):]) + else: + assign(varargs, ()) + elif 0 < num_args < num_pos: + raise TypeError('%s() takes %s %d %s (%d given)' % ( + f_name, 'at most' if defaults else 'exactly', num_args, + 'arguments' if num_args > 1 else 'argument', num_total)) + elif num_args == 0 and num_total: + if varkw: + if num_pos: + # XXX: We should use num_pos, but Python also uses num_total: + raise TypeError('%s() takes exactly 0 arguments ' + '(%d given)' % (f_name, num_total)) + else: + raise TypeError('%s() takes no arguments (%d given)' % + (f_name, num_total)) + for arg in args: + if isinstance(arg, str) and arg in named: + if is_assigned(arg): + raise TypeError("%s() got multiple values for keyword " + "argument '%s'" % (f_name, arg)) + else: + assign(arg, named.pop(arg)) + if defaults: # fill in any missing values with the defaults + for arg, value in zip(args[-num_defaults:], defaults): + if not is_assigned(arg): + assign(arg, value) + if varkw: + assign(varkw, named) + elif named: + unexpected = next(iter(named)) + if isinstance(unexpected, unicode): + unexpected = unexpected.encode(sys.getdefaultencoding(), 'replace') + raise TypeError("%s() got an unexpected keyword argument '%s'" % + (f_name, unexpected)) + unassigned = num_args - len([arg for arg in args if is_assigned(arg)]) + if unassigned: + num_required = num_args - num_defaults + raise TypeError('%s() takes %s %d %s (%d given)' % ( + f_name, 'at least' if defaults else 'exactly', num_required, + 'arguments' if num_required > 1 else 'argument', num_total)) + return arg2value + + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/install.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/install.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,123 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- test-case-name: iro.tests.test_install -*- + +from twisted.python import log +import logging +from sqlalchemy import create_engine +from sqlalchemy.exc import DatabaseError +from sets import Set +import os + +from .config import configParser, confFiles, main +from .error import NeededOption, ValidateException +from .offer.provider import providers, getProvider + +from .model.schema import Base, Offer +from .model.utils import WithSession + +from . import config + +def checkConfig(): + """check configuration file syntax. + + :return: boolean value. + """ + + try: + l = configParser.read(confFiles) + if len(l) > 0: + return True + return False + except (NeededOption,ValidateException) as e: + log.msg("Error while processing config file: %s"%e,logLevel=logging.ERROR) + return False + +def checkDatabase(): + """Checks, if all tables are created. + + :return: boolean value + """ + engine = create_engine(config.main.dburl) + for t in Base.metadata.sorted_tables: + if not t.exists(engine): + return False + return True + +def checkDatabaseConnection(): + """Checks, if database can be connected. + + :return: boolean value + """ + try: + engine = create_engine(config.main.dburl) + con = engine.connect() + con.close() + return True + except DatabaseError as e: + log.msg("Error while trying to connect to database\n%s"%e,logLevel=logging.ERROR) + return False + +def createDatabase(): + """Create all database tables or only missing.""" + engine = create_engine(config.main.dburl) + Base.metadata.create_all(engine) + +def createSampleConfig(): + """create a sample configuration file 'iro.conf' with all possible provider sections.""" + if not os.path.exists("iro.conf"): + with open("iro.conf",'w') as fp: + fp.write("\n".join(main.sampleConf())) + fp.write("\n") + k = providers.keys() + k.sort() + for p in k: + fp.write("\n".join(providers[p](p).sampleConf())) + fp.write("\n") + else: + log.msg("iro.conf exists and will not be overwritten.") + +def getAllRoutes(providers,write=False): + """Checks and update offer list. + + :param boolean write: check or update list + :return dict: + - **"orphand"** (Set) -- a set of orphand offers + - **"added"** (Set) -- a set of new offers. The new name have a schema provider_typ_route + + """ + engine = create_engine(config.main.dburl) + ret={"orphand":Set(),"added":Set()} + with WithSession(engine,write) as session: + ret["orphand"]=Set([i[0] for i in session.query(Offer.name).all()]) + for provider in providers: + p=getProvider(provider,configParser.get(provider,"typ"),configParser.items(provider)) + for t in p.typs: + for r in p.typs[t]: + try: + ret["orphand"].remove(Offer.get(session, provider, r, t).name) + except: + if write: + session.add(Offer(provider=provider,route=r,typ=t,name='%s_%s_%s'%(provider,t,r))) + ret["added"].add("%s_%s_%s"%(provider,t,r)) + return ret + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/iro.api --- a/iro/iro.api Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,163 +0,0 @@ -iro.anbieter.FAX_de.FAX_de.createMailaddress?4(number) -iro.anbieter.FAX_de.FAX_de.default_conf?7 -iro.anbieter.FAX_de.FAX_de.max_recipients?7 -iro.anbieter.FAX_de.FAX_de.section?7 -iro.anbieter.FAX_de.FAX_de?1() -iro.anbieter.anbieter.anbieter.default_conf?7 -iro.anbieter.anbieter.anbieter.section?7 -iro.anbieter.anbieter.anbieter.sendFAX?4(fax, recipients) -iro.anbieter.anbieter.anbieter.sendMail?4(mail, recipients) -iro.anbieter.anbieter.anbieter.sendSMS?4(sms, recipients) -iro.anbieter.geonet.Mail?1(send_from, header, cont, attachments) -iro.anbieter.geonet.geonet.createMailaddress?4(number) -iro.anbieter.geonet.geonet.default_conf?7 -iro.anbieter.geonet.geonet.max_recipients?7 -iro.anbieter.geonet.geonet.section?7 -iro.anbieter.geonet.geonet.sendFAX?4(fax, recipients) -iro.anbieter.geonet.geonet.sendSMS?4(sms, recipients) -iro.anbieter.geonet.geonet?1() -iro.anbieter.sipgate.sipgate.BalanceGet?4() -iro.anbieter.sipgate.sipgate.__connect?6() -iro.anbieter.sipgate.sipgate.__disconnect?6() -iro.anbieter.sipgate.sipgate.__send?6(args, recipients) -iro.anbieter.sipgate.sipgate.__send_method?6(func, args=None) -iro.anbieter.sipgate.sipgate.getNewMessages?4() -iro.anbieter.sipgate.sipgate.getRecommendedInterval?4(methods) -iro.anbieter.sipgate.sipgate.read_basic_config?4(filename) -iro.anbieter.sipgate.sipgate.section?7 -iro.anbieter.sipgate.sipgate.sendFAX?4(fax, recipients) -iro.anbieter.sipgate.sipgate.sendSMS?4(sms, recipients) -iro.anbieter.sipgate.sipgate.updateStatus?4(arranged=None, failed=None) -iro.anbieter.sipgate.sipgate.url?7 -iro.anbieter.sipgate.sipgate?1(user="", password="") -iro.anbieter.smstrade.InternetConnectionError.__str__?6() -iro.anbieter.smstrade.InternetConnectionError?1(url) -iro.anbieter.smstrade.__send?6(self, key, route, to, message, from_=None, timestamp=None) -iro.anbieter.smstrade.sendSMS?4(self, sms, recipients) -iro.anbieter.smstrade.smstrade.read_basic_config?4(filename) -iro.anbieter.smstrade.smstrade.section?7 -iro.anbieter.smstrade.smstrade.url?7 -iro.anbieter.smstrade.smstrade?1() -iro.anbieter.smstrade.updateStatus?4(self, arranged=None, failed=None) -iro.anbieter.smtp.SMTP.prepareSMTP?4() -iro.anbieter.smtp.SMTP.read_basic_config?4(config_filename=None) -iro.anbieter.smtp.SMTP.sendMail?4(mail, recipients) -iro.anbieter.smtp.SMTP.shutdownSMTP?4() -iro.anbieter.smtp.SMTP.updateStatus?4(arranged=None, failed=None) -iro.anbieter.smtp.SMTP?1(config_filename=None, section="smtp") -iro.anbieter.smtp.anbieter.default_conf?7 -iro.anbieter.telnumber.NotATelNumber.__str__?6() -iro.anbieter.telnumber.NotATelNumber?1(number) -iro.anbieter.telnumber.anbieter.default_conf?7 -iro.anbieter.telnumber.telnumber.__eq__?6(y) -iro.anbieter.telnumber.telnumber.__ne__?6(y) -iro.anbieter.telnumber.telnumber.createNumber?4(number) -iro.anbieter.telnumber.telnumber.re_land?7 -iro.anbieter.telnumber.telnumber.re_number?7 -iro.anbieter.telnumber.telnumber.re_telnum?7 -iro.anbieter.telnumber.telnumber.std_land?7 -iro.anbieter.telnumber.telnumber?1(number=None) -iro.anbieter.test.TestTelnumber.equalNumber?4(tel1, tel2) -iro.anbieter.test.TestTelnumber.testEqual?4() -iro.anbieter.test.TestTelnumber.testNumber?4() -iro.anbieter.test.TestTelnumber.testWrongNumber?4() -iro.anbieter.test.anbieter.default_conf?7 -iro.content.FAX.sendto?4(anbieter, recipients) -iro.content.FAX?1(header, cont, attachments) -iro.content.Mail.as_string?4() -iro.content.Mail.sendto?4(anbieter, recipients) -iro.content.Mail?1(subject, body) -iro.content.SMS.sendto?4(anbieter, recipients) -iro.content.SMS?1(cont) -iro.content.content.sendto?4(anbieter, recipients) -iro.content.content?1(content) -iro.iro.MyManager.certificate?7 -iro.iro.MyManager.userdb?7 -iro.iro.MySMTP.setJob?4(job) -iro.iro.MySMTP.updateStatus?4(arranged=None, failed=None) -iro.iro.MySMTP?1(config_filename=None, section="smtp") -iro.iro.MySipgate.setJob?4(job) -iro.iro.MySipgate.updateStatus?4(arranged=None, failed=None) -iro.iro.MySipgate?1(user="", password="") -iro.iro.MySmstrade.setJob?4(job) -iro.iro.MySmstrade.updateStatus?4(arranged=None, failed=None) -iro.iro.MySmstrade?1() -iro.iro.MyUserDB.createUser?4(user) -iro.iro.MyUserDB?1(userlist, jobqueue) -iro.iro.start?4(userlist) -iro.job.Job.addFailed?4(failed) -iro.job.Job.addGood?4(good) -iro.job.Job.getName?4() -iro.job.Job.getProvider?4() -iro.job.Job.getStatus?4(detailed) -iro.job.Job.start?4() -iro.job.Job.stop?4() -iro.job.Job?1(provider, name) -iro.job.MessageJob.getMessage?4() -iro.job.MessageJob.getRecipients?4() -iro.job.MessageJob.start?4() -iro.job.MessageJob.stop?4() -iro.job.MessageJob?1(provider, name, message, recipients) -iro.joblist.Joblist.__getitem__?6(key) -iro.joblist.Joblist.__registerJob__?6(job) -iro.joblist.Joblist._createID?5() -iro.joblist.Joblist.newFAX?4(subject, fax, recipients) -iro.joblist.Joblist.newMail?4(subject, body, recipients) -iro.joblist.Joblist.newSMS?4(message, recipients) -iro.joblist.Joblist?1(manager, queue, providerlist) -iro.providerlist.Providerlist.add?4(name, provider, typeslist) -iro.providerlist.Providerlist.getDefault?4(stype) -iro.providerlist.Providerlist.getProvider?4(stype, name="default") -iro.providerlist.Providerlist.getProviderlist?4(stype) -iro.providerlist.Providerlist.setDefault?4(stype, name) -iro.providerlist.Providerlist?1() -iro.test.MyManager.certificate?7 -iro.test.MyManager.userdb?7 -iro.test.StoppableXMLRPCServer.run?4() -iro.test.StoppableXMLRPCServer.running?7 -iro.test.StoppableXMLRPCServer.stop?4() -iro.test.StoppableXMLRPCServer?1(*args, **kwargs) -iro.test.TestServer.setUp?4() -iro.test.TestServer.tearDown?4() -iro.test.TestServer.testGetDefault?4() -iro.test.TestServer.testGetProvider?4() -iro.test.TestServer.testLogin?4() -iro.test.TestServer.testTwoUser?4() -iro.test.TestServer.testsendSMS?4() -iro.test.init_server?4() -iro.user.Admin?1(jobqueue) -iro.user.NotSupportedFeature.__str__?6() -iro.user.NotSupportedFeature?1(name) -iro.user.User.getDefaultProvider?4(name) -iro.user.User.getProvider?4(name) -iro.user.User.startFAX?4() -iro.user.User.startMail?4(subject, body, recipients) -iro.user.User.startSMS?4(message, recipients) -iro.user.User.status?4(id=None, detailed=False) -iro.user.User.stop?4(id) -iro.user.User?1(jobqueue) -iro.worker.Worker.run?4() -iro.worker.Worker?1(queue) -iro.xmlrpc.AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler.do_POST?4() -iro.xmlrpc.AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler.report_401?4() -iro.xmlrpc.AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler.report_error?4(code) -iro.xmlrpc.AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler.testUser?4(username, password) -iro.xmlrpc.AuthentificateXMLRPCServer.test?4() -iro.xmlrpc.SecureAuthentificateXMLRPCServer.SecureAuthentificateXMLRPCRequestHandler.do_POST?4() -iro.xmlrpc.SecureAuthentificateXMLRPCServer.test?4() -iro.xmlrpc.SecureUserdbXMLRPCServer.SecureAuthentificateXMLRPCRequestHandler2.testUser?4(username, password) -iro.xmlrpc.SecureUserdbXMLRPCServer.SecureUserDBXMLRPCServer.activateUser?4(username, password) -iro.xmlrpc.SecureUserdbXMLRPCServer.SecureUserDBXMLRPCServer?1(addr, userdb, requestHandler=SecureAuthentificateXMLRPCRequestHandler2, certificate="server.cert", privatekey="server.pem", logRequests=1) -iro.xmlrpc.SecureUserdbXMLRPCServer.UserDB.__getitem__?6(key) -iro.xmlrpc.SecureUserdbXMLRPCServer.UserDB.createHash?4(user) -iro.xmlrpc.SecureUserdbXMLRPCServer.UserDB.createUser?4(user) -iro.xmlrpc.SecureUserdbXMLRPCServer.UserDB?1(userClass, userlist, jobqueue) -iro.xmlrpc.SecureXMLRPCServer.SSLWrapper.__getattr__?6(name) -iro.xmlrpc.SecureXMLRPCServer.SSLWrapper.__setattr__?6(name, value) -iro.xmlrpc.SecureXMLRPCServer.SSLWrapper.accept?4() -iro.xmlrpc.SecureXMLRPCServer.SSLWrapper.shutdown?4(how=1) -iro.xmlrpc.SecureXMLRPCServer.SSLWrapper?1(conn) -iro.xmlrpc.SecureXMLRPCServer.SecureTCPServer?1(server_address, RequestHandlerClass, certificate, privatekey) -iro.xmlrpc.SecureXMLRPCServer.SecureXMLRPCRequestHandler.setup?4() -iro.xmlrpc.SecureXMLRPCServer.SecureXMLRPCServer?1(addr, requestHandler=SecureXMLRPCRequestHandler, certificate="server.cert", privatekey="server.pem", logRequests=1) -iro.xmlrpc.SecureXMLRPCServer.test?4() \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/iro.e4p --- a/iro/iro.e4p Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,225 +0,0 @@ - - - - - - - - Python - Console - Ein Daemon zum Senden von Massensms, -faxen und emails - 0.1 - Sandro Knauß - bugs@sandroknauss.de - - iro.py - __init__.py - xmlrpc/SecureXMLRPCServer.py - xmlrpc/__init__.py - xmlrpc/SecureAuthentificateXMLRPCServer.py - xmlrpc/AuthentificateXMLRPCServer.py - xmlrpc/SecureUserdbXMLRPCServer.py - worker.py - user.py - test.py - providerlist.py - joblist.py - job.py - anbieter/smtp.py - anbieter/geonet.py - anbieter/content.py - anbieter/__init__.py - anbieter/telnumber.py - anbieter/test.py - anbieter/FAX_de.py - anbieter/sipgate.py - anbieter/gsm0338.py - anbieter/anbieter.py - anbieter/smstrade.py - - - - - - - - - - - iro.conf.inst - iro.conf - - iro.py - - None - - - - add - - - - - - - - checkout - - - - - - - - commit - - - - - - - - diff - - - - - - - - export - - - - - - - - global - - - - - - - - history - - - - - - - - log - - - - - - - - remove - - - - - - - - status - - - - - - - - tag - - - - - - - - update - - - - - - - - - - - - standardLayout - - - True - - - - - - - - - - - - - - - ERIC4API - - - - - basePackage - - - iro - - - ignoreFilePatterns - - - - - - - - includePrivate - - - True - - - languages - - - - Python - - - - outputFile - - - iro.api - - - useRecursion - - - True - - - - - - - \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/iro.py --- a/iro/iro.py Wed Dec 21 22:07:48 2011 +0100 +++ b/iro/iro.py Thu Sep 27 17:15:46 2012 +0200 @@ -1,177 +1,82 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -import multiprocessing, logging -#logging anfangen -logger=logging.getLogger("iro") -logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)s(%(processName)s)-%(levelname)s: %(message)s') - - -# Server code -from xmlrpc import SecureUserDBXMLRPCServer,UserDB - -from user import User, Admin -import anbieter -import ConfigParser - -from job import SMSJob, FAXJob, MailJob -from joblist import Joblist -from providerlist import Providerlist -from acounting import Acounting - -class MyUserDB(UserDB): - def __init__(self, userlist,jobqueue): - UserDB.__init__(self, None,userlist,jobqueue) - - def createUser(self, user): - self.userlist[self.createHash(user)]=user["class"](user["name"],self.jobqueue) - - - +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -class MySipgate(anbieter.sipgate): - - def __init__(self,user="",password="" ): - anbieter.sipgate.__init__(self, user, password) - - def setJob(self, job): - self.job=job - - def updateStatus(self, arranged=None, failed=None): - if arranged: - self.job.addGood(arranged) - - if failed: - self.job.addFailed(failed) - -class MySMTP(anbieter.SMTP): - - def __init__(self,config_filename=None,section="smtp"): - anbieter.SMTP.__init__(self,config_filename,section) - - def setJob(self, job): - self.job=job - - def updateStatus(self, arranged=None, failed=None): - if arranged: - self.job.addGood(arranged) - - if failed: - self.job.addFailed(failed) - -class MySmstrade(anbieter.smstrade): - - def __init__(self ): - anbieter.smstrade.__init__(self ) - - def setJob(self, job): - self.job=job - - def updateStatus(self, arranged=None, failed=None): - if arranged: - self.job.addGood(arranged) - - if failed: - self.job.addFailed(failed) +from twisted.application.service import Service, MultiService +from twisted.application import internet +from twisted.web import resource, server +from twisted.internet import reactor +from twisted.python import log + +from sqlalchemy import create_engine, pool +import config, install +from .view import xmlrpc, jsonrpc, jsonresource +from .model import setEngine, setPool +from .controller.pool import startPool, dbPool + +class IroService(Service): + def startService(self): + log.msg("Starting service...") + engine = create_engine(config.main.dburl, + poolclass = pool.SingletonThreadPool, pool_size=dbPool.maxthreads, pool_recycle=3600) + + setEngine(engine) + startPool(reactor) + setPool(dbPool) + reactor.callWhenRunning(config.readConfig) + Service.startService(self) -def start(userlist): - from multiprocessing import Queue - from multiprocessing.managers import BaseManager - - - class MyManager(BaseManager): - pass - - MyManager.register('SMSJob', SMSJob) - MyManager.register('FaxJob', FAXJob) - MyManager.register('MailJob',MailJob) - MyManager.register('Providerlist',Providerlist) - MyManager.register('Acounting',Acounting) - manager = MyManager() - manager.start() - - - conf=["iro.conf", "~/iro.conf","/etc/iro/iro.conf"] +def makeService(cfg): + top_service = MultiService() + config.confFiles.insert(0, cfg["config"]) + if not install.checkConfig(): + log.err("You can create a sample configuration file by running iro-install") + raise Exception("Please update or create your configuration file %s." % cfg["config"]) + config.init() - #anbieter erzeugen und konfigurieren - - sip=MySipgate() - sip.read_basic_config(conf) - - localhost=MySMTP() - localhost.read_basic_config(conf) - - smstrade=MySmstrade() - smstrade.read_basic_config(conf) - - cp = ConfigParser.ConfigParser() - cp.read(conf) - dbconn={'type':cp.get('db', 'type'), - 'host':cp.get('db', 'host'), - 'db':cp.get('db', 'db'), - 'user':cp.get('db', 'user'), - 'passwd':cp.get('db', 'passwd'), - 'table':cp.get('db', 'table'), - } - + if not install.checkDatabaseConnection(): + raise Exception("Can't connect to database") - #Benutzerdatenbank erstellen - queue = Queue() - provider=manager.Providerlist() - provider.add("sipgate", sip, ["sms", "fax", ]) - provider.add("smstrade", smstrade, ["sms", ]) - #provider.add("geonet", None, ["sms", "fax", ]) - #provider.add("fax.de", None, ["sms", "fax", ]) - provider.add("localhost", localhost, ["mail", ]) - provider.setDefault("sms","smstrade") - provider.setDefault("fax","sipgate") - provider.setDefault("mail","localhost") - jobqueue=Joblist(manager, queue, provider,dbconn) - - userdb=MyUserDB(userlist,jobqueue) + if not install.checkDatabase(): + raise Exception("Database not in right format. Please run iro-install --install") - #working thread erstellen - from worker import Worker - worker=Worker(queue) - worker.start() + routes = [ s for s in config.configParser.sections() if not s in ["main",]] + ao = install.getAllRoutes(routes, False) + for o in ao["orphand"]: + log.msg("Offer(%s) is orphand (no route using this offer)."%o) + if ao["added"]: + raise Exception("offerlist is not up-to-date.\nPlease run iro-install --update") - #Server starten - cp = ConfigParser.ConfigParser() - cp.read(conf) - cert=cp.get('server', 'cert') - key=cp.get('server', 'key') - server = SecureUserDBXMLRPCServer(addr=("localhost", 8000), - userdb=userdb, - certificate=cert,privatekey=key) - server.relam="xmlrpc" + root = resource.Resource() + xmlrpc.appendResource(root) + jsonrpc.appendResource(root) + jsonresource.appendResource(root) - logger.info('Server gestartet...') - try: - server.serve_forever() - except KeyboardInterrupt: - pass - except: - logger.exception('Äh, ein Fehler ist aufgetreten') - finally: - logger.info('Server wird beendet...') - queue.close() - worker.terminate() + v2 = resource.Resource() + xmlrpc.appendResource(v2) + jsonrpc.appendResource(v2) + jsonresource.appendResource(v2) + root.putChild('1.0a', v2) -if __name__ == '__main__': - userlist=[{"name":"test","password":"test", "class":User}, - {"name":"test2","password":"test2", "class": Admin}] - start(userlist) - - + internet.TCPServer(config.main.port, server.Site(root)).setServiceParent(top_service) + IroService().setServiceParent(top_service) + return top_service diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/job.py --- a/iro/job.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,121 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -class Job(object): - ''' - Basic class for all jobs - ''' - def __init__(self,providerlist,provider, name): - self.providerlist=providerlist - self.provider=provider - self.name=name - self.status = "init" - self.dStatus={"good":[], "failed":[]} - self.id=None - self.acounting=None - - def setAcounting(self,ac): - self.acounting=ac - - def setId(self, id): - self.id=id - if self.acounting: - self.acounting.setId(id) - - def start(self,indifier=None): - self.indifier=indifier - self.status = "started" - - def stop(self): - self.status = "stopped" - - def setLog(self,log): - self.log=log - - def getStatus(self,detailed=False): - if detailed and self.status == "started" or self.status == "sended": - return self.status, self.dStatus - return self.status,{} - - def setStatus(self,status): - self.status=status - - def getName(self): - return self.name - - def getProvider(self): - return None - - def addGood(self, good): - if self.acounting: - self.acounting.addGood(good) - if type(good) == list: - self.dStatus['good']=self.dStatus['good']+good - else: - self.dStatus['good'].append(good) - - def addFailed(self, failed): - if self.acounting: - self.acounting.addFailed(failed) - if type(failed) == list: - self.dStatus['failed']=self.dStatus['failed']+failed - else: - self.dStatus['failed'].append(failed) - -class MessageJob(Job): - ''' - A specialized class for smsjobs - ''' - def __init__(self,providerlist,provider, name, message,recipients): - self.message=message - self.recipients=recipients - Job.__init__(self,providerlist,provider, name) - - def stop(self): - pass - - def start(self, id=None): - Job.start(self,id) - self.getProvider().setJob(self) - self.message.sendto(self.getProvider(), self.recipients) - self.status="sended" - - def getMessage(self): - return self.message - - def getRecipients(self): - return self.recipients - - -class SMSJob(MessageJob): - def __init__(self,providerlist,provider, name, message,recipients): - MessageJob.__init__(self,providerlist,provider, name, message,recipients) - - def getProvider(self): - return self.providerlist.getProvider("sms", self.provider) - -class FAXJob(MessageJob): - def __init__(self,providerlist,provider, name, message,recipients): - MessageJob.__init__(self,providerlist,provider, name, message,recipients) - - def getProvider(self): - return self.providerlist.getProvider("fax", self.provider) - -class MailJob(MessageJob): - def __init__(self,providerlist,provider, name, message,recipients): - MessageJob.__init__(self,providerlist,provider, name, message,recipients) - - def getProvider(self): - return self.providerlist.getProvider("mail", self.provider) - - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/joblist.py --- a/iro/joblist.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -from anbieter import content -import hashlib, os, time -import logging -logger=logging.getLogger("iro.joblist"); - -class Joblist: - ''' - Providing an list of jobs; each new job gets a hash id - ''' - def __init__(self,manager, queue,providerlist,dbconn=None): - self.jobs={} - self.manager=manager - self.queue=queue - self.providerlist=providerlist - self.dbconn=dbconn - - - def __getitem__(self,key): - return self.jobs[key] - - def __registerJob__(self, job, user): - id = self._createID() - if self.dbconn: - job.setAcounting(self.manager.Acounting(id,self.dbconn)) - job.setId(id, user) - self.jobs[id]=job - self.queue.put(job) - return id - - def newSMS(self, message, recipients, provider="default", user=None): - ''' - creates a new SMS - ''' - job=self.manager.SMSJob(self.providerlist, provider,message, content.SMS(message),recipients) - return self.__registerJob__(job,user) - - def newFAX(self,subject, fax,recipients,provider="default",user=None): - ''' - creates a new Fax - ''' - job=self.manager.FaxJob(self.providerlist, provider,subject, content.FAX(subject,'' ,fax),recipients) - return self.__registerJob__(job,user) - - def newMail(self, subject, body, recipients, frm, provider="default",user=None): - ''' - creates a new Mail - ''' - job=self.manager.MailJob(self.providerlist, provider,subject, content.Mail(subject, body, frm),recipients) - return self.__registerJob__(job,user) - - def _createID(self): - ''' - creats a random hash id - ''' - while True: - m = hashlib.sha1() - m.update(str(time.time())) - m.update(os.urandom(10)) - if not self.jobs.has_key(m.hexdigest): - if not self.dbconn: - self.jobs[m.hexdigest()]=None - break - if not self.manager.Acounting(m.hexdigest(),self.dbconn).getStatus(): - self.jobs[m.hexdigest()]=None - break - return m.hexdigest() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/main.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/main.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,69 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.web import resource, server +from twisted.internet import reactor +from twisted.python import log + +from sqlalchemy import create_engine, pool + +from .model import setEngine, setPool +from .controller.pool import startPool, dbPool +from .view import xmlrpc, jsonrpc, jsonresource +from . import config + +def runReactor(reactor, engine, port, root): + """start reactor. + + :param reactor: twisted reactor + :param engine: sqlalchemy engine + :param integer port: port to listen to + :param `twisted.web.resource.Resource` root: resource to share + """ + setEngine(engine) + startPool(reactor) + setPool(dbPool) + + reactor.listenTCP(port, server.Site(root)) + log.msg("Server is running now...") + reactor.run() + + +if __name__ == '__main__': + + config.readConfig() + + engine = create_engine(config.main.dburl, + poolclass = pool.SingletonThreadPool, pool_size=dbPool.maxthreads, pool_recycle=3600) + + + root = resource.Resource() + xmlrpc.appendResource(root) + jsonrpc.appendResource(root) + jsonresource.appendResource(root) + + v2 = resource.Resource() + xmlrpc.appendResource(v2) + jsonrpc.pappendResource(v2) + jsonresource.pappendResource(v2) + root.putChild('1.0a', v2) + + runReactor(reactor, engine, config.main.port, root) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/merlin --- a/iro/merlin Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -#!/bin/bash -# -# merlin commando -# -# eine überprüfung auf korrekten aufruf findet nicht statt -# -# beispiel: -# -# ./merlin ./arthur -# -# startet programm arthur und wenn er stirbt, wird er sofort -# wiederbelebt. -# harmlose magie halt... :-) -# -LOG=/home/sandy/var/log/merlin_Iro.log -while : ; do - echo -n "$(date +'%F %T %Z') " >> $LOG - $1 status >> $LOG - if [ $? -eq 1 ]; then - echo $(date +'%F %T %Z') $1 neustarten >> $LOG - $1 start >> $LOG - fi - sleep 60 -done - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/merlin_daemon --- a/iro/merlin_daemon Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -#! /bin/sh -NAME="merlin" -DEAMON=/home/sandy/svn/iro/$NAME -DEAMON_OPTS="/home/sandy/svn/iro/MyIro_daemon" -PID=/home/sandy/var/run/$NAME.pid - -test -x $DEAMON || exit 0 - -. /lib/lsb/init-functions - -case "$1" in - start) - log_daemon_msg "Starting $NAME server" $NAME - if start-stop-daemon --start --quiet --oknodo --pidfile $PID --make-pidfile --background --startas $DEAMON -- $DEAMON_OPTS; then - log_end_msg 0 - else - log_end_msg 1 - fi - ;; - stop) - log_daemon_msg "Stopping $NAME server" $NAME - if start-stop-daemon --stop --quiet --oknodo --pidfile $PID; then - log_end_msg 0 - else - log_end_msg 1 - fi - ;; - - restart) - $0 stop - sleep 1 - $0 start - ;; - - status) - status_of_proc -p $PID $DEAMON $NAME && exit 0 || exit $? - ;; - - *) - log_action_msg "Usage: $0 {start|stop|restart|status}" - exit 1 -esac - -exit 0 diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/__init__.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,34 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""The model pakcage represents all data, that is stored either in database or in objects. + +before using the model, you have to use, you have to set the Engine with :func:`dbdefer.setEngine` and set the Threadpool with :func:`dbdefer.setPool` (see :func:`iro.main.runReactor` for initalizing this module). +""" + +import schema +import user +import utils +import job +import offer + +from dbdefer import setEngine +from pool import setPool diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/dbdefer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/dbdefer.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,92 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from decorator import FunctionMaker +import sqlalchemy + +from .pool import runInDBPool +from .utils import WithSession + +import inspect + +class DBDefer(object): + '''a twisted sqlalchemy connector. + + This is used as a Decorator class. + It adds a session parameter to the function with a valid session. + If the session parmaeter is used in calling this function only calls a commit after running the function, instead of createing a new session.''' + def __init__(self, engine, autocommit=False): + """ + :param `sqlalchemy.engine.base.Engine` engine: a valid sqlalchemy engine object (normally created via :func:`sqlalchemy.create_engine`). + :param boolean autocommit: autocommit after running the function. + """ + self.autocommit=autocommit + self.engine = engine + + def __call__(self, func): + @runInDBPool + def wrapper(func,*a, **kw): + i = argspec.args.index("session") + ab = a[:i] + ae = a[i:-1] + if isinstance(a[-1],sqlalchemy.orm.session.Session): + al = ab + (a[-1],) + ae + ret = func(*al, **kw) + if self.autocommit: + a[-1].commit() + return ret + else: + with WithSession(self.engine, self.autocommit) as session: + al = ab + (session,) + ae + return func(*al, **kw) + + caller=func + argspec = inspect.getargspec(caller) + args =[i for i in argspec.args if i != "session" ] + sargs=", ".join(args) + if sargs: + sargs+=", session" + else: + sargs="session" + defaults = argspec.defaults + if not defaults: + defaults = (None,) + else: + defaults += (None,) + evaldict = caller.func_globals.copy() + evaldict['_call_'] = func + evaldict['decorator'] = wrapper + wrap = FunctionMaker.create( + '%s(%s)' % (caller.__name__, sargs), + 'return decorator(_call_, %s)' % sargs, + evaldict, defaults=defaults, undecorated=caller, __wrapped__=caller, + doc=caller.__doc__, module=caller.__module__, addsource=True) + return wrap + +dbdefer=DBDefer(None) +"""the decorator to use. Use :func:`setEngine` to set a valid engine after program has started.""" + +def setEngine(engine,autocommit=False): + """set the engine and autocommit for the decorator (see :class:`DBDefer`).""" + dbdefer.engine = engine + dbdefer.autocommit = autocommit + + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/decorators.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/decorators.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,89 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""All decorators, that are created by this package. + +Imports: + +- :func:`.user.vUser` -- a validator for apikeys. +- :func:`.pool.runInDBPool` -- runs a actual function in dbpool. +""" +import types + +from .user import vUser +from .schema import Offer +from .dbdefer import dbdefer +from .pool import runInDBPool + +from ..error import ValidateException + +@dbdefer +def vRoute(session, value, field, typ, allowString=True, allowList=True): + """ a validator to test a valid route. use with :func:`iro.validate.validate`. + + :param session: a valid session object (is created by decorator :func:`iro.model.dbdefer.dbdefer`) + :param value: the value to test + :param string field: the field that is tested (only used to get a propper error message). + :param string typ: a typ to test the route in + :param boolean allowString: a single route is allowd. + :param boolean allowList: a list of routes is allowed. + :return: *value*, if value is a valid route for a given typ. + :raises: :exc:`iro.error.ValidateException` + """ + str_ = False + ret = [] + + if type(value) is types.StringType: + if not allowString: + raise ValidateException(field=field,msg='%s must be a list of routes.'%field) + str_=True + value=[value] + elif not allowList: + raise ValidateException(field=field,msg='%s must be a route - No list of routes.'%field) + + routes = [o.name for o in Offer.routes(session,typ)] + providers = [o.provider for o in Offer.providers(session,typ)] + for v in value: + if v not in routes and v not in providers and v != "default": + raise ValidateException(field=field,msg='Route %s is not valid.'%v) + if v not in ret: + ret.append(v) + if str_: + return ret[0] + return ret + +@dbdefer +def vTyp(value,field, session): + """ a validator to test a valid typ. use with :func:`iro.validate.validate`. + + :param session: a valid session object (is created by decorator :func:`iro.model.dbdefer.dbdefer`) + :param value: the value to test + :param string field: the field that is tested (only used to get a propper error message). + :return: *value*, if value is a valid typ. + :raises: :exc:`iro.error.ValidateException` + """ + + for typ in Offer.typs(session): + if value == typ[0]: + break + else: + raise ValidateException(field=field,msg='Typ %s is not valid.'%value) + return value diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/job.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/job.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,146 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.python import log, threadable + +from datetime import datetime +from collections import MutableMapping + +import schema +import offer +from .dbdefer import dbdefer + +class ExJob: + ''' A ExJob object represents a message to multiple recipients over multiple offers to send. + + One single message to one recipient is handeld in :class:`iro.controller.task.Task`. + This class holds connections to all tasks. + This class is responsiple to update the status in database of one job and updating the bill. + ''' + + synchronized = ["incStatus", "_status"] + + def __init__(self, dbjob, recipients, message, offers): + """Constructor of ExJob. + + :param dbjob: primary key of the job element in database + :param list recipients: list of all recipients + :param `iro.model.message.Message` message: message to send + :param list offers: list of all possible offers to send message over + """ + + self.dbjob = dbjob #Connection to database job element (id) + self.message = message + self.recipients = recipients + self.offers = offers + self.tasks={} + self.c = 0 + self.status = "started" + log.msg("Job(%s) created."%(self.dbjob)) + + def addTask(self,task): + """adding a task to tasks dict - key is the recipient. + + :param `iro.controller.task.Task` task: a task + """ + self.tasks[task.recipient] = task + + def incStatus(self): + """increments the processed messages (function is threadsafe).""" + self.c += 1 + return self.c + + def _status(self, session, status): + """updates the status of the database object (function is threadsafe). + + :param session: a valid database session + :param string status: new status + """ + job = schema.Job.get(session, self.dbjob) + if self.status == "error": + return + elif self.status == "sended" and status != "error": + return + job.status = status + self.status = status + log.msg("Job(%s) status changed to: %s."%(self.dbjob, status)) + session.commit() + + @dbdefer + def setStatus(self, session, task, status): + """callback of one task. + + This function updates the database object and the bill. + """ + c = self.incStatus() + job = schema.Job.get(session, self.dbjob) + + if c == len(self.recipients): + self._status(session,"sended") + elif job.status in ["started","init"]: + self._status(session,"sending") + + if status.costs > 0: + o = schema.Offer.get(session, status.provider.name, status.route, self.message.typ) + job.messages.append(schema.Message(price=status.costs, isBilled=False, recipient=str(task.recipient), count=status.count, exID=status.exID, date=datetime.today(), offer=o)) + session.commit() + + log.msg("Job(%s) to '%s' ended sucecessfully via %s:%s."%(self.dbjob, task.recipient, status.provider.name,status.route)) + + @dbdefer + def setError(self, session, task, err): + """errback for one task. + + This function updates the database object. + """ + self.incStatus() + if self.status != "error": + self._status(session,"error") + log.err(_why="Error: Job(%s) to '%s' failed."%(self.dbjob, task.recipient),_stuff=err) + +threadable.synchronize(ExJob) + +class ExJobs(dict, MutableMapping): + """ a dict to handle all jobs. + """ + @dbdefer + def create(self, session, user, recipients, message, offers, info=None): + """creates on new Job. + + :param session: a valid session ( created by decorator :func:`iro.model.dbdefer.dbdefer`) + :param `iro.model.schema.User` user: a user object + :param list recipients: list of all recipients + :param `iro.model.message.Message` message: message to send + :param list offers: a list of offers ( list will be reduced to the allowed offers for the **user** -- using :func:`iro.model.offer.extendProvider`) + :param string info: a bill group name + :returns: the new job + """ + user = session.merge(user) + job = schema.Job(info=info, status="started") + user.jobs.append(job) + session.commit() + + o = offer.extendProvider(user, message.typ, offers, session=session) + self[job.id] = ExJob(job.id, recipients, message, o) + return self[job.id] + +exJobs = ExJobs() +"""the dict of all available jobs.""" diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/message.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/message.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,147 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- +"""All available message typs to send send. +""" +from email.mime.text import MIMEText +from email.header import Header +from email.Utils import formatdate + +class Message: + """ Baseclass for all different message typs.""" + def __init__(self,content, typ="Message"): + """Constructor of Message class. + + :param content: content of the message + :param string typ: typ of the message + + .. automethod:: __eq__ + .. automethod:: __neq__ + """ + self.content=content + self.typ = typ + + + def getContent(self): + """returns the content of the message""" + return self.content + + def __eq__(self,other): + """return ``True`` if **other** has the same content.""" + return self.content == other.content + + def __neq__(self,other): + """return ``False`` if **other** has the same content.""" + return not self.__eq__(other) + +class SMS(Message): + """ A representation of one SMS""" + def __init__(self, cont, from_ = None): + """Constructor of SMS class. + + :param string cont: SMS content + :param string from_: the telnumber from the SMS should be sended. + """ + Message.__init__(self, cont.encode("utf-8"), typ="sms") + self.from_ = from_ + +class Fax(Message): + """A representation of one fax.""" + def __init__(self,header,cont,attachments=[]): + """Constructor of one fax. + + :param string header: Headline of fax + :param string cont: fax content + :param list attachments: attachments of fax + """ + Message.__init__(self,cont.encode("utf-8"),typ="fax") + self.header=header + self.attachments=attachments + + def getAttachment(self,i): + """returns a attachment + :param integer i: the i-th attachment + """ + return self.attachments[i] + + def __eq__(self,other): + if not Message.__eq__(self,other): + return False + + if self.header != other.header: + return False + + if len(self.attachments) != len(other.attachments): + return False + + for a in self.attachments: + if a not in other.attachments: + return False + return True + + + +class Mail(Message): + """A representation of one Mail""" + def __init__(self, subject, body, frm): + """Constructor of one mail. + + :param string subject: subject of the mail + :param string body: body of the mail + :param string frm: mailaddress to send mail from + + .. automethod:: __repr__ + """ + con = MIMEText(body.encode("utf-8"), _charset='utf-8') + sub = Header(subject.encode('utf-8'), 'utf-8') + con['Subject'] = sub + con['Date'] = formatdate(localtime=True) + self.subject = subject + self.body = body + self.frm=frm + Message.__init__(self, con, typ='mail') + + def as_string(self): + """returns created mail""" + return self.content.as_string() + + def getFrom(self): + """returns the from mailaddress""" + return self.frm + + def __eq__(self,other): + if self.as_string() != other.as_string(): + return False + + if self.frm != other.frm: + return False + + return True + + def __repr__(self): + """string representation of the class. + + :returns: ```` + """ + return ""%(self.subject,self.body,self.frm) + +__all__=["Message", "SMS", "Fax", "Mail"] diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/offer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/offer.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,79 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from .dbdefer import dbdefer + +import schema +from ..config import configParser +from ..offer import getProvider, Offer + +@dbdefer +def extendProvider(session, user, typ, ps): + """extend and reduce the offer list to allowed routes for **user**. + + - extend the "default" to the default offerlist of the **user**. + - extend a Provider name to all routes of that provider. + + :param session: a valid session ( created by decorator :func:`iro.model.dbdefer.dbdefer`) + :param `iro.model.schema.User` user: a user object + :param string typ: typ of the message + :param ps: a list of strings or a string, each one offer name or provider name + :return: a extended an reduced offer list + """ + user = session.merge(user) + ret = [] + if ps == "default" or ps == ["default"]: + ps = (q[0] for q in user.routes(typ,default=True)) + for p in ps: + if p not in ret and user.has_right(typ, offer_name = p): + ret.append(p) + elif user.providers(typ).filter(schema.Offer.provider==p).first(): + for r in providers[p].typs[typ]: + n = user.has_right(typ, provider=p, route=r) + if n and n not in ret: + ret.append(n) + return ret + +@dbdefer +def loadOffers(session): + """loading Offers from database and configuration file and store them in :attr:`~iro.model.offer.offers` and :attr:`~iro.model.offer.providers`. + + :param session: a valid session ( created by decorator :func:`iro.model.dbdefer.dbdefer`) + """ + offers.clear() + providers.clear() + for provider in ( s for s in configParser.sections() if not s in ["main",]): + p=getProvider(provider,configParser.get(provider,"typ"),configParser.items(provider)) + for t in p.typs: + for r in p.typs[t]: + n = schema.Offer.get(session, provider, r, t).name + offers[n]=Offer(provider=p,route=r,typ=t,name=n) + providers[provider]=p + +offers={} +"""A dict of all available offers -- key is the offer name""" + +providers={} +"""A dict of all available providers -- key is the provider name""" + + +configParser.registerReload(loadOffers) + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/pool.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/pool.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,47 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from decorator import decorator +import sqlalchemy +class Data: + """A very simple class to save the thread pool for database requests""" + def __init__(self): + self.pool = None + +data = Data() +"""holds connection to the actual thread pool for the database requests""" + +def setPool(pool): + """setting the thread pool""" + data.pool = pool + +@decorator +def runInDBPool(f,*args,**kwargs): + """Decorator to run DB queries in Twisted's thread pool. + + If last argument is a session object, the function is called directly. + """ + if isinstance(args[-1],sqlalchemy.orm.session.Session): + return f(*args,**kwargs) + else: + return data.pool.run(f, *args, **kwargs) + + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/schema.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,302 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- + +from sqlalchemy import Column, Integer, String, Sequence, Boolean, DateTime, Numeric, Enum +from sqlalchemy.ext.declarative import declarative_base + +#relationship +from sqlalchemy import ForeignKey +from sqlalchemy.orm import relationship, backref, object_session + +from sqlalchemy import and_ +from sqlalchemy.orm.exc import DetachedInstanceError +import sqlalchemy.sql.functions as func + +import job +from ..error import JobNotFound + +Base = declarative_base() + +class Userright(Base): + """Allowed offers for one user. Default routes are sorted by **default** value.""" + __tablename__ = 'userright' + user_name = Column('user', String(100), ForeignKey('apiuser.name'), primary_key=True) + """username""" + offer_name = Column('offer', String(100), ForeignKey('offer.name'), primary_key=True) + """offername""" + default = Column(Integer) + """sorting defaults routes with this value""" + offer = relationship("Offer") + """connected :class:`Offer` object""" + user = relationship("User") + """connected :class:`User` object""" + + def __init__(self, offer, default=None): + """Constructor of Userright class. + + :param `Offer` offer: a offer object + :param integer default: default value + """ + + self.offer = offer + self.default = default + + @property + def bill(self): + """returns a list of unbilled messages grouped by Job.info""" + query = [ func.count(Message.id).label('anz'), # anz of messages + func.sum(Message.price).label("price"), # price of the messages + Job.info.label('info'), # info tag + ] + + filters = [ Message.isBilled==False, # only unbilled messages + Job.user==self.user, # only jobs connected to user + Message.offer==self.offer, # only messages in the right offer + Message.job_id==Job.id, # join Message and Job + ] + return object_session(self).query(*query).filter(and_(*filters)).group_by(Job.info) + +class Offer(Base): + """All possible Offers over a Message can be sended. **provider**, **typ** and **route** are used to get the data form configuration file.""" + __tablename__ = "offer" + name = Column(String(100), primary_key=True) + """name of the offer""" + provider = Column(String(100)) + """provider name""" + route = Column(String(100)) + """route of the provider""" + typ = Column(String(100)) + """typ of message""" + + def __init__(self, **kwargs): + Base.__init__(self,**kwargs) + + @classmethod + def get(cls, session, provider, route, typ): + """returns a Offer object.""" + return session.query(cls).filter(and_(cls.provider==provider, cls.route==route, cls.typ==typ)).first() + + @classmethod + def routes(cls, session, typ): + """returns a query object of all possible offers. + + :param string typ: get all offers that support this typ + """ + return session.query(cls).filter_by(typ=typ) + + @classmethod + def typs(cls, session): + """returns a list of all possible types. + """ + return session.query(cls.typ).distinct() + + @classmethod + def providers(cls, session, typ): + """returns a list of all possible providers. + + :param string typ: get all providers that support this typ + """ + return session.query(cls.provider).filter_by(typ=typ).distinct() + +class Message(Base): + """A message that has created costs. + + **isBilled** is False since the bill is paid. + """ + __tablename__ = "message" + id = Column(Integer, Sequence('message_id_seq'), primary_key=True) + """primary key of the message""" + + recipient = Column(String(100)) + """string representation of the recipient""" + + isBilled = Column(Boolean) + """is bill paid?""" + + date = Column(DateTime) + """date of sending the message""" + + price = Column(Numeric(8,4)) + """price of sending the message""" + + count = Column(Integer) + """Count of sended messages""" + exID = Column(String(100)) + """external API id """ + + job_id = Column("job", String(40), ForeignKey('job.id')) + """id of the connected job""" + job = relationship("Job", backref=backref('messages')) + """connected :class:`Job` object""" + + offer_id = Column("offer",String(100), ForeignKey('offer.name')) + """sended message over this offer woth ithe offer.name""" + + offer = relationship("Offer", backref=backref('messages')) + """connected :class:`Offer` object""" + + def __init__(self, **kwargs): + Base.__init__(self,**kwargs) + + +class Job(Base): + """A complete Job. + + - **status** show the status of the job (``init``, ``started``, ``sending``, ``sended`` or ``error``). + - **info** is used to make it possible to create different billing groups for user. + """ + __tablename__ = "job" + id = Column(Integer, Sequence('job_id_seq'), primary_key=True) + """job id""" + info = Column(String(100)) + """job info, for billing porpuse""" + status = Column(Enum("init","started","sending","sended","error")) + """status of a job""" + user_id = Column("user", String(100), ForeignKey('apiuser.name')) + """connected user id""" + + user = relationship("User", backref=backref('jobs')) + """connected :class:`User` object""" + + def __init__(self, **kwargs): + """ + .. automethod:: __repr__ + """ + Base.__init__(self,**kwargs) + + @property + def extend(self): + """returns the connected :class:`iro.model.job.ExJob`""" + return job.exJobs[self.id] + + + def __repr__(self): + """string representation of the Job class. + + :return: ```` + """ + try: + return ""%(self.id,self.info, self.status, self.user_id) + except DetachedInstanceError: + return Base.__repr__(self) + + @classmethod + def get(cls, session, id): + """returns a job object from a given id""" + return session.query(cls).filter_by(id=id).first() + +class User(Base): + """An user in iro.""" + __tablename__ = "apiuser" + + name = Column(String(100), primary_key=True) + """Username""" + + ng_kunde = Column(Integer) + """Connection to the netzguerilla userdatabase""" + + apikey = Column(String(50),unique=True) + """apikey only [0-9a-f]""" + + rights = relationship('Userright') + """all allowed offers to send with.""" + + def __init__(self, name, apikey): + """Constructor of User class. + + :param string name: username + :param string apikey: apikey for the user + + .. automethod:: __repr__ + """ + self.name=name + self.apikey=apikey + + def __repr__(self): + """string representation of the user class. + + :return: ```` + """ + try: + return ""%(self.name,self.apikey) + except DetachedInstanceError: + return Base.__repr__(self) + + def routes(self, typ, default = False): + """returns a query object to get all possible routes for a given typ + + :param string typ: the typ + :param boolean default: use only default routes + """ + filters=[User.name == self.name, + Offer.typ == typ, + ] + if default: + filters.append(Userright.default != None) + return object_session(self).query(Userright.offer_name).join(Offer,User).filter(and_(*filters)).order_by(Userright.default) + + def providers(self, typ, default = False): + """return a query object for all possible providers for a given typ + + :param string typ: the typ + :param boolean default: use only default routes + """ + filters=[User.name == self.name, + Offer.typ == typ, + ] + if default: + filters.append(Userright.default != None) + return object_session(self).query(Offer.provider).join(Userright,User).filter(and_(*filters)) + + def has_right(self, typ, offer_name = None, provider = None, route = None): + """if a user has the right to use a offer, provider e. al. (arguments are and connected). + + :param string typ: the typ + :param string offer_name: offer name + :param string provider: provider name + :param string route: a route name + :return: offer_name or None (not allwoed) + :raises: :class:`sqlalchemy.orm.exc.MultipleResultsFound` if not a single offer match""" + filters=[User.name == self.name, + Offer.typ == typ, + ] + if offer_name: + filters.append(Userright.offer_name==offer_name) + if provider: + filters.append(Offer.provider==provider) + if route: + filters.append(Offer.route==route) + + return object_session(self).query(Userright.offer_name).join(Offer,User).filter(and_(*filters)).scalar() + + def job(self, id): + """returns a job object. + + :param integer id: id of a Job + :return: :class:`Job` + :raises: :exc:`iro.error.JobNotFound` + """ + job = object_session(self).query(Job).join(User).filter(and_(User.name == self.name, Job.id==id)).first() + if job is None: + raise JobNotFound() + return job diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/status.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/status.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,37 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +class Status: + '''status object -- the resulat of one :class:`iro.controller.task.Task`.''' + def __init__(self, provider, route, costs=0.0, count=0, exID=None): + """ + :param `iro.offer.provider.Provider` provider: a provider object + :param string route: a route of the provider + :param `decimal.Decimal` costs: costs for sending this message + :param integer count: count of sended messages + :param string exID: ID of external API + """ + self.provider = provider + self.route = route + + self.costs = costs + self.count = count + self.exID = exID diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/user.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/user.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,74 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.internet import defer +try: + from inspect import getcallargs +except ImportError: + from ..inspect_getcallargs import getcallargs + +from decorator import decorator + +from .schema import User +from .dbdefer import dbdefer + +from ..validate import validate, vHash +from ..error import UserNotFound, InterfaceException + + +@validate(kwd="apikey", func=vHash, minlength=15, maxlength=15) +@dbdefer +def getuser(session, apikey): + """Returns a valid user object. + + :param session: a valid session object (is created by decorator :func:`iro.model.dbdefer.dbdefer`) + :param string apikey: a vaild apikey (only [0-9a-f] is allowed) + :return: a :class:`iro.model.schema.User` object + :raises: :exc:`iro.error.UserNotFound` + """ + user = session.query(User).filter_by(apikey=apikey).first() + if user is None: + raise UserNotFound() + else: + return user + +@decorator +def vUser(f,*args,**kargs): + """A Decorator to verify the apikey and execute the function with a valid :class:`iro.model.schema.User` instead of the apikey. + + The decorator expect the apikey in the **user** parameter. + + :return: a defer + """ + kp=getcallargs(f,*args,**kargs) + try: + apikey = kp["user"] + except KeyError: + raise InterfaceException() + + def _gotResult(_user): + kp["user"]=_user + e = defer.maybeDeferred(f,**kp) + return e + d = defer.maybeDeferred(getuser, apikey=apikey) + return d.addCallback(_gotResult) + + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/utils.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,51 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from sqlalchemy.orm import sessionmaker + +class WithSession(object): + '''a with statement for a database session connection.''' + def __init__(self, engine, autocommit=False): + """ + :param `sqlalchemy.engine.base.Engine` engine: a valid sqlalchemy engine object (normally created via :func:`sqlalchemy.create_engine`). + :param boolean autocommit: autocommit after running the function. + + .. automethod:: __enter__ + .. automethod:: __exit__ + """ + self.engine = engine + self.autocommit=autocommit + + def __enter__(self): + """returns a vaild session object.""" + self.session = sessionmaker(bind=self.engine)() + return self.session + + def __exit__(self,exc_type, exc_value, traceback): + if exc_type is None: + if self.autocommit: + self.session.commit() + else: + self.session.rollback() + self.session.close() + + + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/newuser.py --- a/iro/newuser.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -class User: - ''' - class for a xmlrpc user - ''' - - def status(self, apikey, id=None, detailed=False): - u'''Gibt den aktuellen Status eines Auftrages zurück. - - Keywords: - apikey[string]: Der API Key - id[hash]: Eine Auftragsnummer - detailed[boolean]: Details ausgeben - - Return: - jobs[list]: Eine Liste der Aufträge. - job.name[string]: Angebener Name - job.status[string]: Status des Auftrages - - - ''' - pass - - def stop(self, apikey,id): - u'''Stoppt den angegeben Auftrag. - - Keywords: - apikey[string]: Der API Key - id[hash]: Eine Auftragsnummer - - Return: - - ''' - pass - - def sms(self, apikey, message, recipients, route="default"): - u'''Versendet eine SMS. - - Keywords: - apikey[string]: Der API Key - message[string]: Nachricht - recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123) - route[string|list]: Route über den geschickt werden soll, - oder eine Liste von Routen, um Fallbacks anzugeben - - Return: - id[hash]: Die ID des Auftrages - - ''' - pass - - - def fax(self, apikey, subject, fax, recipients, route="default"): - u'''Versendet ein FAX. - - Keywords: - apikey[string]: Der API Key - subject[string]: Der Betreff - fax[string]: Das PDF base64 kodiert - recipients[list]: Eine Liste von Emfänger-Nummern (gemäß ITU-T E.123) - route[string|list]: Route über den geschickt werden soll, - oder eine Liste von Routen, um Fallbacks anzugeben - - Return: - id[hash]: Die ID des Auftrages - - ''' - pass - - def mail(self, apikey, subject, body, recipients, frm, route="default"): - u'''Versendet eine Email. - - Keywords: - apikey[string]: Der API Key - subject[string]: Der Betreff - body[string]: Der Email Body - recipients[list]: Eine Liste von Emailadressen - frm[string]: Die Absender Emailadresse - route[string|list]: Route über den geschickt werden soll, - oder eine Liste von Routen, um Fallbacks anzugeben - - Return: - id[hash]: Die ID des Auftrages - - ''' - pass - - def routes(self, apikey, typ): - u'''Gibt eine Liste aller verfügbaren Provider zurück. - - Keywords: - apikey[string]: Der API Key - typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll - Einer der Liste ["sms","fax","mail"] - - Return: - providerlist[list]: Eine Liste aller möglichen Provider - - ''' - pass - - def defaultRoute(self, apikey, typ): - u'''Gibt den Standardprovider zurück. - - Keywords: - apikey[string]: Der API Key - typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll - Einer der Liste ["sms","fax","mail"] - - Return: - provider[string]: Der Standardprovider für den angeben Typ - - - ''' - pass - - def statistic(self,apikey): - u'''Gibt eine Statik zurück über die versendendeten Nachrichten und des Preises. - - Keywords: - apikey[string]: Der API Key - - Return: - statistic[list]: Eine Liste nach Nachrichtentypen - ''' - pass diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/offer/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/offer/__init__.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,36 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +"""Package all about providers. This packages handles the code for sending a message through a provider. + +- :attr:`iro.offer.provider.providers` -- a dict of all available providers +- :func:`iro.offer.provider.getProvider` -- returns a object, from a provider name +- :class:`iro.offer.provider.Provider` -- Baseclass for all providers +- :class:`iro.offer.smtp.SMTP` -- a provider to send emails over smtp +- :class:`iro.offer.smstrade.Smstrade` -- a provider to send sms via http://smstrade.de +- :class:`iro.offer.sipgate.Sipgate` -- a provider to send sms and fax via http://sipgate.de +- :class:`iro.offer.offer.Offer` -- a represantation of an offer, this is used to send (see :mod:`iro.model.offer`). +""" +from .offer import Offer +from .provider import Provider, providers, getProvider +from .smtp import SMTP +from .smstrade import Smstrade +from .sipgate import Sipgate diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/offer/offer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/offer/offer.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,67 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +class Offer(): + """One Offer for sending. + This class is used to send a message via a provider. + """ + def __init__(self, name, provider, route, typ): + """ Constructor for Offer class. + + :param string name: name is the name in database for the offer + :param `iro.offer.provider.Provider` provider: A valid provider object. + :param sting route: used to the the right send function via :meth:`iro.offer.provider.Provider.getSendFunc`. + :param sting typ: used to the the right send function via :meth:`iro.offer.provider.Provider.getSendFunc`. + + + .. automethod:: __call__ + .. automethod:: __eq__ + .. automethod:: __neq__ + .. automethod:: __repr__ + """ + self.name = name + self.route = route + self.provider = provider + self.typ = typ + self.sendfunc = provider.getSendFunc(typ, route) + + def __call__(self, recipient, message): + """send a message to a recipient. This method uses :meth:`iro.offer.provider.Provider.send` + + :param recipient: one recipient + :param `iro.model.message.Message` message: message to send + """ + return self.sendfunc(recipient, message) + + def __eq__(self,o): + """return ``True``, if o is equal.""" + return (self.name == o.name) and (self.route == o.route) and (self.provider == o.provider) and (self.typ == o.typ) + + def __neq__(self,o): + """return ``True``, if ``o`` is not equal (see :meth:`__eq__`).""" + return not self.__eq__(o) + + def __repr__(self): + """string representation of this class for debugging purpose. + + :return: ```` """ + return ""%(self.name,self.provider,self.route,self.typ) + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/offer/provider.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/offer/provider.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,113 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from functools import partial +try: + from collections import OrderedDict +except ImportError: + from ordereddict import OrderedDict + +from ..error import NoRoute, NoTyp, ValidateException, NoProvider +from ..config import Option, Config + +class Provider(Config): + """Base class for Providerbackends.""" + + testmode = False + """- **True** -- no message to external provider should be send. + - **False** (default) -- message are send to external provider.""" + def __init__(self, name, typs={}, options=[]): + """Constructor for Provider class. + + :param string name: Name of the Provider. + :param dict typs: A Dictonary with typs and routes. + :param items options: [("name",Option(...)),...] + + >>> p = Provider("myProvider",{"sms":["route1","route2"]}) + """ + + Config.__init__(self, name, OrderedDict([ + ("typ",Option(vProvider, long="One available provider typ.", must=True, default=name)) + ]+options) + ) + self.typs=typs + self.testmode = False + + def send(self, typ, route, recipient, message): + """Main send function, that is called, for every single message. + + .. note:: + This function is not used directly. Normally :func:`~iro.offer.provider.Provider.getSendFunc` return this function with typ and route bound.""" + pass + + def getSendFunc(self, typ, route): + """Returns the actually send function for a given typ and route. + + Normally it returns the send function with typ and route bound. + + :raises: :exc:`~iro.error.NoRoute`, :exc:`~iro.error.NoTyp` + """ + + try: + if route not in self.typs[typ]: + raise NoRoute(route) + except KeyError: + raise NoTyp(route) + return partial(self.send, typ, route) + +def getProvider(name, typ, config): + '''creates a provider object and init this with config. + + :param dict config: The Configuration dict. Normally you use ``configParser.items("section")``. + :param string typ: A valid typ + :raises: :exc:`~iro.error.NoProvider` + ''' + try: + p = providers[typ](name) + p.load(config) + return p + except KeyError: + raise NoProvider(typ) + +def vProvider(typ, field): + '''validator to test the existence of the typ. + + :param string typ: A typ + :param string field: A field name used for the Exception. + :return: + - valid -- returns typ + - invalid -- raises :class:`~iro.error.ValidateException` + + :raises: :exc:`~iro.error.ValidateException` + ''' + if typ not in providers.keys(): + raise ValidateException(field=field) + return typ + +providers={} +"""Avalable Providers. + - **key** -- typ of provider (see configuration typ field). + - **value** -- class to handle specific Providertyp. + +To extend provider typs, just add this new typ to this dict. +see :doc:`provider` + +""" diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/offer/sipgate.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/offer/sipgate.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,139 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from functools import partial +from twisted.web.xmlrpc import Proxy +import xmlrpclib +from decimal import Decimal + +from .provider import providers, Provider +from ..config import Option +from ..validate import vFloat +from ..model.status import Status +from ..error import NoTyp +from .. import __version__ + + +class Sipgate(Provider): + """ + s. auch http.tuxad.com/sipgate.html + und http://lists.sipgate.net/pipermail/sipgate-developers/2007-September/000016.html + + http://www.sipgate.de/beta/public/static/downloads/basic/api/sipgate_api_documentation.pdf + + See :doc:`provider` for a tutorial, how to create a new provider backend. This turorial implements this provider. + """ + + url="https://%s:%s@samurai.sipgate.net/RPC2" + """XML-RPC url for sipgate""" + + def __init__(self,name): + options =[("username", Option(lambda x,y: x,long="Loginname for sipgate", must=True)), + ("password", Option(lambda x,y: x,long="Password for sipgate", must=True)), + ("price_sms", Option(vFloat, long="price for one sms", default="0.079")), + ("price_fax", Option(vFloat, long="price for one fax", default="0.03")), + ] + Provider.__init__(self, name, {"sms" : [None], "fax":[None]}, options) + + def proxy(self): + """returns a XML-RPC proxy object to sipgate API.""" + return Proxy(self.url%(self.username, self.password)) + + def load(self,cfg): + """Loads configuration into object. + + :param dict cfg: The Configuration dict. Normally you use ``configParser.items("section")``. + """ + + Provider.load(self,cfg) + #return self.clientIdentify() + + def clientIdentify(self): + """identificaton of client to sipgate server.""" + args_identify = { + "ClientName" : "Iro", + "ClientVersion" : __version__, + "ClientVendor" : "netzguerilla.net" + } + + return self.proxy().callRemote("samurai.ClientIdentify", args_identify) + + def sms(self, recipient, sms): + """send one SMS to recimpient. + + :param `iro.telnumber.Telnumber` recipient: mobilenumber of recipient + :param `iro.model.message.SMS` sms: the sms to send + :return: a deferrer + """ + args={ + "TOS" : "text", + "Content" : sms.getContent(), + "RemoteUri" : "sip:%s%s@sipgate.net"%(recipient.land, recipient.number), + } + return self.proxy().callRemote("samurai.SessionInitiate",args) + + def fax(self, recipient, fax): + """send one fax to recimpient. + + :param `iro.telnumber.Telnumber` recipient: faxnumber of recipient + :param `iro.model.message.Fax` fax: the fax to send + :return: a deferrer. + """ + + args={ + "TOS" : "fax", + "Content" : xmlrpclib.Binary(fax.getAttachment(0)), + "RemoteUri" : "sip:%s%s@sipgate.net"%(recipient.land, recipient.number), + } + return self.proxy().callRemote("samurai.SessionInitiate",args) + + def _status(self,value,typ): + """returns a :class:`~iro.model.status.Status` object. + :raises: :exc:`iro.error.NoTyp` + """ + if typ not in self.typs.keys(): + raise NoTyp(typ) + return Status(self, None, Decimal(getattr(self,"price_"+typ)), 1, value["SessionID"]) + + def send(self, typ, recipient, msg): + """send msg to recipient. + + :param string typ: Typ of message. + :param `iro.telnumber.Telnumber` recipient: telnumber of recipient + :param msg: the msg to send + :type msg: :class:`iro.model.message.Fax` or :class:`iro.model.message.SMS` + :return: a deferrer, that returns a :class:`~iro.model.status.Status` object + :raises: :exc:`iro.error.NoTyp` + """ + if typ not in self.typs.keys(): + raise NoTyp(typ) + d = getattr(self,typ)(recipient, msg) + d.addCallback(self._status, typ) + return d + + def getSendFunc(self, typ, route): + """returns :meth:`send` method with bound typ, if typ and route is valid.""" + + Provider.getSendFunc(self, typ, route) + return partial(self.send, typ) + +providers["sipgate"] = Sipgate + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/offer/smstrade.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/offer/smstrade.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,190 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +import urllib +from functools import partial +from decimal import Decimal +#import copy + +from ..config import Option +from ..model.status import Status +from .provider import Provider, providers +from ..error import RejectRecipient, ExternalException + +#import logging +#logger=logging.getLogger("smstrade") + +statusCodes = {10 : "Empfaengernummer nicht korrekt.", + 20 : "Absenderkennung nicht korrekt.", + 30 : "Nachrichtentext nicht korrekt.", + 31 : "Messagetyp nicht korrekt.", + 40 : "SMS Route nicht korrekt.", + 50 : "Identifikation fehlgeschlagen.", + 60 : "nicht genuegend Guthaben.", + 70 : "Netz wird von Route nicht abgedeckt.", + 71 : "Feature nicht ueber diese Route moeglich.", + 80 : "Uebergabe an SMS-C fehlgeschlagen.", + 90 : "Versand nicht moeglich.", + 100 : "SMS wurde versendet.", + } +"""statuscodes of external smstrade API""" + + +class SmstradeException(ExternalException): + """An excetion that connects the status code with the excetion string (see :attr:`statusCodes`)""" + def __init__(self,status): + ExternalException.__init__(self) + self.status = status + self.str_=str(status) + + def __str__(self): + return "%s\n%s"%(ExternalException.__str__(self),self.str_) + + +class StatusCode: + """Class that represents the output of one smstrade request.""" + def __init__(self,code, exID=None, costs=Decimal("0.0"), count=0): + self.code = code + + self.exID = exID + self.costs = Decimal(costs) + self.count = int(count) + + def __str__(self): + if self.code in statusCodes.keys(): + return "%i: %s"%(self.code, statusCodes[self.code]) + + return "%i: unknown statuscode."%self.code + + def __int__(self): + return self.code + +class Smstrade(Provider): + """A Provider to send SMS to recipients using smstrade. + Smstrade only supports to send SMS and four diffrent routes: ``["basic","economy","gold","direct"]``. + + It needs a smstrade Gateway Key https://login.smstrade.de/index.php?gateway in configuration file. + + smstrade API documentation: http://kundencenter.smstrade.de/sites/smstrade.de.kundencenter/__pdf/SMS-Gateway_HTTP_API_v2.pdf + + The smstrade API supports a debug mode, that can be set with :attr:`~iro.offer.provider.Provider.testmode`. + """ + _params= {"debug":("boolean",False), + "concat_sms":('boolean',False), + "message_id":('boolean',False), + "count":('boolean',False), + "cost":('boolean',False), + } + '''dict for standrd values of the smstrade api, it is used to get the right values to the API.''' + + def __init__(self, name): + self.url = "https://gateway.smstrade.de" + options =[("key", Option(lambda x,y:x,long="smstrade Gateway Key https://login.smstrade.de/index.php?gateway", must=True)),] + Provider.__init__(self, name, {"sms":["basic","economy","gold","direct"]},options) + + def send(self, route, recipient, sms): + """send one SMS to recipient via route + + :param string route: A valid route ``["basic", "economy", "gold", "direct"]`` + :param `iro.telnumber.Telnumber` recipient: Mobilenumber of recipient + :param `iro.model.message.SMS` sms: the sms to send + :return: + - All went ok -- :class:`iro.model.status.Status` object + - otherwise -- an exception + """ + #logger.debug('smstrade.sendSMS(%s,%s)'%(sms, recipient)) + + route = unicode(route) + + if recipient.land != '49' and route == "basic": + raise RejectRecipient(recipient) + + to ='00'+recipient.land+recipient.number + + s = self.__send(route, to, sms) + if int(s) in (100,): + return Status(self,route, exID=s.exID, costs=s.costs, count=s.count) + elif int(s) in (70,71,): + raise RejectRecipient(recipient, status=s) + else: + raise SmstradeException(s) + + def __send(self, route, to, sms): + """ This is the main function to request to the sms service. + + :param string route: A valid route ``["basic", "economy", "gold", "direct"] + :param string recipient: Mobilenumber of recipient + :param `iro.model.message.sms` sms: the sms to send + :return: a :class:`.StatusCode` object + """ + + #logger.debug('smstrade._send(%s,%s,%s)'%( route, to, sms)) + parameters= {"key": self.key, + "route": route, + "to": to, + "message": sms.content, + "charset":"utf-8", + "debug": self.testmode, + "message_id":True, + "count":True, + "cost":True, + } + + doubleChar="€[]{}|\\^~" #these charactar need two GSM Chars + + if sms.from_ is not None: + parameters["from"] = sms.from_ + + length=len(sms.content) + for s in doubleChar: + length += sms.content.count(s) + parameters["concat_sms"] = True if length > 160 else False + + ps={} + for p in parameters: + if p in self._params.keys(): + if self._params[p][0] == "boolean": + if parameters[p] != self._params[p][1]: + ps[p]=int(bool(parameters[p])) + else: + ps[p] = parameters[p] + + params = urllib.urlencode(ps) + #dp=copy.deepcopy(ps) + #dp["key"]="" + #print 'smstrade._send-parameters:%s\n\t->%s'%(str(dp), urllib.urlencode(dp)) + + response = urllib.urlopen(self.url, params) + data = response.readlines() + if len(data) == 1: + return StatusCode(int(data[0])) + return StatusCode(int(data[0]),exID=data[1],costs=data[2],count=data[3]) + + def getSendFunc(self, typ, route): + """returns a partial :meth:`send` methed with bounded route, if typ and route is valid.""" + + Provider.getSendFunc(self, typ, route) + return partial(self.send,route) + +providers["smstrade"]=Smstrade diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/offer/smtp.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/offer/smtp.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,94 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- + +import smtplib +import copy +from functools import partial + +from ..validate import vInteger, vEmail,vBool +from ..model.status import Status +from ..config import Option +from .provider import Provider, providers + +class SMTP(Provider): + """A SMTP Provider to send emails. + This Provider has only one typ ``"mail"`` and one route ``None``. + + If :attr:`~iro.offer.provider.Provider.testmode` is **True** no mail will be send, only a connection is created to server. + + """ + def __init__(self, name): + options = [ + ("host", Option(lambda x,y: x, long="Hostname of MTA", must=True)), + ("port", Option(partial(vInteger,minv=0),long="Port of the MTA", default=25)), + ("user", Option(lambda x,y: x, long="username to login into MTA.",default="")), + ("password", Option(lambda x,y: x, long="password to login into MTA.",default="")), + ("SSL", Option(vBool,long="use SSL for connection to MTA", default=False)), + ("TLS", Option(vBool,long="use TLS for connection to MTA", default=False)), + ("send_from", Option(vEmail,long="Emailaddress from which mail will be sended.",must=True)), + ] + Provider.__init__(self,name,{"mail":[None]},options) + + def send(self, recipient, mail): + """sends a mail to recipient + + :param string recipient: A valid email address. + :param `iro.model.message.Mail` mail: Mail to send. + :return: + - All went ok -- :class:`iro.model.status.Status` object + - otherwise -- an exception + """ + if not self.testmode: + if self.SSL: + smtp = smtplib.SMTP_SSL(self.host,self.port) + else: + smtp = smtplib.SMTP(self.host,self.port) + + if self.TLS: + smtp.starttls() + + if not self.user == "": + smtp.login(self.user,self.password) + try: + frm=self.send_from + + if mail.getFrom(): + frm = mail.getFrom() + + tmpmail=copy.deepcopy(mail) + tmpmail.content['From'] = frm + tmpmail.content['To'] = recipient + if not self.testmode: + smtp.sendmail(frm, recipient, tmpmail.as_string()) + return Status(self, None) + finally: + smtp.quit() + + def getSendFunc(self, typ, route): + """returns :meth:`send` method, if typ and route is valid.""" + + Provider.getSendFunc(self, typ, route) + return self.send + +providers["smtp"]=SMTP + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/providerlist.py --- a/iro/providerlist.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . - -class Providerlist: - def __init__(self): - self.provider={} - self.types={} - self.defaults={} - - def add(self, name, provider, typeslist): - self.provider[name]={"name":name, "class":provider, "types":typeslist} - for stype in typeslist: - try: - self.types[stype].append(self.provider[name]) - except KeyError: - self.types[stype]=[self.provider[name]] - - - def setDefault(self, stype, name): - self.defaults[stype]=self.provider[name] - - def getDefault(self, stype): - return self.defaults[stype] - - def getProviderlist(self, stype): - llist=[ provider["name"] for provider in self.types[stype] ] - llist.sort() - return llist - - def status(self): - ret="provider:%s"%self.provider - ret +="\ntypes:%s"%self.types - return ret+"\ndefaults:%s"%self.defaults - - def getProvider(self, stype, name="default"): - if name=="default": - return self.getDefault(stype)["class"] - - if not stype in self.provider[name] ["types"]: - raise Exception("argh") - - return self.provider[name]["class"] diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/telnumber.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/telnumber.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,114 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- +import re + +from .error import InvalidTel + +class Telnumber: + """A telefonnumer, with splitted country part""" + re_telnum=re.compile(r'^\s*(\+)?([0-9\s\-/\(\)]){5,}\s*$') + """Regex for a complete telefon number""" + + re_land=re.compile(r'^\s*(\+|00)(?P[1-9]{2})') + """Regex for country part""" + + re_number=re.compile(r'[^0-9]') + """Regex for numbers""" + + std_land="49" + """Standard country part""" + + def __init__(self,number=None): + """ + :param string number: a telefonnumber + + .. automethod:: __eq__ + .. automethod:: __neq__ + .. automethod:: __hash__ + .. automethod:: __str__ + .. automethod:: __repr__ + + """ + self.land = None + """Country part of a telefonnumber""" + + self.number = None + """Localpart of the telefonnumber""" + + if not(number is None): + self.createNumber(number) + + def createNumber(self, number): + """Split string into two parts: one country part and the rest. + + For a local number :attr:`std_land` will be used for country part. + + :param string number: a telefonnumber + """ + if not self.re_telnum.match(number): + raise InvalidTel(number) + + + self.land=self.std_land + + land=self.re_land.match(number) + + if land: + self.land=land.group("land") + number=number[land.end("land"):] + + number=self.re_number.sub('',number) + + if number[0]=="0": + number=number[1:] + + self.number = number + + def __eq__(self, y): + """Return ``True``, if y hase the same telefonnumber""" + return ((self.number == y.number) and ( self.land == y.land)) + + def __neq__(self, y): + """Return ``True``, if y is not equal (see :meth:`__eq__`)""" + return not self.__eq__(y) + + def __hash__(self): + """Return the Hash for telefonnumbers. + + Yust use the hash of the string representation of the Number + """ + return str(self).__hash__() + + def __str__(self): + """String representation of the telefonnumber. + + :return: the international telefonnumber with leading zeros + """ + return "00%s%s"%(self.land,self.number) + + def __repr__(self): + """debug representation of the class. + + :return: `` + """ + return ""%str(self) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/test_helpers/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/test_helpers/__init__.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,20 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/test_helpers/dbtestcase.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/test_helpers/dbtestcase.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,104 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.trial import unittest + +from sqlalchemy import create_engine, pool +from tempfile import mkdtemp + +from shutil import rmtree +import atexit +from ngdatabase.mysql import Server, createConfig, Database + +from iro.model import setEngine, setPool +from iro.model.utils import WithSession +from iro.model.schema import Base + +from iro.controller.pool import dbPool + +from .utils import DummyObserver + +class DBTestCase(unittest.TestCase): + '''a TestCase with DB connection + you have to set self.session manually in setUp''' + def __init__(self,*args,**kwargs): + unittest.TestCase.__init__(self,*args,**kwargs) + self.engine = md.engine + + def setUp(self): + md.setUp() + self.log = DummyObserver() + self.log.start() + + def tearDown(self): + self.log.stop() + self.__cleanDB() + + def session(self,autocommit=True): + '''returns a WithSession''' + return WithSession(self.engine,autocommit) + + def __cleanDB(self): + '''cleaning database''' + with self.session() as session: + for table in reversed(Base.metadata.sorted_tables): + session.execute(table.delete()) + + +class SampleDatabase(Database): + def createPassword(self): + self.password="test" + return self.password + +class ModuleData(object): + def __init__(self): + self.create() + + def close(self): + if self.valid: + self.server.stop() + rmtree(self.tdir) + self.valid= False + + def create(self): + self.tdir = mkdtemp(prefix='iro-mysql-') + self.server = Server('%s/my.cnf'%self.tdir) + self.db = SampleDatabase("iro","test",'%s/my.cnf'%self.tdir) + self.dburl = 'mysql://test:test@localhost/iro?unix_socket=%s/socket'%self.tdir + self.engine = create_engine(self.dburl, + poolclass = pool.SingletonThreadPool, pool_size=dbPool.maxthreads, )#echo=True) + self.valid = False + + def setUp(self): + if not self.valid: + with open('%s/my.cnf'%self.tdir,'w') as cnf: + cnf.write(createConfig(self.tdir)) + self.server.create() + self.server.start() + self.db.create() + Base.metadata.create_all(self.engine) + setEngine(self.engine) + setPool(dbPool) + self.valid = True + + +md=ModuleData() +atexit.register(md.close) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/test_helpers/smtp_helper.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/test_helpers/smtp_helper.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,49 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +""" +A helper class which allows test code to intercept and store sent email. +(from: http://lakin.weckers.net/thoughts/twisted/part1/threaded/) +""" +import smtpd +import asyncore + +class TestSMTPServer(smtpd.SMTPServer): + """ + An SMTP Server used to allow integration tests which ensure email is sent. + """ + + def __init__(self, localaddr): + self.rcvd = [] + smtpd.SMTPServer.__init__(self, localaddr, None) + + def start(self): + import threading + self.poller = threading.Thread(target=asyncore.loop, + kwargs={'timeout':0.1, 'use_poll':True}) + self.poller.start() + + def process_message(self, peer, mailfrom, rcpttos, data): + self.rcvd.append((mailfrom, rcpttos, data)) + + def close(self): + smtpd.SMTPServer.close(self) + self.poller.join() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/test_helpers/utils.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/test_helpers/utils.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,39 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.python import log + +class DummyObserver(object): + def __init__(self): + self.e=[] + + def start(self): + log.addObserver(self.emit) + + def stop(self): + log.removeObserver(self.emit) + + def emit(self, eventDict): + self.e.append(eventDict) + +class DummyPool(): + def run(self, f,*a,**k): + return f(*a,**k) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/__init__.py --- a/iro/tests/__init__.py Wed Dec 21 22:07:48 2011 +0100 +++ b/iro/tests/__init__.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,32 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import db +import config +import email_validate, telnumber, validate, model_validate +import offer, job, task +import smtp, smstrade, offer_integrated + +import reload, xmlrpc +import install +import viewinterface + +__all__=[db] diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/config.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/config.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,230 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from mock import patch, Mock +from twisted.trial import unittest +import signal +import io +import ConfigParser +from functools import partial + +from iro import config, error, validate +from iro.config import OrderedDict +from iro.offer.provider import Provider, providers + +class TestModuleConfig(unittest.TestCase): + '''test config class''' + + def setUp(self): + self._reloadList=config.configParser.reloadList + config.configParser.reloadList=[] + + def tearDown(self): + config.configParser.reloadList = self._reloadList + + @patch('iro.config.main') + @patch('iro.config.configParser') + def testReadConfig(self,pConfig,pMain): + pMain._init = True + config.readConfig() + self.assertEqual([i[0] for i in pConfig.method_calls],["read","reload_","items"]) + pConfig.read.assert_called_once_with(config.confFiles) + pConfig.items.assert_called_once_with("main") + pConfig.reload_.assert_called_once_with() + self.assertEqual(pMain.load.called,1) + + @patch('iro.config.main') + @patch('iro.config.configParser') + def testReadConfigInit(self,pConfig,pMain): + pConfig.items.return_value = [('dburl',''),("port",1000)] + pMain._init = False + config.readConfig() + self.assertEqual([i[0] for i in pConfig.method_calls],["read","reload_","items"]) + self.assertEqual([i[0] for i in pMain.method_calls],["same"]) + sa = pMain.same.call_args_list[0][0][0] + self.assertEqual(sa.port,1000) + self.assertEqual(sa.dburl,'') + pMain.same.return_value = False + self.assertRaises(Exception,config.readConfig,) + + @patch('iro.config.main') + @patch('iro.config.configParser') + def testInit(self,pConfig,pMain): + pMain._init = False + config.init() + self.assertEqual([i[0] for i in pConfig.method_calls],["read", "items"]) + pConfig.read.assert_called_once_with(config.confFiles) + pConfig.items.assert_called_once_with("main") + self.assertEqual(pMain.load.called,1) + + + @patch('signal.signal') + @patch('iro.config.readConfig') + def testRegisterSignal(self, pReadConfig, pSignal): + config.registerSignal() + self.assertEqual(pSignal.call_args[0][0],signal.SIGUSR2) + self.assertEqual(pReadConfig.called,0) + pSignal.call_args[0][1](None, None) + pReadConfig.assert_called_once_with() + + def testRegisterReload(self): + def x(): + pass + config.configParser.registerReload(x) + self.assertEqual(config.configParser.reloadList,[x]) + + def testReload(self): + x = Mock() + config.configParser.reloadList = [x] + config.configParser.reload_() + x.assert_called_once_with() + + +class TestRead(unittest.TestCase): + + + + def tearDown(self): + for s in config.configParser.sections(): + config.configParser.remove_section(s) + + @patch('iro.config.ConfigParser.read') + def testMain(self,pRead): + sample_config = """[main] +port = 8000 +dburl = sdfdsafgsfdg +""" + config.configParser.readfp(io.BytesIO(sample_config)) + config.configParser.read([]) + pRead.assert_called_once_with(config.configParser,[]) + + @patch('iro.config.ConfigParser.read') + def testMainBadPort(self,pRead): + sample_config = """[main] +port = -8000 +dburl = sadfaserasg +""" + config.configParser.readfp(io.BytesIO(sample_config)) + self.assertRaises(error.ValidateException, config.configParser.read, []) + + @patch('iro.config.ConfigParser.read') + def testMainNoMust(self,pRead): + sample_config = """[main] +port = 8000 +""" + config.configParser.readfp(io.BytesIO(sample_config)) + self.assertRaises(config.NeededOption, config.configParser.read, []) + + @patch('iro.config.ConfigParser.read') + def testMust(self,pRead): + v=Mock() + config.main.options["test"] = config.Option(v,default="jehei") + try: + sample_config = """[main] +dburl = sdfawersdf +port = 8000 +""" + config.configParser.readfp(io.BytesIO(sample_config)) + config.configParser.read([]) + self.assertEqual(v.called,0) + config.main.load(config.configParser.items("main")) + self.assertEqual(config.main.test,'jehei') + sample_config = """[main] +dburl = adfgsdftsfg +port = 8000 +test = foohu + """ + config.configParser.readfp(io.BytesIO(sample_config)) + config.configParser.read([]) + v.assert_called_once_with("foohu","test") + finally: + del(config.main.options["test"]) + + @patch('iro.config.ConfigParser.read') + def testProviders(self, pRead): + v=Mock() + class TestProvider(Provider): + def __init__(self,name): + o = [("test",config.Option(v))] + Provider.__init__(self, name, options=o) + providers["test"]=TestProvider + try: + sample_config = """[p] +""" + config.configParser.readfp(io.BytesIO(sample_config)) + self.assertRaises(ConfigParser.NoOptionError, config.configParser.read, []) + self.assertEqual(v.called,0) + sample_config = """[p] +typ= test +test= foo +""" + config.configParser.readfp(io.BytesIO(sample_config)) + config.configParser.read([]) + v.assert_called_once_with("foo","test") + finally: + del(providers["test"]) + + +class TestConfig(unittest.TestCase): + + def config(self,name): + return config.Config(name, OrderedDict([ + ("dburl",config.Option(lambda x,y:x,long="Connection URL to database",must=True)), + ("port",config.Option(partial(validate.vInteger,minv=0),long="Port under that twisted is running",must=True)), + ]) + ) + + def testSame(self): + c1 = self.config("1") + c1.port = 1 + c1.dburl = "dburl1" + c2 = self.config("2") + c2.port = 1 + c2.dburl = "dburl1" + self.assertTrue(c1.same(c2)) + self.assertTrue(c1.same(c1)) + self.assertTrue(c2.same(c1)) + c2.port = 2 + self.assertFalse(c2.same(c1)) + self.assertFalse(c1.same(c2)) + c2.port = 1 + c2.dburl = "dburl2" + self.assertFalse(c2.same(c1)) + self.assertFalse(c1.same(c2)) + + def testSampleConf(self): + c1 = self.config("1") + self.assertEqual(c1.sampleConf(),["[1]", + "# Connection URL to database", + "dburl = ","", + "# Port under that twisted is running", + "port = ",""]) + + def testSampleConfDefault(self): + c1 = self.config("1") + c1.options["port"].default = 12345 + c1.options["port"].must = False + c1.options["dburl"].default = True + self.assertEqual(c1.sampleConf(),["[1]", + "# Connection URL to database", + "dburl = True","", + "# Port under that twisted is running", + "# port = 12345",""]) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/db.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/db.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,307 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import unittest +from sqlalchemy.orm.exc import MultipleResultsFound +from datetime import datetime + +from iro.model.schema import User, Offer, Userright, Job, Message +from decimal import Decimal + +from ..test_helpers.dbtestcase import DBTestCase + +class DBTests(DBTestCase): + """tests for the db model""" + + def testRoutes(self): + '''test routes''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.routes('sms').all(),[('sipgate_basic',),]) + + def testRoutesNoDefault(self): + '''test default routes''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.routes('sms',default=True).all(),[]) + + def testRoutesDefault(self): + '''test default routes''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o,default=1)) + session.add(u) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.routes('sms',default=True).all(),[('sipgate_basic',),]) + + def testRoutesDefaultOrdering(self): + '''test default routes ordering''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="s1", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o,default=3)) + o=Offer(name="s2", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o,default=None)) + o=Offer(name="s3", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o,default=1)) + session.add(u) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.routes('sms',default=True).all(),[('s3',),('s1',)]) + + def testProviders(self): + '''test providers''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.providers('sms').all(),[('sipgate',),]) + + def testTyps(self): + with self.session() as session: + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + session.add(o) + + with self.session() as session: + self.assertEqual(Offer.typs(session).all(),[('sms',),]) + + with self.session() as session: + o=Offer(name="s2", provider="sipgate", route="basic", typ="sms") + session.add(o) + o=Offer(name="s3", provider="sipgate", route="basic", typ="sms2") + session.add(o) + + with self.session() as session: + self.assertEqual(Offer.typs(session).order_by(Offer.typ).all(),[('sms',),('sms2',)]) + + def testOfferRoutes(self): + with self.session() as session: + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + session.add(o) + + with self.session() as session: + self.assertEqual([o.name for o in Offer.routes(session, "sms").order_by(Offer.name)],["sipgate_basic"]) + + with self.session() as session: + o=Offer(name="s2", provider="sipgate", route="basic", typ="sms") + session.add(o) + o=Offer(name="s3", provider="sipgate", route="basic", typ="sms2") + session.add(o) + + with self.session() as session: + self.assertEqual([o.name for o in Offer.routes(session, "sms2").order_by(Offer.name)],["s3"]) + self.assertEqual([o.name for o in Offer.routes(session, "sms").order_by(Offer.name)],["s2","sipgate_basic"]) + + def testOfferProviders(self): + with self.session() as session: + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + session.add(o) + + with self.session() as session: + self.assertEqual([o.provider for o in Offer.providers(session, "sms").order_by(Offer.provider)],["sipgate"]) + + with self.session() as session: + o=Offer(name="s2", provider="sipgate2", route="basic", typ="sms") + session.add(o) + o=Offer(name="s3", provider="sipgate", route="basic", typ="sms2") + session.add(o) + + with self.session() as session: + self.assertEqual([o.provider for o in Offer.providers(session, "sms2").order_by(Offer.provider)],["sipgate"]) + self.assertEqual([o.provider for o in Offer.providers(session, "sms").order_by(Offer.provider)],["sipgate","sipgate2"]) + +class Has_RightTests(DBTestCase): + '''test User.has_right''' + def testSimple(self): + '''test a very simple case''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.has_right("sms"),"sipgate_basic") + self.assertEqual(u.has_right("sms", offer_name="sipgate_basic"),"sipgate_basic") + self.assertEqual(u.has_right("sms", route="basic"),"sipgate_basic") + self.assertEqual(u.has_right("sms", provider="sipgate"),"sipgate_basic") + + def testDouble(self): + '''now we have different routes''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="b", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o)) + o=Offer(name="c", provider="sipgate", route="c", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.has_right("sms", offer_name="b"),"b") + self.assertEqual(u.has_right("sms", route="basic"),"b") + self.assertEqual(u.has_right("sms", offer_name="c"),"c") + self.assertEqual(u.has_right("sms", route="c"),"c") + self.assertRaises(MultipleResultsFound, u.has_right,"sms") + self.assertRaises(MultipleResultsFound, u.has_right,"sms", provider="sipgate") + + def testUnknown(self): + ''' a unknown typ''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="b", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o)) + o=Offer(name="c", provider="sipgate", route="c", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.has_right("fax", offer_name="b"), None) + self.assertEqual(u.has_right("fax"), None) + + + +class BillTest(DBTestCase): + """test the bill function""" + + def testBill(self): + '''test bill function''' + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") + j = Job(info='i',status='sended') + m = Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.30, offer=o, job=j) + u.rights.append(Userright(o)) + u.jobs.append(j) + session.add(m) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.rights[0].bill.all(),[(1L,Decimal('0.3000'),'i')]) + + def testBillAgreget(self): + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") + j = Job(info='i',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.30, offer=o)) + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o)) + u.rights.append(Userright(o)) + u.jobs.append(j) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.rights[0].bill.all(),[(2L,Decimal('0.7000'),'i')]) + + def testBillIsBilled(self): + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") + j = Job(info='i',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=True, date=datetime.now() , price=0.30, offer=o)) + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o)) + u.rights.append(Userright(o)) + u.jobs.append(j) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.rights[0].bill.all(),[(1L,Decimal('0.4000'),'i')]) + + def testBillMultipleJobs(self): + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") + u.rights.append(Userright(o)) + + j = Job(info='i',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o)) + u.jobs.append(j) + + j = Job(info='a',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o)) + u.jobs.append(j) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.rights[0].bill.order_by(Job.info).all(), + [(1L,Decimal('0.4000'),'a'), + (1L,Decimal('0.4000'),'i') + ]) + + def testBillMultipleOffers(self): + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") + u.rights.append(Userright(o)) + + j = Job(info='a',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o)) + u.jobs.append(j) + + o = Offer(name='sipgate_gold',provider="sipgate",route="gold",typ="sms") + u.rights.append(Userright(offer=o)) + + j = Job(info='a',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.5, offer=o)) + u.jobs.append(j) + + with self.session() as session: + u=session.merge(u) + self.assertEqual(u.rights[0].bill.all(), + [(1L,Decimal('0.4000'),'a')]) + + self.assertEqual(u.rights[1].bill.all(),[(1L,Decimal('0.5000'),'a')]) + + +if __name__ == '__main__': + unittest.main() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/email_validate.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/email_validate.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,90 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +try: + import unittest + unittest.case +except AttributeError: + import unittest2 as unittest + +from iro.validate import vEmail +from iro.error import InvalidMail, ValidateException + + +class testEmail(unittest.TestCase): + """tests for email adresses""" + + def testVaildEmail(self): + '''test vaild email adresses (got from wikipedia)''' + validmails=["niceandsimple@example.com", + "simplewith+symbol@example.com", + 'a.little.unusual@example.com', + 'a.little.more.unusual@dept.example.com', + '@[10.10.10.10]', + '@[1.1.1.1]', + '@[200.100.100.100]', + 'user@[2001:db8:1ff::a0b:dbd0]', + '"much.more\ unusual"@example.com', + 't."test".t@example.com', + '"very.unusual.@.unusual.com"@example.com', + '"very.(),:;<>[]\\".VERY.\\"very@\\\ \\"very\\".unusual"@strange.example.com', + "!#$%&'*+-/=?^_`{}|~@example.org", + '"()<>[]:;@,\\\"!#$%&\'*+-/=?^_`{}| ~ ? ^_`{}|~."@example.org', + '""@example.org'] + + for num in validmails: + self.failUnlessEqual(vEmail([num],None),[num]) + + def testInvaildEmail(self): + '''test invaild email adresses (got from wikipedia)''' + invalid=["Abc.example.com", # (an @ character must separate the local and domain parts) + "Abc.@example.com", # (character dot(.) is last in local part) + "Abc..123@example.com", # (character dot(.) is double) + "A@b@c@example.com", # (only one @ is allowed outside quotation marks) + 'a"b(c)d,e:f;gi[j\k]l@example.com', # (none of the special characters in this local part is allowed outside quotation marks) + 'just"not"right@example.com', # (quoted strings must be dot separated, or the only element making up the local-part) + 'thisis."notallowed@example.com', # (spaces, quotes, and backslashes may only exist when within quoted strings and preceded by a slash) + 'this\\ still\\"not\\allowed@example.com', # (even if escaped (preceded by a backslash), spaces, quotes, and backslashes must still be contained by quotes) + 't."test".t"test".t@example.com', + 't."test"t."test".t@example.com', + ] + + for number in invalid: + with self.assertRaises(InvalidMail) as e: + vEmail([number],None) + self.failUnlessEqual(e.exception.number, number) + + def testInvalidDomain(self): + '''invalid Domainname''' + with self.assertRaises(InvalidMail): + vEmail(['x@&.de'],None) + + def testDoubles(self): + self.assertEqual(vEmail(['x@test.de','x@test.de'],None),["x@test.de"]) + + def testString(self): + self.assertEqual(vEmail('x@test.de', None),"x@test.de") + + def testAllowString(self): + self.assertRaises(ValidateException,vEmail, 'x@test.de', None, allowString=False) + + def testAllowList(self): + self.assertRaises(ValidateException,vEmail, ['x@test.de'], None, allowList=False) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/install.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/install.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,184 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +#from mock import patch, Mock +from twisted.trial import unittest +from sqlalchemy import create_engine +from sets import Set +import io +import os + +from iro import install +from iro import config +from iro.model.schema import Base, Offer +from iro.model.utils import WithSession +from iro.offer.provider import providers, Provider +from ..test_helpers.dbtestcase import md, SampleDatabase +from ..test_helpers.utils import DummyObserver + +class TestInstallation(unittest.TestCase): + '''test install script''' + + def setUp(self): + md.setUp() + if not hasattr(md,"db2"): + md.db2=SampleDatabase("test2","test2",'%s/my.cnf'%md.tdir) + md.dburl2='mysql://test2:test@localhost/test2?unix_socket=%s/socket'%md.tdir + md.db2.create() + self.log = DummyObserver() + self.log.start() + self.engine = None + + def tearDown(self): + self.log.stop() + if self.engine: + Base.metadata.drop_all(self.engine) + self.engine = None + try: + os.remove("iro.conf") + except OSError as e: + if e.errno != 2: + raise + + + + def testCheckConfig(self): + self.assertEqual(install.checkConfig(),False) + with open("iro.conf",'w') as fp: + fp.write("""[main] +dburl=foo""") + self.assertEqual(install.checkConfig(),False) + self.assertEqual(len(self.log.e),1) + self.assertEqual(self.log.e[0]['message'], ("Error while processing config file: Option 'port' in section 'main' is missing.",)) + with open("iro.conf",'w') as fp: + fp.write("""[main] +dburl=foo +port=123456 +""") + self.assertEqual(install.checkConfig(),True) + + def testCheckDatabase(self): + config.main.dburl=md.dburl2 + self.assertTrue(install.checkDatabaseConnection()) + self.assertFalse(install.checkDatabase()) + self.engine = create_engine(md.dburl2) + Base.metadata.create_all(self.engine) + self.assertTrue(install.checkDatabase()) + + def testCheckDatabaseConnection(self): + config.main.dburl="mysql://t@localhost/test" + self.assertFalse(install.checkDatabaseConnection()) + self.assertEqual(len(self.log.e),1) + self.assertTrue(self.log.e[0]['message'][0].startswith("Error while trying to connect to database\n")) + config.main.dburl="sqlite://" + self.assertTrue(install.checkDatabaseConnection()) + + + def testCreateDatabase(self): + config.main.dburl=md.dburl2 + self.assertTrue(install.checkDatabaseConnection()) + self.assertFalse(install.checkDatabase()) + install.createDatabase() + self.assertTrue(install.checkDatabase()) + + def testCreateSampleConfig(self): + install.createSampleConfig() + with open("iro.conf",'r') as fp: + c = fp.read() + with open(os.path.abspath("../iro.conf.inst"),"r") as fp: + self.assertEqual(c,fp.read()) + + def testNoOverwrite(self): + with open("iro.conf","w") as fp: + fp.write("muhaha") + install.createSampleConfig() + with open("iro.conf",'r') as fp: + self.assertEqual(fp.read(),"muhaha") + self.assertEqual(len(self.log.e),1) + self.assertEqual(self.log.e[0]['message'], ("iro.conf exists and will not be overwritten.",)) + + def testGetAllRoutesNone(self): + config.main.dburl=md.dburl2 + install.createDatabase() + self.assertEqual(install.getAllRoutes([]),{"orphand":Set(),"added":Set()}) + + def testGetAllRoutesOrphand(self): + config.main.dburl=md.dburl2 + install.createDatabase() + self.engine = create_engine(md.dburl2) + with WithSession(self.engine,True) as session: + session.add(Offer(provider="p",route="r",typ="t",name="test")) + session.add(Offer(provider="p",route="r2",typ="t",name="test2")) + self.assertEqual(install.getAllRoutes([]),{"orphand":Set(["test","test2"]),"added":Set()}) + + def testGetAllRoutesAdded(self): + config.main.dburl=md.dburl2 + install.createDatabase() + self.engine = create_engine(md.dburl2) + class TestProvider(Provider): + def __init__(self, name): + Provider.__init__(self, name, {"r":["1","2","3"]}) + providers["test"]=TestProvider + sample_config = """[p] +typ= test +test= foo +""" + config.configParser.readfp(io.BytesIO(sample_config)) + self.assertEqual(install.getAllRoutes(["p"]),{"orphand":Set(),"added":Set(["p_r_1","p_r_2","p_r_3"])}) + + #test writing + self.assertEqual(install.getAllRoutes(["p"],True),{"orphand":Set(),"added":Set(["p_r_1","p_r_2","p_r_3"])}) + with WithSession(self.engine,True) as session: + o = Set([i[0] for i in session.query(Offer.name).all()]) + self.assertEqual(o,Set(["p_r_1","p_r_2","p_r_3"])) + + #nothing to do anymore + self.assertEqual(install.getAllRoutes(["p"]),{"orphand":Set(),"added":Set()}) + + def testGetAllRoutesOaA(self): + config.main.dburl=md.dburl2 + install.createDatabase() + self.engine = create_engine(md.dburl2) + class TestProvider(Provider): + def __init__(self, name): + Provider.__init__(self, name, {"r":["1","2","3"]}) + providers["test"]=TestProvider + sample_config = """[p] +typ= test +test= foo +""" + config.configParser.readfp(io.BytesIO(sample_config)) + + with WithSession(self.engine,True) as session: + session.add(Offer(provider="q",route="r",typ="t",name="test")) + session.add(Offer(provider="q",route="r2",typ="t",name="test2")) + session.add(Offer(provider="p",route="1",typ="r",name="foo")) + self.assertEqual(install.getAllRoutes(["p"]),{"orphand":Set(["test","test2"]),"added":Set(["p_r_2","p_r_3"])}) + + #test writing + self.assertEqual(install.getAllRoutes(["p"],True),{"orphand":Set(["test","test2"]),"added":Set(["p_r_2","p_r_3"])}) + with WithSession(self.engine,True) as session: + o = Set([i[0] for i in session.query(Offer.name).all()]) + self.assertEqual(o,Set(["test","test2","foo","p_r_2","p_r_3"])) + + self.assertEqual(install.getAllRoutes(["p"]),{"orphand":Set(["test","test2"]),"added":Set()}) + + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/job.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/job.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,197 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from datetime import datetime +from decimal import Decimal +from mock import patch + +from iro.model.job import exJobs, ExJob +from iro.model.pool import data +from iro.model.message import SMS +from iro.model.status import Status +from iro.model.schema import Job, User, Offer as DBOffer, Userright + +from iro.controller.task import Task + +from iro.offer.provider import Provider + +from iro.telnumber import Telnumber +from iro.validate import vInteger + +from ..test_helpers.dbtestcase import DBTestCase +from ..test_helpers.utils import DummyPool + +class JobTestCase(DBTestCase): + def setUp(self): + DBTestCase.setUp(self) + self.pool = data.pool + data.pool = DummyPool() + + def tearDown(self): + exJobs.clear() + data.pool = self.pool + self.pool = None + DBTestCase.tearDown(self) + + +class exJobsTest(JobTestCase): + '''tests for exJobs''' + + def testCreate(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + + job = exJobs.create(u, [Telnumber('123456789')], SMS('test'), ['test']) + self.assertIsInstance(job, ExJob) + self.assertTrue(vInteger(job.dbjob, None, minv=0 )) + self.assertEqual(job.message, SMS('test')) + self.assertEqual(job.recipients, [Telnumber('123456789')]) + self.assertEqual(job.offers,[]) + self.assertEqual(job.tasks,{}) + + with self.session() as session: + j = session.query(Job.id).all() + self.assertEqual(j,[(job.dbjob,)]) + + self.assertEqual(exJobs[job.dbjob],job) + self.assertEqual(self.log.e[0]['message'], ("Job(%s) created."%job.dbjob,)) + + def testCreate2(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="test", provider="bla", route="basic", typ="sms") + u.rights.append(Userright(o)) + + job = exJobs.create(u, [Telnumber('123456789')], SMS('test'), ['test']) + self.assertEqual(job.offers,['test']) + + def testGet(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + + job = ExJob(None, [Telnumber('123456789')], SMS('test'), ['test']) + exJobs[1] = job + + self.assertEqual(len(exJobs), 1) + self.assertEqual(job, exJobs[1]) + + def testGetFromDB(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + job = Job( info="info", status="started") + u.jobs.append(job) + session.add(u) + + with self.session() as session: + job = session.merge(job) + u = session.merge(u) + ejob= ExJob(job.id, [Telnumber('123456789')], SMS('test'), ['test']) + exJobs[job.id]=ejob + self.assertEqual(job.extend, ejob) + self.assertEqual(u.jobs[0].extend,ejob) + + def testUnknownExJob(self): + self.assertRaises(KeyError,exJobs.__getitem__,'a1234567890') + + +class StatiTest(JobTestCase): + def setUp(self): + JobTestCase.setUp(self) + + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="test", provider="bla", route="a", typ="sms") + u.rights.append(Userright(o)) + + self.user = u + self.offer = o + + self.provider=Provider("bla", {"sms":["a","b","c"]}) + self.job = exJobs.create(u, [Telnumber('123456789')], SMS('test'), []) + + def tearDown(self): + JobTestCase.tearDown(self) + + + def testSetError(self): + self.job.setError(Task(Telnumber('123456789'),self),Exception("muhaha")) + errors = self.flushLoggedErrors(Exception) + self.assertEqual(len(errors), 1) + self.assertEqual(self.log.e[1]['message'], ("Job(%s) status changed to: error."%self.job.dbjob,)) + self.assertEqual(self.log.e[2]['why'], "Error: Job(%s) to '0049123456789' failed."%self.job.dbjob) + + with self.session() as session: + u = session.merge(self.user) + job = u.job(self.job.dbjob) + self.assertEqual(job.status,"error") + + def testSetStatus(self): + task = Task(Telnumber('123456789'),self.job) + status = Status(self.provider,"a") + self.job.setStatus(task, status) + + self.assertEqual(self.log.e[1]['message'], ("Job(%s) status changed to: sended."%self.job.dbjob,)) + self.assertEqual(self.log.e[2]['message'], ("Job(%s) to '0049123456789' ended sucecessfully via bla:a."%self.job.dbjob,)) + + with self.session() as session: + u = session.merge(self.user) + job = u.job(self.job.dbjob) + self.assertEqual(job.status,"sended") + self.assertEqual(len(job.messages),0) + + def testMultipleRecipients(self): + self.job.recipients.append(Telnumber("01234567890")) + task = Task(Telnumber('123456789'),self.job) + status = Status(self.provider,"a") + self.job.setStatus(task, status) + + with self.session() as session: + u = session.merge(self.user) + job = u.job(self.job.dbjob) + self.assertEqual(job.status,"sending") + + @patch("iro.model.job.datetime") + def testCosts(self,p_dt): + p_dt.today.return_value = datetime(2000, 1, 2, 3, 4, 5) + task = Task(Telnumber('123456789'),self.job) + status = Status(self.provider,"a",costs=0.055,exID="12345678",count=1) + + self.job.setStatus(task, status) + + with self.session() as session: + u = session.merge(self.user) + o = session.merge(self.offer) + job = u.job(self.job.dbjob) + self.assertEqual(job.status,"sended") + self.assertEqual(len(job.messages),1) + + msg = job.messages[0] + self.assertEqual(msg.price,Decimal('0.0550')) + self.assertEqual(msg.isBilled,False) + self.assertEqual(msg.recipient,str(Telnumber('123456789'))) + self.assertEqual(msg.date,datetime(2000, 1, 2, 3, 4, 5)) + self.assertEqual(msg.offer,o) + self.assertEqual(msg.exID,"12345678") + self.assertEqual(msg.count,1) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/jsonresource.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/jsonresource.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,354 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +from twisted.trial import unittest + +from twisted.web.client import Agent, ResponseDone +from twisted.internet import reactor +from twisted.web.http_headers import Headers +from twisted.python.failure import Failure +from twisted.internet.protocol import Protocol +from twisted.internet import defer + +from zope.interface import implements + +from twisted.internet.defer import succeed +from twisted.web.iweb import IBodyProducer + +import json + +from twisted.web import server +from txjsonrpc.web.jsonrpc import Fault + +from datetime import datetime + +from iro.model.schema import User, Offer, Userright, Job, Message + +from iro.view import jsonresource +import iro.error as IroError + +from iro.test_helpers.dbtestcase import DBTestCase + +from iro.model import setEngine, setPool +from iro.controller.pool import startPool, dbPool + + +class StringProducer(object): + implements(IBodyProducer) + + def __init__(self, body): + self.body = json.dumps(body) + self.length = len(self.body) + + def startProducing(self, consumer): + consumer.write(self.body) + return succeed(None) + + def pauseProducing(self): + pass + + def stopProducing(self): + pass + +class SimpleReceiver(Protocol): + def __init__(self, d): + self.d = d + self.data = "" + + def dataReceived(self, data): + self.data += data + + def connectionLost(self, reason): + try: + if reason.check(ResponseDone): + self.d.callback(json.loads(self.data)) + else: + self.d.errback(reason) + except: + self.d.errback(Failure()) + +class JSONRPCTest(DBTestCase): + """tests for the jsonrpc interface""" + ContentType = 'application/json' + StringProducer = StringProducer + def setUp(self): + DBTestCase.setUp(self) + + setEngine(self.engine) + startPool(reactor) + setPool(dbPool) + self.p = reactor.listenTCP(0, server.Site(jsonresource.JSONFactory()), + interface="127.0.0.1") + self.port = self.p.getHost().port + self.agent = Agent(reactor) + + def tearDown(self): + DBTestCase.tearDown(self) + return self.p.stopListening() + + def proxy(self, method, data=None, **kargs): + d = self.agent.request( + 'GET', + 'http://localhost:%d/%s'%(self.port, method), + Headers({'Content-Type':[self.ContentType]}), + self.StringProducer(data) if data else None, + ) + + def cbResponse(response): + if kargs.has_key('code'): + self.failUnlessEqual(response.code, kargs["code"]) + if response.code not in [200,400,500]: + raise Fault(response.code,'') + self.failUnlessEqual(response.headers.getRawHeaders('Content-Type'), ['application/json']) + d = defer.Deferred() + response.deliverBody(SimpleReceiver(d)) + def _(data): + if data["status"]: + return data["result"] + else: + raise Fault(data["error"]["code"],data["error"]["msg"]) + + d.addCallback(_) + return d + d.addCallback(cbResponse) + return d + + + def testListMethods(self): + '''list of all offical Methods, that can be executed''' + + def cbMethods(ret): + ret.sort() + self.failUnlessEqual(ret, ['bill', 'defaultRoute', 'email', 'fax', 'listMethods', 'mail', 'routes', 'sms', 'status', 'telnumber']) + + d=self.proxy("listMethods", code=200) + d.addCallback(cbMethods) + return d + + def testApikey(self): + ''' test apikey''' + with self.session() as session: + session.add(User(name='test',apikey='abcdef123456789')) + + d = self.proxy('status', ['abcdef123456789'], code=200) + d.addCallback(lambda ret: self.failUnlessEqual(ret,{})) + + return d + + def testStatus(self): + ''' test the status function''' + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + j = Job(info='info', status="started") + j.user=u + session.add(j) + session.commit() + jid=j.id + status = {str(jid):{"status":"started"}} + args=[('abcdef123456789',jid), + ('abcdef123456789',), + ('abcdef123456789', '', 'false'), + ('abcdef123456789', '', 0), + ('abcdef123456789', '', 0), + {"id":jid, "user":'abcdef123456789'}, + ] + dl=[] + for a in args: + d = self.proxy('status', a, code=200) + d.addCallback(lambda ret: self.failUnlessEqual(ret,status)) + dl.append(d) + + def f(exc): + jnf = IroError.JobNotFound() + self.failUnlessEqual(exc.faultCode, jnf.code) + self.failUnlessEqual(exc.faultString, jnf.msg) + d = self.proxy('status', ['abcdef123456789', jid+1], code=500) + d = self.assertFailure(d, Fault) + d.addCallback(f) + dl.append(d) + + return defer.DeferredList(dl, fireOnOneErrback=True) + + def testNoSuchUser(self): + '''a unknown user should raise a UserNotNound Exception + bewcause xmlrpc only has a Fault exception this Exception has to be deliverd through a xmlrpclib.Fault Exception''' + def f(exc): + unf = IroError.UserNotFound() + self.failUnlessEqual(exc.faultCode, unf.code) + self.failUnlessEqual(exc.faultString, unf.msg) + d = self.proxy('status', ['abcdef123456789'], code=500) + d = self.assertFailure(d, Fault) + d.addCallback(f) + return d + + def testNoSuchMethod(self): + '''a unknown mothod should raise a Exception ''' + def f(exc): + self.failUnlessEqual(exc.faultCode, 404) + self.failUnlessEqual(exc.faultString, '') + d = self.proxy('nosuchmethod', code=404) + d = self.assertFailure(d, Fault) + d.addCallback(f) + return d + + def testValidationFault(self): + '''a validate Exception should be translated to a xmlrpclib.Fault.''' + def f(exc): + self.failUnlessEqual(exc.faultCode, 700) + self.failUnlessEqual(exc.faultString, "Validation of 'apikey' failed.") + d = self.proxy('status',{'user':'xxx'}, code=400) + d = self.assertFailure(d, Fault) + d.addCallback(f) + return d + + @defer.inlineCallbacks + def testRoutes(self): + '''test the route function''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + + x = yield self.proxy('routes',['abcdef123456789','sms'], code=200) + self.failUnlessEqual(x,['sipgate_basic']) + + def f(exc): + self.failUnlessEqual(exc.faultCode, 700) + self.failUnlessEqual(exc.faultString, "Typ fax is not valid.") + d = self.proxy('routes',['abcdef123456789','fax'], code=400) + d = self.assertFailure(d, Fault) + d.addCallback(f) + yield d + + with self.session() as session: + o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms") + u = session.query(User).filter_by(name="test").first() + u.rights.append(Userright(o)) + o=Offer(name="faxde", provider="faxde", route="", typ="fax") + session.add(o) + session.commit() + + x = yield self.proxy('routes',['abcdef123456789','sms'], code=200) + self.failUnlessEqual(x, ['sipgate_basic','sipgate_plus']) + x = yield self.proxy('routes', ['abcdef123456789','fax'], code=200) + self.failUnlessEqual(x, []) + + with self.session() as session: + u = session.query(User).filter_by(name="test").first() + u.rights.append(Userright(o)) + + x = yield self.proxy('routes',['abcdef123456789','sms'], code=200) + self.failUnlessEqual(x, ['sipgate_basic','sipgate_plus']) + x = yield self.proxy('routes', ['abcdef123456789','fax'], code=200) + self.failUnlessEqual(x, ['faxde']) + + def testDefaultRoutes(self): + '''test the defaultRoute function''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o,True)) + o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + d = self.proxy('defaultRoute', ['abcdef123456789','sms'], code=200) + d.addCallback(lambda x: self.failUnlessEqual(x,['sipgate_basic'])) + + return d + + def testTelnumbers(self): + '''test the telefon validator''' + dl = [] + d = self.proxy('telnumber',[["0123/456(78)","+4912346785433","00123435456-658"]], code=200) + d.addCallback(lambda x: self.failUnlessEqual(x,True)) + dl.append(d) + + invalid=['xa','+1','1-23',';:+0','0123'] + def f(exc): + self.failUnlessEqual(exc.faultCode, 701) + self.failUnlessEqual(exc.faultString, "No valid telnumber: '%s'" % invalid[0]) + d = self.proxy('telnumber',[['01234']+invalid], code=400) + d = self.assertFailure(d, Fault) + d.addCallback(f) + dl.append(d) + + return defer.DeferredList(dl, fireOnOneErrback=True) + + def testVaildEmail(self): + '''test vaild email adresses (got from wikipedia)''' + validmails=["niceandsimple@example.com"] + d = self.proxy('email', {"recipients":validmails}, code=200) + d.addCallback(lambda x: self.failUnlessEqual(x,True)) + return d + + def testInvaildEmail(self): + '''test invaild email adresses (got from wikipedia)''' + invalid=["Abc.example.com",'foo@t.de'] + def f(exc): + self.failUnlessEqual(exc.faultCode, 702) + self.failUnlessEqual(exc.faultString, "No valid email: '%s'" % invalid[0]) + d = self.proxy('email', [invalid], code=400) + d = self.assertFailure(d, Fault) + d.addCallback(f) + return d + + def testBill(self): + '''test bill function''' + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + d = self.proxy('bill', {"user":apikey}, code=200) + d.addCallback(lambda x: self.failUnlessEqual(x,{'total':{'price':0.0,'anz':0}})) + + return d + + def testBillWithPrice(self): + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") + u.rights.append(Userright(o)) + j = Job(info='i',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o)) + u.jobs.append(j) + + j = Job(info='a',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o)) + u.jobs.append(j) + + def f(ret): + self.failUnlessEqual(ret['total'],{'price':0.8,'anz':2}) + self.failUnlessEqual(ret['sipgate_basic'], + {'price':0.8,'anz':2, + 'info':{'i':{'price':0.4,'anz':1}, + 'a':{'price':0.4,'anz':1}, + } + }) + + d = self.proxy('bill', [apikey], code=200) + d.addCallback(f) + return d + +if __name__ == '__main__': + unittest.main() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/jsonrpc.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/jsonrpc.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,279 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import unittest +from twisted.internet import reactor, defer +from twisted.web import server +from txjsonrpc.web import jsonrpc as txjsonrpc +from txjsonrpc.web.jsonrpc import Fault +from txjsonrpc import jsonrpclib + +from datetime import datetime + +from iro.model.schema import User, Offer, Userright, Job, Message + +from iro.view import jsonrpc +import iro.error as IroError + +from iro.test_helpers.dbtestcase import DBTestCase + +from iro.model import setEngine, setPool +from iro.controller.pool import startPool, dbPool + + +class JSONRPCTest(DBTestCase): + """tests for the jsonrpc interface""" + def setUp(self): + DBTestCase.setUp(self) + + setEngine(self.engine) + startPool(reactor) + setPool(dbPool) + self.p = reactor.listenTCP(0, server.Site(jsonrpc.JSONRPCInterface()), + interface="127.0.0.1") + self.port = self.p.getHost().port + + def tearDown(self): + DBTestCase.tearDown(self) + return self.p.stopListening() + + def proxy(self): + return txjsonrpc.Proxy("http://127.0.0.1:%d/" % self.port,version=jsonrpclib.VERSION_2) + + def testListMethods(self): + '''list of all offical Methods, that can be executed''' + + def cbMethods(ret): + ret.sort() + self.failUnlessEqual(ret, ['bill', 'defaultRoute', 'email', 'fax', 'listMethods', 'mail', 'routes', 'sms', 'status', 'telnumber']) + + d=self.proxy().callRemote("listMethods") + d.addCallback(cbMethods) + return d + + def testApikey(self): + ''' test apikey''' + with self.session() as session: + session.add(User(name='test',apikey='abcdef123456789')) + + d = self.proxy().callRemote('status', 'abcdef123456789') + d.addCallback(lambda ret: self.failUnlessEqual(ret,{})) + + return d + + def testStatus(self): + ''' test the status function''' + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + j = Job(info='info', status="started") + j.user=u + session.add(j) + session.commit() + jid=j.id + status = {str(jid):{"status":"started"}} + args=[('abcdef123456789',jid), + ('abcdef123456789',), + ('abcdef123456789', '', 'false'), + ('abcdef123456789', '', 0), + ] + dl=[] + for a in args: + d = self.proxy().callRemote('status',*a) + d.addCallback(lambda ret: self.failUnlessEqual(ret,status)) + dl.append(d) + + def f(exc): + jnf = IroError.JobNotFound() + self.failUnlessEqual(exc.faultCode, jnf.code) + self.failUnlessEqual(exc.faultString, jnf.msg) + d = self.proxy().callRemote('status', 'abcdef123456789', jid+1) + d = self.assertFailure(d, Fault) + d.addCallback(f) + dl.append(d) + + return defer.DeferredList(dl, fireOnOneErrback=True) + + def testNoSuchUser(self): + '''a unknown user should raise a UserNotNound Exception + bewcause xmlrpc only has a Fault exception this Exception has to be deliverd through a xmlrpclib.Fault Exception''' + def f(exc): + unf = IroError.UserNotFound() + self.failUnlessEqual(exc.faultCode, unf.code) + self.failUnlessEqual(exc.faultString, unf.msg) + d = self.proxy().callRemote('status', 'abcdef123456789') + d = self.assertFailure(d, Fault) + d.addCallback(f) + return d + + def testNoSuchMethod(self): + '''a unknown mothod should raise a Exception ''' + def f(exc): + self.failUnlessEqual(exc.faultCode, -32601) + self.failUnlessEqual(exc.faultString, 'procedure nosuchmethod not found') + d = self.proxy().callRemote('nosuchmethod') + d = self.assertFailure(d, Fault) + d.addCallback(f) + return d + + def testValidationFault(self): + '''a validate Exception should be translated to a xmlrpclib.Fault.''' + def f(exc): + self.failUnlessEqual(exc.faultCode, 700) + self.failUnlessEqual(exc.faultString, "Validation of 'apikey' failed.") + d = self.proxy().callRemote('status','xxx') + d = self.assertFailure(d, Fault) + d.addCallback(f) + return d + + @defer.inlineCallbacks + def testRoutes(self): + '''test the route function''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + + x = yield self.proxy().callRemote('routes','abcdef123456789','sms') + self.failUnlessEqual(x,['sipgate_basic']) + + def f(exc): + self.failUnlessEqual(exc.faultCode, 700) + self.failUnlessEqual(exc.faultString, "Typ fax is not valid.") + d = self.proxy().callRemote('routes','abcdef123456789','fax') + d = self.assertFailure(d, Fault) + d.addCallback(f) + yield d + + with self.session() as session: + o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms") + u = session.query(User).filter_by(name="test").first() + u.rights.append(Userright(o)) + o=Offer(name="faxde", provider="faxde", route="", typ="fax") + session.add(o) + session.commit() + + x = yield self.proxy().callRemote('routes','abcdef123456789','sms') + self.failUnlessEqual(x, ['sipgate_basic','sipgate_plus']) + x = yield self.proxy().callRemote('routes','abcdef123456789','fax') + self.failUnlessEqual(x, []) + + with self.session() as session: + u = session.query(User).filter_by(name="test").first() + u.rights.append(Userright(o)) + + x = yield self.proxy().callRemote('routes','abcdef123456789','sms') + self.failUnlessEqual(x, ['sipgate_basic','sipgate_plus']) + x = yield self.proxy().callRemote('routes','abcdef123456789','fax') + self.failUnlessEqual(x, ['faxde']) + + def testDefaultRoutes(self): + '''test the defaultRoute function''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o,True)) + o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + d = self.proxy().callRemote('defaultRoute','abcdef123456789','sms') + d.addCallback(lambda x: self.failUnlessEqual(x,['sipgate_basic'])) + + return d + + def testTelnumbers(self): + '''test the telefon validator''' + dl = [] + d = self.proxy().callRemote('telnumber',["0123/456(78)","+4912346785433","00123435456-658"]) + d.addCallback(lambda x: self.failUnlessEqual(x,True)) + dl.append(d) + + invalid=['xa','+1','1-23',';:+0','0123'] + def f(exc): + self.failUnlessEqual(exc.faultCode, 701) + self.failUnlessEqual(exc.faultString, "No valid telnumber: '%s'" % invalid[0]) + d = self.proxy().callRemote('telnumber', ['01234']+invalid) + d = self.assertFailure(d, Fault) + d.addCallback(f) + dl.append(d) + + return defer.DeferredList(dl, fireOnOneErrback=True) + + def testVaildEmail(self): + '''test vaild email adresses (got from wikipedia)''' + validmails=["niceandsimple@example.com"] + d = self.proxy().callRemote('email', validmails) + d.addCallback(lambda x: self.failUnlessEqual(x,True)) + return d + + def testInvaildEmail(self): + '''test invaild email adresses (got from wikipedia)''' + invalid=["Abc.example.com",'foo@t.de'] + def f(exc): + self.failUnlessEqual(exc.faultCode, 702) + self.failUnlessEqual(exc.faultString, "No valid email: '%s'" % invalid[0]) + d = self.proxy().callRemote('email', invalid) + d = self.assertFailure(d, Fault) + d.addCallback(f) + return d + + def testBill(self): + '''test bill function''' + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + d = self.proxy().callRemote('bill', apikey) + d.addCallback(lambda x: self.failUnlessEqual(x,{'total':{'price':0.0,'anz':0}})) + + return d + + def testBillWithPrice(self): + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") + u.rights.append(Userright(o)) + j = Job(info='i',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o)) + u.jobs.append(j) + + j = Job(info='a',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o)) + u.jobs.append(j) + + def f(ret): + self.failUnlessEqual(ret['total'],{'price':0.8,'anz':2}) + self.failUnlessEqual(ret['sipgate_basic'], + {'price':0.8,'anz':2, + 'info':{'i':{'price':0.4,'anz':1}, + 'a':{'price':0.4,'anz':1}, + } + }) + + d = self.proxy().callRemote('bill', apikey) + d.addCallback(f) + return d + +if __name__ == '__main__': + unittest.main() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/model_validate.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/model_validate.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,75 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from iro.model.schema import Offer +from iro.model.decorators import vRoute, vTyp +from iro.model.pool import data + +from iro.error import ValidateException + +from ..test_helpers.dbtestcase import DBTestCase +from ..test_helpers.utils import DummyPool + +class ModelVaidatorTest(DBTestCase): + """tests for the model vaidators""" + def setUp(self): + DBTestCase.setUp(self) + self.pool = data.pool + data.pool = DummyPool() + + def tearDown(self): + data.pool = self.pool + self.pool = None + DBTestCase.tearDown(self) + + def testTyp(self): + with self.session() as session: + session.add(Offer(name="t",provider="p",typ="type")) + + with self.session() as session: + self.assertEqual(vTyp("type",None),"type") + e = self.assertRaises(ValidateException,vTyp, "sss", None) + self.assertEqual(str(e),'700: Typ sss is not valid.') + + def testRoute(self): + with self.session() as session: + session.add(Offer(name="t",provider="p",typ="type")) + self.assertEqual(vRoute("t",None,typ="type"),"t") + self.assertEqual(vRoute(["t","t"],None,typ="type"),["t"]) + e = self.assertRaises(ValidateException,vRoute, "s", None, typ="type") + self.assertEqual(str(e),'700: Route s is not valid.') + + def testRouteAllow(self): + with self.session() as session: + session.add(Offer(name="t",provider="p",typ="type")) + e = self.assertRaises(ValidateException,vRoute, "t", "foo", typ="type", allowString=False) + self.assertEqual(str(e),'700: foo must be a list of routes.') + e = self.assertRaises(ValidateException,vRoute, ["t"], "foo", typ="type", allowList=False) + self.assertEqual(str(e),'700: foo must be a route - No list of routes.') + + def testRouteProvider(self): + with self.session() as session: + session.add(Offer(name="t",provider="p",typ="type")) + self.assertEqual(vRoute("p",None,typ="type"),"p") + + def testRouteDefault(self): + self.assertEqual(vRoute("default",None,typ="type"),"default") + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/offer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/offer.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,177 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.internet import reactor +from twisted.internet.defer import inlineCallbacks + +import io + +from iro.model import offer +from iro.config import configParser +from iro.offer import Offer, providers as OfferProviders, Provider +from iro.model.schema import User, Offer as DBOffer, Userright +from iro.controller.pool import dbPool +from iro.error import NoProvider +from ..test_helpers.dbtestcase import DBTestCase + +class TestOffers(DBTestCase): + '''test config class''' + def setUp(self): + DBTestCase.setUp(self) + dbPool.start(reactor) + + def tearDown(self): + for s in configParser.sections(): + configParser.remove_section(s) + + dbPool.pool.stop() + offer.offers.clear() + + try: + del(OfferProviders["test"]) + except KeyError: + pass + + offer.providers.clear() + DBTestCase.tearDown(self) + + def testReloadList(self): + '''test if loadOffers will be fired by reloading Config''' + self.assertTrue(offer.loadOffers in configParser.reloadList) + + @inlineCallbacks + def testExtendProviderUnknown(self): + '''test the extendProvider Function (route unknown)''' + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + + ret = yield offer.extendProvider(u, "sms", ["blub"]) + self.assertEqual(ret, []) + + @inlineCallbacks + def testExtendProviderKnown(self): + '''test the extendProvider Function (route known)''' + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="blub", provider="bla", route="basic", typ="sms") + u.rights.append(Userright(o)) + + ret = yield offer.extendProvider(u, "sms", ["blub"]) + self.assertEqual(ret, ["blub"]) + + ret = yield offer.extendProvider(u, "sms", ["blub", "blub"]) + self.assertEqual(ret, ["blub"]) + + @inlineCallbacks + def testExtendProviderProvider(self): + '''test the extendProvider Function (provider known)''' + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="oh", provider="bla", route="a", typ="sms") + u.rights.append(Userright(o)) + + offer.providers={"bla":Provider("bla", {"sms":["a","b","c"]})} + + for l in [['bla'],['oh'],['oh','bla'],['bla','oh']]: + ret = yield offer.extendProvider(u, "sms", l) + self.assertEqual(ret, ["oh"]) + + @inlineCallbacks + def testExtendProviderDouble(self): + '''test the extendProvider Function (provider and name doubles)''' + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="oh", provider="bla", route="a", typ="sms") + u.rights.append(Userright(o)) + o=DBOffer(name="a", provider="bla", route="b", typ="sms") + u.rights.append(Userright(o)) + + offer.providers={"bla":Provider("bla", {"sms":["a","b","c"]})} + + ret = yield offer.extendProvider(u, "sms", ["bla"]) + self.assertEqual(ret, ["oh","a"]) + + ret = yield offer.extendProvider(u, "sms", ["a","bla"]) + self.assertEqual(ret, ["a","oh"]) + + ret = yield offer.extendProvider(u, "sms", ["bla", "a"]) + self.assertEqual(ret, ["oh","a"]) + + @inlineCallbacks + def testExtendProviderDefault(self): + '''test the extendProvider Function (default)''' + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="blub3", provider="bla", route="basic", typ="sms") + u.rights.append(Userright(o,default=2)) + o=DBOffer(name="blub", provider="bla", route="basic", typ="sms") + u.rights.append(Userright(o,default=1)) + o=DBOffer(name="blub2", provider="bla2", route="basic", typ="sms") + u.rights.append(Userright(o)) + o=DBOffer(name="blub4", provider="bla", route="basic", typ="sms") + u.rights.append(Userright(o,default=3)) + + ret = yield offer.extendProvider(u, "sms", ["default"]) + self.assertEqual(ret, ["blub","blub3",'blub4']) + + ret = yield offer.extendProvider(u, "sms", "default") + self.assertEqual(ret, ["blub","blub3", 'blub4']) + + + @inlineCallbacks + def testLoadOffers(self): + + class TestProvider(Provider): + def __init__(self,name): + Provider.__init__(self,name,{"sms":["a",]}) + + with self.session() as session: + session.add(DBOffer(name="oh", provider="p", route="a", typ="sms")) + + sample_config = """[p] +typ = test +""" + configParser.readfp(io.BytesIO(sample_config)) + OfferProviders["test"]=TestProvider + yield offer.loadOffers() + self.assertEqual(offer.offers.keys(),["oh"]) + self.assertEqual(offer.providers.keys(),["p"]) + self.assertEqual(offer.providers["p"].name,"p") + self.assertEqual(offer.providers["p"].typ,"test") + self.assertEqual(offer.offers["oh"],Offer(provider=offer.providers["p"], name="oh", route="a", typ="sms")) + + def testLoadOffersUnknown(self): + with self.session() as session: + session.add(DBOffer(name="oh", provider="p", route="a", typ="sms")) + + sample_config = """[p] +typ = unknown +""" + configParser.readfp(io.BytesIO(sample_config)) + d = offer.loadOffers() + self.assertFailure(d, NoProvider) + return d + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/offer_integrated.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/offer_integrated.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,222 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.internet.defer import inlineCallbacks + +from datetime import datetime +from decimal import Decimal +from mock import patch, Mock + +from iro.model.job import exJobs +from iro.model.pool import data +from iro.model.schema import User, Offer as DBOffer, Userright +from iro.model.message import SMS, Mail +from iro.model import offer + +from iro.controller.task import Task, taskPool +from iro.telnumber import Telnumber + +from iro.offer import Smstrade, SMTP, Offer +from iro.offer.smstrade import SmstradeException, StatusCode +from iro.error import NoRouteForTask + +from ..test_helpers.dbtestcase import DBTestCase +from ..test_helpers.utils import DummyPool + +def run( f,*a,**k): + return f(*a,**k) + +class IntegratedOfferTests(DBTestCase): + def setUp(self): + DBTestCase.setUp(self) + self.pool = data.pool + data.pool = DummyPool() + + self.taskPool = taskPool.run + taskPool.run = run + + def tearDown(self): + exJobs.clear() + data.pool = self.pool + self.pool = None + taskPool.run = self.taskPool + self.taskPool = None + DBTestCase.tearDown(self) + + + @patch("iro.model.job.datetime") + @patch("urllib.urlopen") + @inlineCallbacks + def testSmstrade(self, p_u, p_dt): + f = Mock() + f.readlines.return_value = ["100","12345678","0.055","1"] + p_u.return_value = f + + p_dt.today.return_value = datetime(2000, 1, 2, 3, 4, 5) + + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="s", provider="bla", route="basic", typ="sms") + u.rights.append(Userright(o)) + + + offer.providers["bla"] = Smstrade("bla") + offer.providers["bla"].key = "XXXXXX" + offer.offers["s"] = Offer("s",offer.providers["bla"],"basic","sms") + + j = yield exJobs.create(u,[Telnumber("0123456789")],SMS("bla"),['s'],'tesched') + t = Task(Telnumber("0123456789"),j) + yield t.start() + + self.assertEqual(self.log.e[2]['message'], ("Job(%s) to '0049123456789' ended sucecessfully via bla:basic."%j.dbjob,)) + + with self.session() as session: + u = session.merge(u) + o = session.merge(o) + job = u.job(j.dbjob) + self.assertEqual(job.status,"sended") + self.assertEqual(job.info,"tesched") + self.assertEqual(len(job.messages),1) + + msg = job.messages[0] + self.assertEqual(msg.price,Decimal('0.0550')) + self.assertEqual(msg.isBilled,False) + self.assertEqual(msg.recipient,str(Telnumber('123456789'))) + self.assertEqual(msg.date,datetime(2000, 1, 2, 3, 4, 5)) + self.assertEqual(msg.offer,o) + self.assertEqual(msg.exID,"12345678") + self.assertEqual(msg.count,1) + + + @patch("urllib.urlopen") + @inlineCallbacks + def testSmstradeException(self, mock_urlopen): + f = Mock() + f.readlines.return_value = ["703"] + mock_urlopen.return_value = f + + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="s", provider="bla", route="basic", typ="sms") + u.rights.append(Userright(o)) + + offer.providers["bla"] = Smstrade("bla") + offer.providers["bla"].key = "XXXXXX" + offer.offers["s"] = Offer("s",offer.providers["bla"],"basic","sms") + + j = yield exJobs.create(u,[Telnumber("0123456789")],SMS("bla"),['s'],'tesched') + t = Task(Telnumber("0123456789"),j) + yield t.start() + + errors = self.flushLoggedErrors(SmstradeException) + self.assertEqual(len(errors), 1) + errors = self.flushLoggedErrors(NoRouteForTask) + self.assertEqual(len(errors), 1) + self.assertEqual(self.log.e[1]['why'], "Job(%s): Send to '0049123456789' failed via 's'"%j.dbjob) + self.assertEqual(self.log.e[3]['why'], "Error: Job(%s) to '0049123456789' failed."%j.dbjob) + + self.assertEqual(t.error, True) + self.assertEqual(str(self.log.e[1]['failure'].value),str(SmstradeException(StatusCode(703)))) + + with self.session() as session: + u = session.merge(u) + o = session.merge(o) + job = u.job(j.dbjob) + self.assertEqual(job.status,"error") + self.assertEqual(len(job.messages),0) + + @patch("smtplib.SMTP") + @inlineCallbacks + def testSmtp(self, p_s ): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="s", provider="bla", route=None, typ="mail") + u.rights.append(Userright(o)) + + offer.providers["bla"] = SMTP("bla") + offer.providers["bla"].SSL = False + offer.providers["bla"].TLS = False + offer.providers["bla"].host = "localhost" + offer.providers["bla"].port = 12345 + offer.providers["bla"].user = "" + offer.providers["bla"].send_from = "frm@test.de" + offer.offers["s"] = Offer("s",offer.providers["bla"],None,"mail") + + j = yield exJobs.create(u,["t@test.de"],Mail("bla",'msg',None),['s'],'tesched') + t = Task("t@test.de",j) + yield t.start() + + self.assertEqual(self.log.e[2]['message'], ("Job(%s) to 't@test.de' ended sucecessfully via bla:None."%j.dbjob,)) + + with self.session() as session: + u = session.merge(u) + o = session.merge(o) + job = u.job(j.dbjob) + self.assertEqual(job.status,"sended") + self.assertEqual(job.info,"tesched") + self.assertEqual(len(job.messages),0) + + @patch("smtplib.SMTP") + @inlineCallbacks + def testSmtpException(self, p_s): + p_s.side_effect = IOError(111,"Connection refused") + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="s", provider="bla", route=None, typ="mail") + u.rights.append(Userright(o)) + + offer.providers["bla"] = SMTP("bla") + offer.providers["bla"].SSL = False + offer.providers["bla"].TLS = False + offer.providers["bla"].host = "localhost" + offer.providers["bla"].port = 12345 + offer.providers["bla"].user = "" + offer.providers["bla"].send_from = "frm@test.de" + offer.offers["s"] = Offer("s",offer.providers["bla"],None,"mail") + + j = yield exJobs.create(u,["t@test.de"],Mail("bla",'msg',None),['s'],'tesched') + t = Task("t@test.de",j) + yield t.start() + + errors = self.flushLoggedErrors(NoRouteForTask) + self.assertEqual(len(errors), 1) + errors = self.flushLoggedErrors(IOError) + self.assertEqual(len(errors), 1) + self.assertEqual(self.log.e[1]['why'], "Job(%s): Send to 't@test.de' failed via 's'"%j.dbjob) + self.assertEqual(self.log.e[3]['why'], "Error: Job(%s) to 't@test.de' failed."%j.dbjob) + self.assertEqual(t.error, True) + self.assertEqual(str(self.log.e[1]['failure'].value),str(IOError(111,"Connection refused"))) + + with self.session() as session: + u = session.merge(u) + o = session.merge(o) + job = u.job(j.dbjob) + self.assertEqual(job.status,"error") + self.assertEqual(len(job.messages),0) + + def testUnicode(self): + '''tests unicode spefiica''' + pass + testUnicode.todo="to implement" diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/pool.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/pool.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,42 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.trial import unittest +from mock import Mock +import copy + +from iro.controller import pool + +class PoolTestCase(unittest.TestCase): + + def setUp(self): + self._pools = copy.copy(pool.pools) + + def tearDown(self): + del pool.pools[:] + pool.pools.extend(self._pools) + + def testStartPool(self): + del pool.pools[:] + pool.pools.extend([Mock(),Mock()]) + pool.startPool("blafo") + for i in pool.pools: + i.start.assert_called_with("blafo") diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/reload.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/reload.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,35 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from ..test_helpers.dbtestcase import DBTestCase + + +class TestReload(DBTestCase): + '''tests for reloading feature''' + + def testLog(self): + pass + testLog.todo = "reloading logfile is not implemented" + + def testOffers(self): + pass + testOffers.todo = "reloading offers test not implemented" + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/sipgate.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/sipgate.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,106 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- +from twisted.trial import unittest +from twisted.web import xmlrpc, server +from twisted.internet import reactor +from decimal import Decimal + +from iro.error import NoRoute, NoTyp, NeededOption, RejectRecipient +from iro.telnumber import Telnumber +from iro.model.message import SMS, Fax +from iro.offer.sipgate import Sipgate + + +class XMLRPCServer(xmlrpc.XMLRPC): + def lookupProcedure(self, procedurePath): + return self.l[procedurePath] + +class TestSipgateProvider(unittest.TestCase): + + def setUp(self): + self.server = XMLRPCServer() + self.p = reactor.listenTCP(0, server.Site(self.server), + interface="127.0.0.1") + self.port = self.p.getHost().port + + def tearDown(self): + self.server = None + return self.p.stopListening() + + def getProvider(self, c=None): + _c={"username":"XXXXXXXX", + "password":"PPPPPPPP", + "typ": "sipgate" + } + + if c: + _c.update(c) + + ret = Sipgate("test") + ret.url="http://%%s:%%s@127.0.0.1:%d/" % self.port + ret.load(_c.items()) + return ret + + def testSendSMS(self): + p=self.getProvider() + content = u"Hello World - äüöß'\"" + + @xmlrpc.withRequest + def f(request, args): + self.assertEqual(request.getUser(),"XXXXXXXX") + self.assertEqual(request.getPassword(),"PPPPPPPP") + self.assertEqual(args,{'Content': u'Hello World - äüöß\'"', + 'TOS': 'text', + 'RemoteUri': 'sip:491701234567@sipgate.net'}) + return {"SessionID":"", "StatusCode":200, "StatusString":"Method success"} + + self.server.l = {"samurai.SessionInitiate": f} + + def s(r): + self.assertEqual(r.provider, p) + self.assertEqual(r.route, None) + self.assertEqual(r.costs, Decimal('0.079')) + self.assertEqual(r.exID, '') + self.assertEqual(r.count, 1) + + d = p.send("sms", Telnumber("01701234567"), SMS(content,None)) + d.addCallback(s) + return d + + + def testNeededOption(self): + s= self.getProvider() + self.assertEqual(s.username, "XXXXXXXX") + self.assertEqual(s.password, "PPPPPPPP") + self.assertEqual(s.price_sms, '0.079') + self.assertEqual(s.price_fax, '0.03') + + self.assertRaises(NeededOption, s.load,[]) + + def testSendFunc(self): + s = self.getProvider() + p = s.getSendFunc("sms",None) + self.assertEqual(p.func, s.send) + self.assertEqual(p.args, ("sms",)) + self.assertRaises(NoRoute,s.getSendFunc,"sms","foo") + self.assertRaises(NoTyp,s.getSendFunc,"mail2",None) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/smstrade.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/smstrade.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,151 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.trial import unittest +from decimal import Decimal +from mock import patch, Mock + +from iro.error import NoRoute, NoTyp, NeededOption, RejectRecipient +from iro.telnumber import Telnumber +from iro.model.message import SMS +from iro.offer.smstrade import Smstrade, SmstradeException, StatusCode + + +HOST = "localhost" +PORT = 9999 + +class TestSMStradeProvider(unittest.TestCase): + + def getProvider(self, c=None): + _c={"key":"XXXXXXXX", + "typ":"smstrade", + } + + if c: + _c.update(c) + + ret = Smstrade("test") + ret.load(_c.items()) + return ret + + @patch("urllib.urlopen") + def testSendSMS(self,mock_urlopen): + f = Mock() + f.readlines.return_value = ["100","12345678","0.055","1"] + mock_urlopen.return_value = f + + params = ["key=XXXXXXXX","to=00491701234567", "message=Hello+World", "route=gold", "message_id=1", "cost=1","count=1",'charset=utf-8'] + params.sort() + + p=self.getProvider() + content = "Hello World" + r = p.send("gold", Telnumber("01701234567"), SMS(content,None)) + + ca = mock_urlopen.call_args[0] + c=ca[1].split("&") + c.sort() + + self.assertEqual(ca[0],"https://gateway.smstrade.de") + self.assertEqual(c,params) + self.assertEqual(f.readlines.call_count,1) + + self.assertEqual(r.provider, p) + self.assertEqual(r.route, 'gold') + self.assertEqual(r.costs, Decimal('0.055')) + self.assertEqual(r.exID, '12345678') + self.assertEqual(r.count, 1) + + def testStatusCode(self): + s = StatusCode(10,"12345678","1.10",1) + self.assertEqual(str(s),'10: Empfaengernummer nicht korrekt.') + self.assertEqual(int(s),10) + self.assertEqual(s.count,1) + self.assertEqual(s.costs,Decimal("1.10")) + self.assertEqual(s.exID,'12345678') + + + def testUnknownStatusCode(self): + s = StatusCode(999) + self.assertEqual(str(s),'999: unknown statuscode.') + self.assertEqual(int(s),999) + self.assertEqual(s.count,0) + self.assertEqual(s.costs,Decimal("0.00")) + self.assertEqual(s.exID, None) + + + + def testRejectRecipient(self): + p=self.getProvider() + content = "Hello World" + e = self.assertRaises(RejectRecipient, p.send, "basic", Telnumber("+331701234567"), SMS(content,None)) + self.assertEqual(str(e),'Reject recipient(00331701234567): None') + + @patch("urllib.urlopen") + def testRejectRecipient70(self,mock_urlopen): + f = Mock() + f.readlines.return_value = ["70"] + mock_urlopen.return_value = f + + p=self.getProvider() + content = "Hello World" + self.assertRaises(RejectRecipient, p.send , "basic", Telnumber("01701234567") ,SMS(content,None)) + + f.readlines.return_value = ["71"] + e = self.assertRaises(RejectRecipient, p.send , "basic", Telnumber("01701234567"), SMS(content,None)) + self.assertEqual(str(e),'Reject recipient(00491701234567): 71: Feature nicht ueber diese Route moeglich.') + + @patch("urllib.urlopen") + def testUnknwonStatuscode(self,mock_urlopen): + f = Mock() + f.readlines.return_value = ["703"] + mock_urlopen.return_value = f + + p=self.getProvider() + content = "Hello World" + e = self.assertRaises(SmstradeException, p.send , "basic", Telnumber("01701234567"), SMS(content,None)) + self.assertEqual(str(e),'950: Error in external API.\n703: unknown statuscode.') + + @patch("urllib.urlopen") + def testSmstradeException(self,mock_urlopen): + f = Mock() + f.readlines.return_value = ["10"] + mock_urlopen.return_value = f + + p=self.getProvider() + content = "Hello World" + e = self.assertRaises(SmstradeException, p.send , "basic", Telnumber("01701234567"), SMS(content,None)) + self.assertEqual(str(e),'950: Error in external API.\n10: Empfaengernummer nicht korrekt.') + + + + def testNeededOption(self): + s= self.getProvider() + self.assertEqual(s.key, "XXXXXXXX") + + self.assertRaises(NeededOption, s.load,[]) + + def testSendFunc(self): + s = self.getProvider() + p = s.getSendFunc("sms","basic") + self.assertEqual(p.func, s.send) + self.assertEqual(p.args, ("basic",)) + self.assertRaises(NoRoute,s.getSendFunc,"sms","foo") + self.assertRaises(NoTyp,s.getSendFunc,"mail2","basic") diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/smtp.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/smtp.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,170 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.trial import unittest + +import email +from email.header import decode_header +import base64 +from ..test_helpers.smtp_helper import TestSMTPServer + +from mock import patch, Mock +import smtplib + +from iro.error import NoRoute, NoTyp, NeededOption +from iro.model.message import Mail +from iro.offer.smtp import SMTP + +HOST = "localhost" +PORT = 9999 + +class TestSMTPProvider(unittest.TestCase): + def setUp(self): + self.smtp_server = TestSMTPServer((HOST, PORT)) + self.smtp_server.start() + + def tearDown(self): + self.smtp_server.close() + + def getSMTP(self, c=None): + _c={"send_from":"send@t.de", + "host":HOST, + "port":PORT, + "typ":"smtp", + } + + if c: + _c.update(c) + + ret = SMTP("test") + ret.load(_c.items()) + return ret + + @patch("iro.model.message.formatdate") + def testSendMail(self,mock_f): + mock_f.return_value="Wed, 21 Mar 2012 17:16:11 +0100" + p=self.getSMTP() + content = "sadfadfgwertsdgsdf\n\nsdfgaerasdfsad\nadfasdf" + p.send("t@t.de", Mail("sub", content, None)) + + + self.assertEqual(len(self.smtp_server.rcvd), 1) + fromaddr, toaddrs, message = self.smtp_server.rcvd[0] + msg = email.message_from_string(message) + + self.assertEqual(fromaddr,"send@t.de") + self.assertEqual(msg.get_all("From"),["send@t.de"]) + self.assertEqual(toaddrs,["t@t.de"]) + self.assertEqual(msg.get_all("To"),["t@t.de"]) + self.assertEqual(msg.get_all("Date"),["Wed, 21 Mar 2012 17:16:11 +0100"]) + self.assertEqual(decode_header(msg.get("Subject")),[("sub","utf-8")]) + self.assertEqual(base64.b64decode(msg.get_payload()),content) + + def testSendMailExtraFrm(self): + p=self.getSMTP() + content = "" + p.send("t@t.de", Mail("sub", content, "f@t.de")) + + self.assertEqual(len(self.smtp_server.rcvd), 1) + fromaddr, toaddrs, message = self.smtp_server.rcvd[0] + msg = email.message_from_string(message) + + self.assertEqual(fromaddr,"f@t.de") + self.assertEqual(msg.get_all("From"),["f@t.de"]) + + def testSendMailException(self): + p=self.getSMTP({"port":PORT-1}) + content = "" + self.assertRaises(IOError, p.send, "t@t.de", Mail("sub", content, "f@t.de")) + + self.assertEqual(len(self.smtp_server.rcvd), 0) + + @patch("smtplib.SMTP_SSL") + def testSSLSendMail(self,mock_ssl): + def se(*args): + return smtplib.SMTP(*args) + mock_ssl.side_effect=se + + p=self.getSMTP({"SSL":True}) + content = "sadfadfgwertsdgsdf\n\nsdfgaerasdfsad\nadfasdf" + p.send("t@t.de", Mail("sub", content, None)) + + self.assertEqual(mock_ssl.call_count,1) + + self.assertEqual(len(self.smtp_server.rcvd), 1) + + @patch("smtplib.SMTP") + def testTLSSendMail(self,mock_smtp): + mock_s = Mock() + mock_smtp.return_value = mock_s + + p=self.getSMTP({"TLS":True}) + content = "sadfadfgwertsdgsdf\n\nsdfgaerasdfsad\nadfasdf" + p.send("t@t.de", Mail("sub", content, None)) + + mock_s.starttls.assert_called_once_with() + self.assertEqual(mock_s.sendmail.call_count,1) + self.assertEqual([i[0] for i in mock_s.method_calls],["starttls","sendmail","quit"]) + + @patch("smtplib.SMTP") + def testLoginSendMail(self,mock_smtp): + mock_s = Mock() + mock_smtp.return_value = mock_s + + p=self.getSMTP({"user":"user","password":"pw"}) + content = "sadfadfgwertsdgsdf\n\nsdfgaerasdfsad\nadfasdf" + p.send("t@t.de", Mail("sub", content, None)) + + mock_s.login.assert_called_once_with("user","pw") + self.assertEqual(mock_s.sendmail.call_count,1) + self.assertEqual([i[0] for i in mock_s.method_calls],["login","sendmail","quit"]) + + + def testNeededOption(self): + c={"send_from":"send@t.de", + "host":HOST, + "port":PORT, + "user":"u", + "password":"p", + "typ":"smtp", + } + s = self.getSMTP(c) + self.assertEqual(s.send_from, "send@t.de") + self.assertEqual(s.host, HOST) + self.assertEqual(s.port, PORT) + self.assertEqual(s.user, "u") + self.assertEqual(s.password, "p") + self.assertEqual(s.SSL,False) + self.assertEqual(s.TLS,False) + + c.update({"TLS":True, "SSL":True}) + s = self.getSMTP(c) + self.assertEqual(s.SSL,True) + self.assertEqual(s.TLS,True) + + del c["host"] + self.assertRaises(NeededOption, s.load, c) + + def testSendFunc(self): + s = self.getSMTP() + self.assertEqual(s.getSendFunc("mail",None), s.send) + self.assertRaises(NoRoute,s.getSendFunc,"mail","foo") + self.assertRaises(NoTyp,s.getSendFunc,"mail2","foo") diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/stopableServer.py --- a/iro/tests/stopableServer.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,118 +0,0 @@ -import ConfigParser - -import threading - -from multiprocessing import Queue -from multiprocessing.managers import BaseManager - -from iro import xmlrpc,anbieter -from iro.user import User, Admin -from iro.iro import MySMTP,MySmstrade,MyUserDB -from iro.job import SMSJob, FAXJob, MailJob -from iro.joblist import Joblist -from iro.providerlist import Providerlist - -class StoppableXMLRPCServer(xmlrpc.SecureUserDBXMLRPCServer, threading.Thread): - running=False - def __init__(self, *args, **kwargs): - xmlrpc.SecureUserDBXMLRPCServer.__init__(self, *args, **kwargs) - threading.Thread.__init__(self) - self.timeout=.5 - - def start(self): - self.running=True - threading.Thread.start(self) - - - def run(self): - # *serve_forever* muss in einem eigenen Thread laufen, damit man es - # unterbrochen werden kann! - while (self.running): - try: - self.handle_request() - except : - break - - def stop(self): - if (self.running): - self.running=False - self.server_close() - self.join() - - def __del__(self): - self.stop() - - def __enter__(self): - self.start() - return self - - def __exit__(self,type,value,traceback): - self.stop() - - -class Internal: - pass - -def init_server(): - userlist=[{"name":"test","password":"test", "class":User}, - {"name":"test2","password":"test2", "class": User}, - {"name":"admin","password":"admin", "class": Admin}] - - - - class MyManager(BaseManager): - pass - - internal=Internal() - - MyManager.register('SMSJob', SMSJob) - MyManager.register('FaxJob', FAXJob) - MyManager.register('MailJob',MailJob) - MyManager.register('Providerlist',Providerlist) - manager = MyManager() - manager.start() - - internal.manager=manager - - #anbieter erzeugen und konfigurieren - sip=anbieter.sipgate() - sip.read_basic_config("iro.conf") - - localhost=MySMTP() - localhost.read_basic_config("iro.conf") - - smstrade=MySmstrade() - smstrade.read_basic_config("iro.conf") - - #Benutzerdatenbank erstellen - queue = Queue() - internal.queue=queue - provider=Providerlist() - internal.provider=provider - provider.add("sipgate", sip, ["sms", "fax", ]) - provider.add("smstrade", smstrade, ["sms", ]) - provider.add("geonet", None, ["sms", "fax", ]) - provider.add("fax.de", None, ["sms", "fax", ]) - provider.add("localhost", localhost, ["mail", ]) - provider.setDefault("sms","smstrade") - provider.setDefault("fax","sipgate") - provider.setDefault("mail","localhost") - jobqueue=Joblist(manager, queue, provider) - internal.jobqueue=jobqueue - userdb=MyUserDB(userlist,jobqueue) - internal.userdb=userdb - - - #Server starten - cp = ConfigParser.ConfigParser() - cp.read(["iro.conf"]) - cert=cp.get('server', 'cert') - key=cp.get('server', 'key') - serv = StoppableXMLRPCServer(addr=("localhost", 8000), - userdb=userdb, - certificate=cert,privatekey=key, - logRequests=False) - serv.relam="xmlrpc" - internal.serv=serv - return internal - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/task.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/task.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,188 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.internet import reactor +from twisted.internet.defer import inlineCallbacks, Deferred + +from Queue import deque + +from iro.model.schema import User, Offer as DBOffer, Userright +from iro.model.message import SMS +from iro.model.status import Status +from iro.model.offer import offers +from iro.model.job import exJobs + +from iro.controller.task import createJob, Task +from iro.controller.pool import taskPool, dbPool + +from iro.offer import Offer, Provider + +from iro.error import NoRouteForTask,RejectRecipient +from iro.telnumber import Telnumber + +from ..test_helpers.dbtestcase import DBTestCase + +class TaskTestCase(DBTestCase): + def setUp(self): + DBTestCase.setUp(self) + dbPool.start(reactor) + + def tearDown(self): + exJobs.clear() + offers.clear() + dbPool.pool.stop() + taskPool.pool.q.queue = deque() + DBTestCase.tearDown(self) + +class TestTasks(TaskTestCase): + + @inlineCallbacks + def testCreateSMS(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + + job = yield createJob(u,[Telnumber('0123325456')],SMS('sms'),[]) + + self.assertEqual(taskPool.pool.q.qsize(),1) + + self.assertEqual(job.tasks.keys(),[Telnumber('0123325456')]) + self.assertIsInstance(job.tasks[Telnumber('0123325456')], Task) + + @inlineCallbacks + def testRun(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="test", provider="bla", route="basic", typ="sms") + u.rights.append(Userright(o)) + + p=Provider(name="p", typs={"sms":["test",]}) + def send(typ,route,recipient,message): + return Status(provider=p, route=route) + p.send=send + offers["test"] = Offer("test",provider=p, route="test", typ="sms") + + exjob = yield exJobs.create(u, [Telnumber('123456789')], SMS('test'), ['test']) + + task=Task(Telnumber('123456789'), exjob) + task.d=Deferred() + task._run() + ret = yield task.d + self.assertIsInstance(ret, Status) + self.assertEqual(ret.provider, p) + self.assertEqual(ret.route, "test") + + def testNoRoute(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + + def c(exjob): + task = Task(Telnumber('123456789'), exjob) + task.d = Deferred() + task._run() + self.assertFailure(task.d, NoRouteForTask) + return task.d + + d = exJobs.create(u, [Telnumber('123456789')], SMS('test'), []) + d.addCallback(c) + return d + + def testNoRoute2(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="test", provider="bla", route="basic", typ="sms") + u.rights.append(Userright(o)) + o=DBOffer(name="test2", provider="bla", route="basic2", typ="sms") + u.rights.append(Userright(o)) + + p=Provider(name="p", typs={"sms":["basic","basic2"]}) + def send(typ,route,recipient,message): + raise RejectRecipient(recipient) + p.send=send + offers["test"] = Offer("test",provider=p, route="basic", typ="sms") + offers["test2"] = Offer("test2",provider=p, route="basic2", typ="sms") + + def c(exjob): + task = Task(Telnumber('123456789'), exjob) + task.d = Deferred() + task._run() + self.assertFailure(task.d, NoRouteForTask) + return task.d + + d = exJobs.create(u, [Telnumber('123456789')], SMS('test'), ['test','test2']) + d.addCallback(c) + return d + + def testChaining(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(u) + o=DBOffer(name="test", provider="bla", route="basic", typ="sms") + u.rights.append(Userright(o)) + o=DBOffer(name="test2", provider="bla", route="basic2", typ="sms") + u.rights.append(Userright(o)) + + p=Provider(name="p", typs={"sms":["basic","basic2"]}) + def send(typ,route,recipient,message): + if route=="basic": + raise Exception("oh my god an exception") + return Status(p,route) + p.send=send + offers["test"] = Offer("test",provider=p, route="basic", typ="sms") + offers["test2"] = Offer("test2",provider=p, route="basic2", typ="sms") + + def c(exjob): + task = Task(Telnumber('123456789'), exjob) + task.d = Deferred() + task.d.addCallback(stat, task) + task._run() + return task.d + + def stat(status, task): + errors = self.flushLoggedErrors(Exception) + self.assertEqual(len(errors), 1) + self.assertEqual(self.log.e[1]['why'], "Job(%s): Send to '0049123456789' failed via 'test'"% task.job.dbjob) + self.assertIsInstance(status, Status) + self.assertEqual(status.provider, p) + self.assertEqual(status.route, "basic2") + + d = exJobs.create(u, [Telnumber('123456789')], SMS('test'), ['test','test2']) + d.addCallback(c) + return d + + + + def testSetStatus(self): + task=Task(Telnumber('123456789'), None) + self.assertEqual(task.status,None) + self.assertEqual(task.error,False) + + self.assertEqual(task.setStatus("fooja"),"fooja") + self.assertEqual(task.status,"fooja") + + def testSetError(self): + task=Task(Telnumber('123456789'), None) + self.assertEqual(task.setError("fooja"),"fooja") + self.assertEqual(task.status,"fooja") + self.assertEqual(task.error,True) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/telnumber.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/telnumber.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,105 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +try: + import unittest + unittest.case +except AttributeError: + import unittest2 as unittest + +from iro.validate import vTel +from iro.error import InvalidTel +from iro.telnumber import Telnumber + +class testTelefonnumbers(unittest.TestCase): + """tests for telefonnumbers""" + + def testMultipleTelnumbers(self): + '''test the telefon validator''' + ret = vTel(["0123/456(78)","+4912346785433","00123435456-658"], None) + x=[Telnumber('+4912345678'),Telnumber('012346785433'),Telnumber('+123435456658')] + self.assertEqual(ret,x) + + def testInvalidTelnumbers(self): + '''invalid telnumbers''' + + numbers=['xa','+1','1-23',';:+0','0123'] + + for number in numbers: + with self.assertRaises(InvalidTel) as e: + vTel([number], None) + self.assertEqual(e.exception.number,number) + + with self.assertRaises(InvalidTel) as e: + vTel(['01234']+numbers, None) + self.assertEqual(e.exception.number,numbers[0]) + + def testDoubles(self): + ret = vTel(["0123/456(78)","+4912345678","004912345678"], None) + x=[Telnumber('+4912345678')] + self.assertEqual(ret,x) + + def equalNumber(self, tel1, tel2): + self.assertEqual(tel1.number, tel2.number) + self.assertEqual(tel1.land, tel2.land) + + def testWrongNumber(self): + telnum=Telnumber() + self.assertRaises(InvalidTel, telnum.createNumber, "hallo") + self.assertRaises(InvalidTel, telnum.createNumber, "0?242") + + def testNumber(self): + telnum=Telnumber("0345-94103640") + telnum2=Telnumber("+49345/94103640") + telnum3=Telnumber("00493459410364-0") + telnum4=Telnumber("+49(0)345-94103640") + + self.assertEqual(telnum.land, "49") + self.assertEqual(telnum.number, "34594103640") + + self.equalNumber(telnum, telnum2) + self.equalNumber(telnum, telnum3) + self.equalNumber(telnum, telnum4) + + def testEqual(self): + telnum=Telnumber("0345-94103640") + telnum2=Telnumber("+49345/94103640") + li=[] + self.assertEqual(telnum == telnum2, True) + self.assertEqual(telnum <> telnum2, False) + self.assertEqual(telnum, telnum2) + self.assertEqual(telnum in li,False) + li.append(telnum) + self.assertEqual(telnum in li,True) + self.assertEqual(telnum2 in li,True) + + def testHash(self): + telnum=Telnumber("0345-94103640") + self.assertEqual(hash(telnum),hash("004934594103640")) + self.assertNotEqual(hash(telnum),hash("004934594103641")) + + def testString(self): + telnum=Telnumber("0345-94103640") + self.assertEqual(str(telnum),"004934594103640") + + def testRepr(self): + telnum=Telnumber("0345-94103640") + self.assertEqual(repr(telnum),"") diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/test.pdf diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/testJob.py --- a/iro/tests/testJob.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,82 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -import xmlrpclib -from stopableServer import init_server -from iro.anbieter.content import SMS,FAX,Mail - -class TestServer(unittest.TestCase): - - def setUp(self): - self.i = init_server() - self.serv=self.i.serv - self.serv.start() - - def tearDown(self): - self.serv.stop() - - - def SendSMS(self,msg): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - id=client.startSMS(msg,["01234", ] ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': unicode(msg)}} ) - ele=self.i.queue.get(.1) - self.assertEqual(ele.getRecipients(),["01234", ] ) - self.assertNotEqual(ele.getMessage(),SMS('') ) - self.assertEqual(ele.getMessage(),SMS(msg) ) - - def testSimpleSMS(self): - self.SendSMS("test") - - def testSpecialCharacters(self): - self.SendSMS(u"!\"§$%&/()=?\'") - self.SendSMS(u"@ł€ł€¶ŧł¼¼½¬¬↓ŧ←ĸ↓→øđŋħ“”µ·…–|") - - def testSendFAX(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - msg="2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ" - id=client.startFAX("test",xmlrpclib.Binary(msg),["01234", ] ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) - ele=self.i.queue.get(.1) - self.assertEqual(ele.getRecipients(),["01234", ] ) - self.assertEqual(ele.getMessage(),FAX('test','',[msg])) - - def testDoubleFAX(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - msg="2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ" - pdf=open('tests/test.pdf').read() - id=client.startFAX("test",[xmlrpclib.Binary(msg),xmlrpclib.Binary(pdf)],["01234", ] ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) - ele=self.i.queue.get(.1) - self.assertEqual(ele.getRecipients(),["01234", ] ) - self.assertEqual(ele.getMessage(),FAX('test','',[msg, pdf])) - - def testSendMail(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - msg=u"2134wergsdfg4w56q34134æſðđæðſđŋ³@¼ŧæðđŋł€¶ŧ€¶ŧ" - id=client.startMail("test",msg,["test@test.de", ],'absender@test.de' ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) - ele=self.i.queue.get(.1) - self.assertEqual(ele.getRecipients(),["test@test.de", ] ) - self.assertEqual(ele.getMessage(),Mail('test',msg,'absender@test.de')) - self.assertEqual(ele.getMessage().as_string(),"""Content-Type: text/plain; charset="utf-8" -MIME-Version: 1.0 -Content-Transfer-Encoding: base64 -Subject: =?utf-8?q?test?= - -MjEzNHdlcmdzZGZnNHc1NnEzNDEzNMOmxb/DsMSRw6bDsMW/xJHFi8KzQMK8xafDpsOwxJHFi8WC -4oKswrbFp+KCrMK2xac= -""") - sub=u"³¼½ſðđŋſ€¼½ÖÄÜß" - id=client.startMail(sub,msg,["test@test.de", ],'absender@test.de' ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': sub}}) - ele=self.i.queue.get(.1) - self.assertEqual(ele.getMessage(),Mail(sub, msg, 'absender@test.de')) - -if __name__ == "__main__": - unittest.main() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/testWorker.py --- a/iro/tests/testWorker.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,147 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest -import logging -from time import sleep - -from multiprocessing import Queue -from multiprocessing.managers import BaseManager, ListProxy - -from iro.worker import Worker -from iro.job import Job, SMSJob -from iro.anbieter.anbieter import anbieter -from iro.anbieter.content import SMS -from iro.providerlist import Providerlist - -from logging.handlers import BufferingHandler - -class MyHandler(BufferingHandler): - def __init__(self,buffer=None): - '''BufferingHandler takes a "capacity" argument - so as to know when to flush. As we're overriding - shouldFlush anyway, we can set a capacity of zero. - You can call flush() manually to clear out the - buffer. - buffer: log messages to this buffer, needed f.ex for processes or threads''' - BufferingHandler.__init__(self, 0) - self.buffer=buffer - - def shouldFlush(self): - return False - - def emit(self, record): - if record.exc_info: #sonst geht das append schief, weil nicht picklebar - record.exc_info=record.exc_info[:-1] - self.buffer.append(record.__dict__) - - -class BadJob(Job): - def start(self,id=None): - Job.start(self,id) - raise Exception("Error") - -#einen Manager anlegen, der Job und eine Liste anbietet -class MyManager(BaseManager): - pass -MyManager.register('Job', Job) -MyManager.register('SMSJob', SMSJob) -MyManager.register('BadJob', BadJob) -MyManager.register('list', list, ListProxy) -MyManager.register('Providerlist',Providerlist) - -class TestWorker(unittest.TestCase): - def setUp(self): - #erstelle eine Queue&Manager - self.manager = MyManager() - self.queue = Queue() - - #manager starten, damit wir Logging anpassen können - self.manager.start() - self.setUpLogging() - - self.providerlist=self.manager.Providerlist() - self.providerlist.add("test", anbieter() , ["sms", ]) - - #eigentlich Workerprocess starten - self.worker= Worker(self.queue) - self.worker.start() - - def tearDown(self): - #Thread&Queue stoppen - self.stop() - - #Logging änderungen rückgängig - self.tearDownLogging() - self.manager.shutdown() - - def stop(self): - self.queue.close() - self.queue.join_thread() - self.worker.terminate() - - - def setUpLogging(self): - '''Logging so umbasteln, das wir alle logging Meldung in self.buf sind''' - #wir brauchen eine threadsichere liste - self.buffer=self.manager.list() - - #Handler erstellen, der in den Buffer schreibt - self.handler = h = MyHandler(self.buffer) - self.logger =l= logging.getLogger() - - #Level anpassen - l.setLevel(logging.DEBUG) - h.setLevel(logging.DEBUG) - l.addHandler(h) - - def tearDownLogging(self): - '''crazy logging hacks wieder entfernen''' - self.logger.removeHandler(self.handler) - self.logger.setLevel(logging.NOTSET) - self.handler.close() - - - def testJob(self): - '''einen Job verarbeiten''' - job=self.manager.Job(None,None,"test") - self.assertEqual(job.getStatus(),("init",{})) - self.queue.put(job) - sleep(.1) - self.stop() - self.assertEqual(job.getStatus(),("started",{})) - self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"], - [(20,'Workerprocess läuft nun...'), - (20,'ein neuer Job(1)'), - (20,'Job(1) fertig ;)')]) - - def testBadJob(self): - '''einen Job verarbeiten, der fehlschlägt''' - job=self.manager.BadJob(None,None,"test") - self.assertEqual(job.getStatus(),("init",{})) - self.queue.put(job) - sleep(.1) - self.stop() - self.assertEqual(job.getStatus(),("error",{})) - self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"], - [(20,'Workerprocess läuft nun...'), - (20,'ein neuer Job(1)'), - (40,'Job(1) fehlgeschlagen :(')]) - error=Exception('Error') - self.assertEqual(self.buffer[-1]['exc_info'][0],type(error)) - self.assertEqual(str(self.buffer[-1]['exc_info'][1]),str(error)) - - def testSMSJob(self): - job=self.manager.SMSJob(self.providerlist, "test", "name", SMS("message"),[012345]) - self.assertEqual(job.getStatus(),("init",{})) - self.queue.put(job) - sleep(.1) - self.stop() - self.assertEqual(job.getStatus(),("sended",{'failed': [], 'good': []})) - self.assertEqual([(l['levelno'],l['msg']) for l in self.buffer if l['name']=="iro.worker"], - [(20,'Workerprocess läuft nun...'), - (20,'ein neuer Job(1)'), - (20,'Job(1) fertig ;)')]) - - -if __name__ == "__main__": - unittest.main() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/testXMLRPCServer.py --- a/iro/tests/testXMLRPCServer.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- - -import unittest - -import xmlrpclib -from stopableServer import init_server - -class TestServer(unittest.TestCase): - - def setUp(self): - self.i = init_server() - self.serv=self.i.serv - - self.serv.start() - - def tearDown(self): - self.serv.stop() - - def testLogin(self): - self.assertEqual(xmlrpclib.Server("https://test:test@localhost:8000").status(), {}) - self.assertEqual(xmlrpclib.Server("https://test2:test2@localhost:8000").status(), {}) - self.assertRaises(xmlrpclib.ProtocolError, xmlrpclib.Server("https://test2:test@localhost:8000").status) - self.assertRaises(xmlrpclib.ProtocolError,xmlrpclib.Server ("https://test:test2@localhost:8000").status) - - def testsendSMS(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - id=client.startSMS("test",["01234", ] ) - self.assertEqual(client.status(id),{id: {'status': ['init',{}], 'name': 'test'}} ) - - def testTwoUser(self): - u1="https://test:test@localhost:8000" - u2="https://test2:test2@localhost:8000" - admin="https://admin:admin@localhost:8000" - client1=xmlrpclib.Server(u1) - client2=xmlrpclib.Server(u2) - admin=xmlrpclib.Server(admin) - id1=client1.startSMS("test",["01234"] ) - self.assertEqual(client2.status(),{} ) - self.assertEqual(admin.status(id1),{id1: {'status': ['init', {}], 'name': 'test'}} ) - id2=client2.startSMS("test2",["01234"] ) - self.assertNotEqual(id1, id2) - self.assertEqual(client1.status(),{id1: {'status': ['init', {}], 'name': 'test'}}) - self.assertEqual(client2.status(),{id2: {'status': ['init', {}], 'name': 'test2'}}) - self.assertEqual(admin.status(),{id1: {'status': ['init', {}], 'name': 'test'}, - id2: {'status': ['init', {}], 'name': 'test2'}} ) - - self.assertEqual(client2.status(id1), {}) - self.assertEqual(client1.status(id2), {}) - - def testGetProvider(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - self.assertEqual(client.getProvider("sms"), ["fax.de","geonet", "sipgate", "smstrade"]) - self.assertEqual(client.getProvider("fax"), ["fax.de","geonet", "sipgate"]) - self.assertEqual(client.getProvider("mail"), ["localhost"]) - - self.assertRaises(xmlrpclib.ProtocolError,client.getProvider, "temp") - - def testGetDefault(self): - servstr="https://test:test@localhost:8000" - client=xmlrpclib.Server(servstr) - self.assertEqual(client.getDefaultProvider("sms"), "smstrade") - self.assertEqual(client.getDefaultProvider("fax"),"sipgate") - self.assertEqual(client.getDefaultProvider("mail"), "localhost") - - self.assertRaises(xmlrpclib.ProtocolError,client.getDefaultProvider, "temp") - - -if __name__ == "__main__": - unittest.main() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/testloglock.py --- a/iro/tests/testloglock.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ - -import unittest -import os -import tempfile -import signal -import threading -import time - -class testLogLock(unittest.TestCase): - def test_ThreadingAndLocks(self): - #create a thread, have that thread grab a lock, and sleep. - fd, file = tempfile.mkstemp('.nonlog') - _lock = threading.RLock() - os.close(fd) - def locker(): - os.write(fd, 'Thread acquiring lock\n') - _lock.acquire() - os.write(fd, 'Thread acquired lock\n') - time.sleep(0.4) - os.write(fd, 'Thread releasing lock\n') - _lock.release() - os.write(fd, 'Thread released lock\n') - - #Then in the main thread, throw a signal to self - def handleSignal(sigNum, frame): - os.write(fd, 'Main Thread acquiring lock\n') - lock = False - endtime = time.time() + 1.0 - while not lock: - lock = _lock.acquire(blocking=0) - time.sleep(0.01) - if time.time() > endtime: - break - if not lock: - os.write(fd, 'Main Thread could not acquire lock\n') - return - os.write(fd, 'Main Thread acquired lock\n') - os.write(fd, 'Main Thread releasing lock\n') - _lock.release() - os.write(fd, 'Main Thread released lock\n') - - sighndlr = signal.signal(signal.SIGUSR1, handleSignal) - try: - fd = os.open(file, os.O_SYNC | os.O_WRONLY | os.O_CREAT) - thread = threading.Thread(target=locker) - thread.start() - time.sleep(0.1) - os.kill(os.getpid(), signal.SIGUSR1) - thread.join() - - #check the results - os.close(fd) - fileconts = open(file, 'r').read() - self.assertTrue('Main Thread released lock' in fileconts) - self.assertEqual(fileconts, - '''Thread acquiring lock -Thread acquired lock -Main Thread acquiring lock -Thread releasing lock -Thread released lock -Main Thread acquired lock -Main Thread releasing lock -Main Thread released lock -''') - - #Now try after acquiring the lock from the main thread - fd = os.open(file, os.O_SYNC | os.O_WRONLY | os.O_CREAT) - _lock.acquire() - thread = threading.Thread(target=locker) - thread.start() - time.sleep(0.1) - os.kill(os.getpid(), signal.SIGUSR1) - _lock.release() - thread.join() - os.close(fd) - fileconts = open(file, 'r').read() - self.assertEqual(fileconts, - '''Thread acquiring lock -Main Thread acquiring lock -Main Thread acquired lock -Main Thread releasing lock -Main Thread released lock -Thread acquired lock -Thread releasing lock -Thread released lock -''') - - finally: - signal.signal(signal.SIGUSR1, sighndlr) - try: - os.close(fd) - except OSError: - pass - try: - os.unlink(file) - except OSError: - pass - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/validate.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/validate.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,156 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.trial import unittest +from mock import Mock + +from iro.validate import vBool, vInteger, vHash, validate +from iro.error import ValidateException + +class testValidators(unittest.TestCase): + '''test for simple validators''' + + def testBool(self): + self.assertEqual(vBool(True,None),True) + self.assertEqual(vBool(1,None),True) + self.assertEqual(vBool("true",None),True) + self.assertEqual(vBool("True",None),True) + self.assertEqual(vBool("TRUE",None),True) + + self.assertEqual(vBool(False,None),False) + self.assertEqual(vBool(0,None),False) + self.assertEqual(vBool("false",None),False) + self.assertEqual(vBool("False",None),False) + self.assertEqual(vBool("FALSE",None),False) + + e = self.assertRaises(ValidateException, vBool, "TRue","test") + self.assertEqual(e.msg,"test is not boolean") + + def testInteger(self): + self.assertEqual(vInteger(123,None),123) + self.assertEqual(vInteger("123",None),123) + self.assertEqual(vInteger("-123",None),-123) + + self.assertRaises(ValidateException, vInteger, "a123",None) + + def testIntegerLimits(self): + self.assertEqual(vInteger(10,None,maxv=10),10) + self.assertRaises(ValidateException, vInteger, 11, None, maxv=10) + + self.assertEqual(vInteger(4,None,minv=4),4) + self.assertRaises(ValidateException, vInteger, 3, None, minv=4) + + self.assertRaises(ValidateException, vInteger, -1, None, minv=0) + self.assertRaises(ValidateException, vInteger, 1, None, maxv=0) + + def testIntegerNoneAllowed(self): + self.assertEqual(vInteger(None,None,none_allowed=True),None) + self.assertEqual(vInteger('',None,none_allowed=True),None) + + self.assertRaises(ValidateException, vInteger, "", None) + self.assertRaises(ValidateException, vInteger, None, None) + + def testHash(self): + self.assertEqual(vHash("0123456789abcdef",None),"0123456789abcdef") + self.assertEqual(vHash("0123456789ABCDEF",None),"0123456789abcdef") + self.assertEqual(vHash("F",None),"f") + self.assertEqual(vHash("",None),'') + + self.assertRaises(ValidateException, vHash, "GHIJKL", None) + + def testHashLimits(self): + self.assertEqual(vHash("F",None,minlength=1),"f") + self.assertRaises(ValidateException, vHash, "", None, minlength=1) + + self.assertEqual(vHash("Fa",None,maxlength=2),"fa") + self.assertRaises(ValidateException, vHash, "123", None, maxlength=1) + + + def testValidate(self): + f = Mock() + f.return_value = "valid" + @validate("t",f,True,1,2,3,4,k=5) + def g(u=False, t="bla"): + return t + d = g(t="uhuhu") + def r(t): + f.called_once_with("uhuhu","t",1,2,3,4,k=5) + self.assertEqual(t,"valid") + d.addCallback(r) + return d + + def testValidateMissingNeed(self): + f = Mock() + @validate("t",f,True,1,2,3,4,k=5) + def g(u, t="buuh"): + return t + e = self.assertRaises(ValidateException, g, u="uhuhu", t = None) + self.assertEqual(str(e),'700: t is nessasary') + + def testValidateMissingNeedNonExplicit(self): + f = Mock() + @validate("t",f,True,1,2,3,4,k=5) + def g(u, **k): + return k["t"] + e = self.assertRaises(ValidateException, g, u="uhuhu") + self.assertEqual(str(e),'700: t is nessasary') + + + def testValidateMissingNeed2(self): + f = Mock() + f.return_value = "valid" + @validate("t",f,True,1,2,3,4,k=5) + def g(u, t="buuh"): + return t + + d = g(True) + + def r(t): + f.called_once_with("buuh","t",1,2,3,4,k=5) + self.assertEqual(t,"valid") + d.addCallback(r) + return d + + def testvalidateNoNeed(self): + f = Mock() + f.return_value = "valid" + @validate("t",f,False,1,2,3,4,k=5) + def g(u, t="buuh"): + return t + d = g("uhu") + def r(t): + self.assertEqual(f.called,True) + self.assertEqual(t,"valid") + d.addCallback(r) + return d + + def testvalidateNoNeed2(self): + f = Mock() + f.return_value = "valid" + @validate("t",f,False,1,2,3,4,k=5) + def g(u, **k): + return k["t"] + d = g("uhu") + def r(t): + self.assertEqual(f.called,False) + self.assertEqual(t,None) + d.addCallback(r) + return d diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/viewinterface.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/viewinterface.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,292 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.internet.defer import inlineCallbacks +from datetime import datetime +from Queue import deque +from mock import patch + +from iro.model.schema import User, Offer, Userright, Job, Message +from iro.controller.viewinterface import Interface +from iro.controller.pool import taskPool + +from iro.model.message import SMS, Fax, Mail +from iro.model.pool import data +from iro.model.offer import offers +from iro.model.job import exJobs + +import iro.error as IroError + +from ..test_helpers.dbtestcase import DBTestCase +from ..test_helpers.utils import DummyPool + +class ViewInterfaceTest(DBTestCase): + """tests for the xmlrpc interface""" + def setUp(self): + DBTestCase.setUp(self) + self.pool = data.pool + data.pool = DummyPool() + + def tearDown(self): + exJobs.clear() + offers.clear() + taskPool.pool.q.queue = deque() + data.pool = self.pool + self.pool = None + DBTestCase.tearDown(self) + + @inlineCallbacks + def testStatus(self): + ''' test the status function''' + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(User(name='test',apikey='abcdef123456789')) + st = yield Interface().status('abcdef123456789') + self.assertEqual(st, {}) + + with self.session() as session: + u = session.merge(u) + j = Job(info='info', status="started") + j.user=u + session.add(j) + session.commit() + jid=j.id + status = {str(jid):{"status":"started"}} + st = yield Interface().status('abcdef123456789',jid) + self.assertEqual(st, status) + st = yield Interface().status('abcdef123456789') + self.assertEqual(st, status) + st = yield Interface().status('abcdef123456789', '', 'false') + self.assertEqual(st, status) + st = yield Interface().status('abcdef123456789', '', 0) + self.assertEqual(st, status) + + #JobNotFound + d = Interface().status('abcdef123456789',jid+1) + self.assertFailure(d, IroError.JobNotFound) + yield d + + #self.assertEqual(self.__rpc2().status('abcdef123456789','abcde', True), ["",'abcde', True]) + #self.assertEqual(self.__rpc2().status('abcdef123456789', '', 'true'), ["", '', True]) + #self.assertEqual(self.__rpc2().status('abcdef123456789', '', 1), ["", '', True]) + + def testNoSuchUser(self): + '''a unknown user should raise a UserNotNound Exception + bewcause xmlrpc only has a Fault exception this Exception has to be deliverd through a xmlrpclib.Fault Exception''' + d = Interface().status('abcdef123456789') + self.assertFailure(d, IroError.UserNotFound) + return d + + + def testValidationFault(self): + '''a validate Exception should be translated to a xmlrpclib.Fault.''' + d = Interface().status('xxx') + self.assertFailure(d, IroError.ValidateException) + + @inlineCallbacks + def testRoutes(self): + '''test the route function''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + r = yield Interface().routes('abcdef123456789','sms') + self.assertEqual(r, ['sipgate_basic']) + + d = Interface().routes('abcdef123456789','fax') + self.assertFailure(d,IroError.ValidateException) + yield d + + with self.session() as session: + o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms") + u = session.query(User).filter_by(name="test").first() + u.rights.append(Userright(o)) + o=Offer(name="faxde", provider="faxde", route="", typ="fax") + session.add(o) + session.commit() + + r = yield Interface().routes('abcdef123456789','sms') + self.assertEqual(r, ['sipgate_basic','sipgate_plus']) + r = yield Interface().routes('abcdef123456789','fax') + self.assertEqual(r, []) + + + with self.session() as session: + u = session.query(User).filter_by(name="test").first() + u.rights.append(Userright(o)) + + r = yield Interface().routes('abcdef123456789','sms') + self.assertEqual(r, ['sipgate_basic','sipgate_plus']) + r = yield Interface().routes('abcdef123456789','fax') + self.assertEqual(r, ['faxde']) + + @inlineCallbacks + def testDefaultRoutes(self): + '''test the defaultRoute function''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o,True)) + o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + r = yield Interface().defaultRoute('abcdef123456789','sms') + self.assertEqual(r, ['sipgate_basic']) + + @inlineCallbacks + def testTelnumbers(self): + '''test the telefon validator''' + r = yield Interface().telnumber(["0123/456(78)","+4912346785433","00123435456-658"]) + self.assertEqual(r, True) + + invalid=['xa','+1','1-23',';:+0','0123'] + + d = Interface().telnumber(['01234']+invalid) + def x(failure): + self.assertEqual(failure.getErrorMessage(),"701: No valid telnumber: '%s'"%invalid[0]) + failure.raiseException() + d.addErrback(x) + self.assertFailure(d, IroError.InvalidTel) + yield d + + @inlineCallbacks + def testVaildEmail(self): + '''test vaild email adresses (got from wikipedia)''' + validmails=["niceandsimple@example.com"] + r = yield Interface().email(validmails) + self.assertEqual(r,True) + + def testInvaildEmail(self): + '''test invaild email adresses (got from wikipedia)''' + invalid=["Abc.example.com",] + d = Interface().email(invalid) + self.assertFailure(d, IroError.InvalidMail) + return d + + @inlineCallbacks + def testBill(self): + '''test bill function''' + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + + r = yield Interface().bill(apikey) + self.assertEqual(r,{'total':{'price':0.0,'anz':0}}) + + with self.session() as session: + u = session.merge(u) + o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") + u.rights.append(Userright(o)) + j = Job(info='i',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o)) + u.jobs.append(j) + + j = Job(info='a',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o)) + u.jobs.append(j) + + ret=yield Interface().bill(apikey) + self.assertEqual(ret['total'],{'price':0.8,'anz':2}) + self.assertEqual(ret['sipgate_basic'], + {'price':0.8,'anz':2, + 'info':{'i':{'price':0.4,'anz':1}, + 'a':{'price':0.4,'anz':1}, + } + }) + + @inlineCallbacks + def testSMS(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") + u.rights.append(Userright(o)) + session.add(u) + + jobid = yield Interface().sms('abcdef123456789','message',['0123325456'],['sipgate_basic']) + + with self.session() as session: + u = session.merge(u) + job = u.job(jobid) + exJob = job.extend + + self.assertEqual(exJob.message,SMS("message",None)) + self.assertEqual(taskPool.pool.q.qsize(),1) + + + @patch("iro.model.message.formatdate") + @inlineCallbacks + def testMail(self,mock_f): + mock_f.return_value="Wed, 21 Mar 2012 17:16:11 +0100" + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + o = Offer(name='loc',provider="localhost",route="",typ="mail") + u.rights.append(Userright(o)) + session.add(u) + + jobid = yield Interface().mail('abcdef123456789','sub', "hey body!", ['t@te.de'], "frm@t.de" ,['loc']) + with self.session() as session: + u = session.merge(u) + job = u.job(jobid) + exJob = job.extend + + self.assertEqual(exJob.message,Mail("sub",'hey body!','frm@t.de')) + self.assertEqual(taskPool.pool.q.qsize(),1) + + @patch("iro.model.message.formatdate") + @inlineCallbacks + def testMailFrmNone(self,mock_f): + mock_f.return_value="Wed, 21 Mar 2012 17:16:11 +0100" + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + o = Offer(name='loc',provider="localhost",route="",typ="mail") + u.rights.append(Userright(o)) + session.add(u) + + jobid = yield Interface().mail('abcdef123456789','sub', "hey body!", ['t@te.de'], None,['loc']) + with self.session() as session: + u = session.merge(u) + job = u.job(jobid) + exJob = job.extend + + self.assertEqual(exJob.message,Mail("sub",'hey body!',None)) + self.assertEqual(taskPool.pool.q.qsize(),1) + + + + @inlineCallbacks + def testFax(self): + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + o = Offer(name='b',provider="sipgate",route="b",typ="fax") + u.rights.append(Userright(o)) + session.add(u) + + jobid = yield Interface().fax('abcdef123456789','subject', 'blublbubblu',['0123325456'],['b']) + + with self.session() as session: + u = session.merge(u) + job = u.job(jobid) + exJob = job.extend + + self.assertEqual(exJob.message,Fax("subject","blublbubblu")) + self.assertEqual(taskPool.pool.q.qsize(),1) diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/tests/xmlrpc.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/tests/xmlrpc.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,237 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from multiprocessing import Process +import unittest + +from datetime import datetime + +import time + +from xmlrpclib import Server as xServer, ServerProxy, Fault + +from iro.model.schema import User, Offer, Userright, Job, Message + +from iro.main import runReactor + +import iro.error as IroError + +from ..test_helpers.dbtestcase import DBTestCase + + +class XMLRPCTest(DBTestCase): + """tests for the xmlrpc interface""" + def setUp(self): + DBTestCase.setUp(self) + self.s = Process(target=startReactor, args=(self.engine,)) + self.s.start() + #the new process needs time to get stated, so this process has to sleep + time.sleep(.2) + + def tearDown(self): + self.__debug().stop() + time.sleep(.2) + self.s.join() + DBTestCase.tearDown(self) + + def __debug(self): + return xServer('http://localhost:7080/debug') + + def __rpc2(self): + return ServerProxy('http://localhost:7080/RPC2') + + def testDebugHello(self): + '''simple test for the connection to xmlrpc server''' + ret=self.__debug().hello() + self.failUnlessEqual(ret,'hello') + + def testListMethods(self): + '''list of all offical Methods, that can be executed''' + ret=self.__rpc2().listMethods() + self.failUnlessEqual(ret, ['listMethods', 'status', 'stop', 'sms', 'fax', 'mail', 'routes', 'defaultRoute', 'bill', 'telnumber','email']) + + def testStatus(self): + ''' test the status function''' + with self.session() as session: + u = User(name='test',apikey='abcdef123456789') + session.add(User(name='test',apikey='abcdef123456789')) + self.failUnlessEqual(self.__rpc2().status('abcdef123456789'), {}) + + with self.session() as session: + u = session.merge(u) + j = Job(info='info', status="started") + j.user=u + session.add(j) + session.commit() + jid=j.id + status = {str(jid):{"status":"started"}} + self.failUnlessEqual(self.__rpc2().status('abcdef123456789',jid), status) + self.failUnlessEqual(self.__rpc2().status('abcdef123456789'), status) + self.failUnlessEqual(self.__rpc2().status('abcdef123456789', '', 'false'), status) + self.failUnlessEqual(self.__rpc2().status('abcdef123456789', '', 0), status) + + #JobNotFound + exc = self.assertRaises(Fault, self.__rpc2().status, 'abcdef123456789',jid+1) + unf = IroError.JobNotFound() + self.failUnlessEqual(exc.faultCode, unf.code) + self.failUnlessEqual(exc.faultString, unf.msg) + + #self.failUnlessEqual(self.__rpc2().status('abcdef123456789','abcde', True), ["",'abcde', True]) + #self.failUnlessEqual(self.__rpc2().status('abcdef123456789', '', 'true'), ["", '', True]) + #self.failUnlessEqual(self.__rpc2().status('abcdef123456789', '', 1), ["", '', True]) + + def testNoSuchUser(self): + '''a unknown user should raise a UserNotNound Exception + bewcause xmlrpc only has a Fault exception this Exception has to be deliverd through a xmlrpclib.Fault Exception''' + exc = self.assertRaises(Fault, self.__rpc2().status, 'abcdef123456789') + unf=IroError.UserNotFound() + self.failUnlessEqual(exc.faultCode, unf.code) + self.failUnlessEqual(exc.faultString, unf.msg) + + def testNoSuchMethod(self): + '''a unknown mothod should raise a Exception ''' + exc = self.assertRaises(Fault, self.__rpc2().nosuchmethod) + self.failUnlessEqual(exc.faultCode, 8001) + self.failUnlessEqual(exc.faultString, "procedure nosuchmethod not found") + + def testValidationFault(self): + '''a validate Exception should be translated to a xmlrpclib.Fault.''' + exc = self.assertRaises(Fault, self.__rpc2().status,'xxx') + self.failUnlessEqual(exc.faultCode, 700) + self.failUnlessEqual(exc.faultString, "Validation of 'apikey' failed.") + + def testRoutes(self): + '''test the route function''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + self.failUnlessEqual(self.__rpc2().routes('abcdef123456789','sms'),['sipgate_basic']) + + exc = self.assertRaises(Fault, self.__rpc2().routes,'abcdef123456789','fax') + self.failUnlessEqual(exc.faultCode, 700) + self.failUnlessEqual(exc.faultString, "Typ is not valid.") + + with self.session() as session: + o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms") + u = session.query(User).filter_by(name="test").first() + u.rights.append(Userright(o)) + o=Offer(name="faxde", provider="faxde", route="", typ="fax") + session.add(o) + session.commit() + self.failUnlessEqual(self.__rpc2().routes('abcdef123456789','sms'),['sipgate_basic','sipgate_plus']) + self.failUnlessEqual(self.__rpc2().routes('abcdef123456789','fax'),[]) + + with self.session() as session: + u = session.query(User).filter_by(name="test").first() + u.rights.append(Userright(o)) + + self.failUnlessEqual(self.__rpc2().routes('abcdef123456789','sms'),['sipgate_basic','sipgate_plus']) + self.failUnlessEqual(self.__rpc2().routes('abcdef123456789','fax'),['faxde']) + + def testDefaultRoutes(self): + '''test the defaultRoute function''' + with self.session() as session: + u=User(name='test',apikey='abcdef123456789') + o=Offer(name="sipgate_basic", provider="sipgate", route="basic", typ="sms") + u.rights.append(Userright(o,True)) + o=Offer(name="sipgate_plus", provider="sipgate", route="plus", typ="sms") + u.rights.append(Userright(o)) + session.add(u) + self.failUnlessEqual(self.__rpc2().defaultRoute('abcdef123456789','sms'),['sipgate_basic']) + + def testTelnumbers(self): + '''test the telefon validator''' + self.failUnlessEqual(self.__rpc2().telnumber(["0123/456(78)","+4912346785433","00123435456-658"]),True) + + invalid=['xa','+1','1-23',';:+0','0123'] + + exc = self.assertRaises(Fault, self.__rpc2().telnumber,['01234']+invalid) + self.failUnlessEqual(exc.faultCode, 701) + self.failUnlessEqual(exc.faultString, "No valid telnumber: '%s'" % invalid[0]) + + + def testVaildEmail(self): + '''test vaild email adresses (got from wikipedia)''' + validmails=["niceandsimple@example.com"] + self.failUnlessEqual(self.__rpc2().email(validmails),True) + + def testInvaildEmail(self): + '''test invaild email adresses (got from wikipedia)''' + invalid=["Abc.example.com",] + exc= self.assertRaises(Fault, self.__rpc2().email, invalid) + self.failUnlessEqual(exc.faultCode, 702) + self.failUnlessEqual(exc.faultString, "No valid email: '%s'" % invalid[0]) + + def testBill(self): + '''test bill function''' + apikey='abcdef123456789' + with self.session() as session: + u=User(name='test',apikey=apikey) + session.add(u) + + self.failUnlessEqual(self.__rpc2().bill(apikey),{'total':{'price':0.0,'anz':0}}) + + with self.session() as session: + u = session.merge(u) + o = Offer(name='sipgate_basic',provider="sipgate",route="basic",typ="sms") + u.rights.append(Userright(o)) + j = Job(info='i',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now() , price=0.4, offer=o)) + u.jobs.append(j) + + j = Job(info='a',status='sended') + j.messages.append(Message(recipient='0123456789', isBilled=False, date=datetime.now(), price=0.4, offer=o)) + u.jobs.append(j) + + ret=self.__rpc2().bill(apikey) + self.failUnlessEqual(ret['total'],{'price':0.8,'anz':2}) + self.failUnlessEqual(ret['sipgate_basic'], + {'price':0.8,'anz':2, + 'info':{'i':{'price':0.4,'anz':1}, + 'a':{'price':0.4,'anz':1}, + } + }) + + +def startReactor(engine): + """starts the Rector with a special debug Child, so that the reactor can be stopped remotly. """ + from twisted.internet import reactor + from twisted.web import xmlrpc, resource + + from iro.view.xmlrpc import appendResource + + class XMLRPCDebug(xmlrpc.XMLRPC): + def xmlrpc_stop(self): + reactor.callLater(0.1,reactor.stop) + return "" + + def xmlrpc_hello(self): + return "hello" + + root = resource.Resource() + root = appendResource(root) + root.putChild('debug', XMLRPCDebug()) + runReactor(reactor, engine, root) + +if __name__ == '__main__': + unittest.main() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/user.py --- a/iro/user.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,195 +0,0 @@ -# -*- coding: utf-8 -*- -#Copyright (C) 2009 Sandro Knauß - -#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 3 of the License, or 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, see . -import logging -logger=logging.getLogger("iro.user") -class NotSupportedFeature (Exception): - def __init__(self,name): - self.name=name - - def __str__(self): - return ("This is not a supported feature:", self.name) - -class NoID(Exception): - def __init__(self,i): - self.i=i - - def __str__(self): - return ("No Job with id:", self.i) - - -class User: - ''' - class for a xmlrpc user - ''' - def __init__(self, name, jobqueue): - self.jobqueue=jobqueue - self.jobs={} - self.name=name - self.features=["mail", "sms", "fax", ] - - def status(self,id=None,detailed=False): - u'''Gibt den aktuellen Status eines Auftrages zurück. - - Keywords: - id[hash]: Eine Auftragsnummer - detailed[boolean]: Details ausgeben - - Return: - jobs[list]: Eine Liste der Aufträge. - job.name[string]: Angebener Name - job.status[string]: Status des Auftrages - - - ''' - try: - jobs={} - if id==None: - jobs=self.jobs - else: - try: - jobs={id:self.jobs[id]} - except: - logger.error("No Job ID %s",id) - #raise NoID(id) - ret={} - if not jobs: - return {} - - for key in jobs: - job=jobs[key] - ret[key]={"name":job.getName(),"status":job.getStatus(detailed)} - - return ret - except: - logger.exception("Fehler in iro.user.status") - return {} - - def stop(self,id): - u'''Stoppt den angegeben Auftrag. - - Keywords: - id[hash]: Eine Auftragsnummer - - Return: - - ''' - try: - job=self.jobs[id] - job.stop() - except: - raise NoID(id) - job.stop() - - - def startSMS(self, message, recipients, provider="default"): - u'''Versendet eine SMS. - - Keywords: - message[string]: Nachricht - recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123) - provider[string]: Provider über den geschickt werden soll - - Return: - id[hash]: Die ID des Auftrages - - ''' - if not "sms" in self.features: - raise NotSupportedFeature("sms") - id = self.jobqueue.newSMS(message,recipients,provider,user=self.name) - self.jobs[id]=self.jobqueue[id] - return id - - - def startFAX(self, subject, fax, recipients, provider="default"): - u'''Versendet ein FAX. - - Keywords: - subject[string]: der Betreff - fax[string]: das pdf base64 kodiert - recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123) - provider[string]: Provider über den geschickt werden soll - - Return: - id[hash]: Die ID des Auftrages - - ''' - logger.debug("startFAX(%s,%s,%s,%s)"%(subject, fax, recipients, provider)) - if not "fax" in self.features: - raise NotSupportedFeature("fax") - - if type(fax) != list: - fax=[fax] - f=[i.data for i in fax] - - id = self.jobqueue.newFAX(subject, f,recipients,provider,user=self.name) - self.jobs[id]=self.jobqueue[id] - return id - - def startMail(self, subject, body, recipients, frm, provider="default"): - u'''Versendet eine Email. - - Keywords: - subject[string]: der Betreff - body[string]: der Email Body - recipients[list]: eine Liste von Emailadressen - frm[string]: Die Absender Emailadresse - provider[string]: Provider über den geschickt werden soll - - Return: - id[hash]: Die ID des Auftrages - - ''' - if not "mail" in self.features: - raise NotSupportedFeature("mail") - logger.debug("startMail(%s,%s,%s,%s,%s)"%(subject, body, recipients, frm, provider)) - id = self.jobqueue.newMail(subject, body, recipients, frm, provider,user=self.name) - self.jobs[id]=self.jobqueue[id] - return id - - def getProvider(self, typ): - u'''Gibt eine Liste aller verfügbaren Provider zurück. - - Keywords: - typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll - Einer der Liste ["sms","fax","mail"] - - Return: - providerlist[list]: Eine Liste aller möglichen Provider - - ''' - if not typ in self.features: - raise NotSupportedFeature(typ) - - return self.jobqueue.providerlist.getProviderlist(typ) - - def getDefaultProvider(self, typ): - u'''Gibt den Standardprovider zurück. - - Keywords: - typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll - Einer der Liste ["sms","fax","mail"] - - Return: - provider[string]: Der Standardprovider für den angeben Typ - - - ''' - if not typ in self.features: - raise NotSupportedFeature(typ) - - return self.jobqueue.providerlist.getDefault(typ)["name"] - -class Admin(User): - def __init__(self, name, jobqueue): - User.__init__(self, name, jobqueue) - self.jobs=jobqueue.jobs diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/validate.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/validate.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,258 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.internet import defer + +import re +from decorator import decorator +try: + from inspect import getcallargs +except ImportError: + from .inspect_getcallargs import getcallargs +import types + +from .error import ValidateException, InvalidTel, InvalidMail +from .telnumber import Telnumber + +def vBool(value, field): + '''Validate function for boolean values + + :return: **value** + :raises: :exc:`iro.error.ValidateException` + ''' + t=[True, 1, "true", "True", "TRUE"] + f=[False, 0, "false", "False", "FALSE"] + if value in t: + return True + elif value in f: + return False + else: + raise ValidateException(field=field, msg='%s is not boolean' % field) + +def vNumber(value,field, nval, minv=None, maxv=None, none_allowed=False): + """validate function for integer values. + + :param integer minv: minimum value + :param integer maxv: maximum value + :param func nval: function that give back a number + :param boolean none_allowed: is None or empty string allowed + :return: **value** + :raises: :exc:`iro.error.ValidateException` + """ + if none_allowed and value in [None,'']: + return None + + try: + ret = nval(value) + except ValueError: + raise ValidateException(field=field) + except TypeError: + raise ValidateException(field=field) + + if minv is not None and ret < minv: + raise ValidateException(field=field) + + if maxv is not None and ret > maxv: + raise ValidateException(field=field) + + return ret + +def vInteger(value, field, minv=None, maxv=None, none_allowed=False): + """validate function for integer values. + + :param integer minv: minimum value + :param integer maxv: maximum value + :param boolean none_allowed: is None or empty string allowed + :return: **value** + :raises: :exc:`iro.error.ValidateException` + + see also :func:vNumber + """ + return vNumber(value, field, int, minv, maxv, none_allowed) + +def vFloat(value, field, minv=None, maxv=None, none_allowed=False): + """validate function for float values. + + :param integer minv: minimum value + :param integer maxv: maximum value + :param boolean none_allowed: is None or empty string allowed + :return: **value** + :raises: :exc:`iro.error.ValidateException` + + see also :func:vNumber + """ + return vNumber(value, field, float, minv, maxv, none_allowed) + + + +def vHash(value,field,minlength=None,maxlength=None): + '''Validate function for hash values + + :param integer minlength: minimum length of value string + :param integer maxlength: maximum length of value string + :return: **value** + :raises: :exc:`iro.error.ValidateException` + ''' + if not re.match(r'^[a-f0-9]*$', value.lower()): + raise ValidateException(field=field) + if minlength and len(value)maxlength: + raise ValidateException(field=field) + return value.lower() + +def vTel(value,field): + '''Validator for telefon numbers + :return: **value** + :raises: :exc:`iro.error.InvalidTel` + ''' + + ret = [] + for v in value: + try: + tel=Telnumber(v) + if tel not in ret: + ret.append(tel) + except InvalidTel, e: + e.field=field + raise e + return ret + +def vEmail(value, field, allowString=True, allowList=True): + '''validator for emailadresses (see wikipeda for strange mailadresses and RFC3696) + + valid: + + - "very.(),:;<>[]\\".VERY.\\"very@\\\ \\"very\\".unusual"@strange.example.com + - ""@example.org + - "very.unusual.@.unusual.com"@example.com' + + not valid: + + - Abc.@example.com + - Abc..123@example.com + - thisis."notallowed@example.com + - this\\ still\\"not\\allowed@example.com + + :param boolean allowString: value can be a string -> a string is returned + :param boolean allowList: value is a a list -> a list is returned + :return: **value** + :raises: :exc:`iro.error.ValidateException`, :exc:`iro.error.InvalidMail` + ''' + ret = [] + str_=False + if type(value) is types.StringType: + if not allowString: + raise ValidateException('%s must be a list of email addresses.'%field) + str_=True + value=[value] + elif not allowList: + raise ValidateException('%s must be a email address - No list of email addresses.'%field) + for v in value: + parts= re.match(r'^(.*)@(.+?)$',v) + if not parts: + raise InvalidMail(v,field) + local=parts.group(1) + domain=parts.group(2) + + if not re.match(r'^(\[[0-9\.]{7,16}\]|\[[0-9a-f:]{3,}\]|([a-z0-9+\-%_]+\.)+[a-z]{2,6})$',domain.lower()): + raise InvalidMail(v,field) + + if local == "": + ret.append(v) + continue + + if local.startswith(".") or local.endswith("."): + raise InvalidMail(v,field) + unquote = True + parts = local.split('"') + c=0 + i=0 + for part in parts: + if unquote and part != "": #unquoted is not allowd so much + if not re.match(r'^[^\\,\[\];\(\)@<>: ]+$',part) or ".." in part: + raise InvalidMail(v,field) + if i == 0: + if unquote and part != "" and len(parts) > 1 and part[-1] != '.': #quoted parts must be seperated by a dot + raise InvalidMail(v,field) + unquote = not unquote + c+=1 + elif part == '' or part[-1] != "\\": + if unquote and part != "": #quoted parts must be seperated by a dot + if part[0] != ".": + raise InvalidMail(v,field) + if i < len(parts)-1 and part[-1] != '.': + raise InvalidMail(v,field) + unquote = not unquote + c += 1 + i += 1 + if c%2 == 0 and c > 1: #no single quote allowed + raise InvalidMail(v,field) + if v not in ret: + ret.append(v) + if str_: + ret=ret[0] + return ret + +def validate(kwd,func, need=True,*args,**kargs): + '''validate decorator. + + :param string kwd: keyword to validate + :param func func: validate function + :param boolean need: ``False`` -- ``None`` is a valid value for kwd + :params args: arguments for validate function + :params kargs: keyword arguments for validate function + + .. note:: this decorator can handle function that returns a defer object. + + use it like this:: + + @validate(kwd=userhash, func=vuserhash) + f(userhash) + + that will validate ``userhash`` with the function **vuserhash**. + Every validate function should raise an Exception, if the the value is not valid. + All **args** and **kargs** are used to call the validate function. + if **need** is True, the kwd can't be `None`. + ''' + @decorator + def v(f,*a,**k): + kp=getcallargs(f,*a,**k) + def dfunc(*x,**y): + return None + try: + if kp[kwd] is not None: + dfunc=func + elif need: + raise ValidateException(field=kwd,msg="%s is nessasary"%kwd) + except KeyError: + if need: + raise ValidateException(field=kwd,msg="%s is nessasary"%kwd) + kp[kwd] = None + + def _gotResult(value): + kp[kwd] = value + e = defer.maybeDeferred(f,**kp) + return e + d = defer.maybeDeferred(dfunc, kp[kwd],kwd,*args,**kargs) + return d.addCallback(_gotResult) + return v + diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/view/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/view/__init__.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,20 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/view/jsonresource.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/view/jsonresource.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,140 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from twisted.web import resource, server, http +from twisted.python import log, failure +from twisted.internet import defer + +from ..controller.viewinterface import Interface +from ..error import ValidateException + +try: + import json +except ImportError: + import simplejson as json + + +class TwistedInterface(Interface): + """Class that addes needed function for JSON""" + def __init__(self): + Interface.__init__(self) + + def listMethods(self): + """Since we override lookupProcedure, its suggested to override + listProcedures too. + """ + return self.listProcedures() + + + def listProcedures(self): + """returns a list of all functions that are allowed to call via XML-RPC.""" + return ['listMethods','status','sms','fax','mail','routes','defaultRoute','bill','telnumber','email'] + +class MethodFactory(resource.Resource): + def __init__(self,method,twistedInterface): + self.method = method + self.twistedInterface = twistedInterface + + def render(self,request): + try: + args = [] + if request.getHeader('Content-Type') == 'application/x-www-form-urlencoded': + args = {} + for a in request.args: + value = request.args[a] + if a != "recipients" and len(value) == 1: + value = value[0] + args[a] = value + elif request.getHeader('Content-Type') == 'application/json': + content = request.content.read() + if content: + args = json.loads(content) + if args is None: + args = [] + else: + request.setResponseCode(http.NOT_ACCEPTABLE) + return "Only application/x-www-form-urlencoded or application/json ist allowed for Content-Type" + if isinstance(args,list): + d = defer.maybeDeferred(getattr(self.twistedInterface,self.method),*args) + else: + d = defer.maybeDeferred(getattr(self.twistedInterface,self.method), **args) + d.addCallback(self._cbRender, request) + d.addErrback(self._ebRender, request) + d.addBoth(lambda _: request.finish()) + return server.NOT_DONE_YET + except Exception: + log.err(failure.Failure()) + request.setResponseCode(http.INTERNAL_SERVER_ERROR) + err= { + "code" : 999, + "msg" : "Unknown error.", + } + request.setHeader('Content-Type', 'application/json') + return json.dumps({"status":False, "error":err}) + + + def _cbRender(self,result,request): + request.setHeader('Content-Type', 'application/json') + request.write(json.dumps({"status":True, "result":result})) + + def _ebRender(self, failure, request): + if isinstance(failure.value, ValidateException): + request.setResponseCode(http.BAD_REQUEST) + else: + request.setResponseCode(http.INTERNAL_SERVER_ERROR) + + err= { + "code" : 999, + "msg" : "Unknown error.", + } + + try: + err["code"]=failure.value.code + err["msg"]=failure.value.msg + except Exception: + log.err(failure) + pass + request.setHeader('Content-Type', 'application/json') + request.write(json.dumps({"status":False, "error":err})) + +class JSONFactory(resource.Resource): + """JSON factory""" + def __init__(self): + resource.Resource.__init__(self) + self.twistedInterface = TwistedInterface() + for method in self.twistedInterface.listProcedures(): + self.putChild(method, MethodFactory(method, self.twistedInterface)) + + +def appendResource(root): + """adding JSON to root.""" + root.putChild('json', JSONFactory()) + +if __name__ == '__main__': + from twisted.web import resource + from twisted.internet import reactor + + root = resource.Resource() + root = appendResource(root) + reactor.listenTCP(7080, server.Site(root)) + reactor.run() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/view/jsonrpc.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/view/jsonrpc.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from txjsonrpc.web import jsonrpc +from txjsonrpc import jsonrpclib +from ..controller.viewinterface import Interface + +from ..error import InterfaceException, ValidateException + +class TwistedInterface(Interface): + """Class that addes needed function for XML-RPC/SOAP""" + def __init__(self): + Interface.__init__(self) + + def listMethods(self): + """Since we override lookupProcedure, its suggested to override + listProcedures too. + """ + return self.listProcedures() + + + def listProcedures(self): + """returns a list of all functions that are allowed to call via XML-RPC.""" + return ['listMethods','status','sms','fax','mail','routes','defaultRoute','bill','telnumber','email'] + + +class JSONRPCInterface(TwistedInterface,jsonrpc.JSONRPC): + """JSON-RPC interface""" + NOT_FOUND = jsonrpclib.METHOD_NOT_FOUND + def __init__(self): + jsonrpc.JSONRPC.__init__(self) + TwistedInterface.__init__(self) + + def _getFunction(self, procedurePath): + if procedurePath not in self.listProcedures(): + raise jsonrpclib.NoSuchFunction(self.NOT_FOUND, + "procedure %s not found" % procedurePath) + try: + return getattr(self,procedurePath) + except KeyError: + raise jsonrpclib.NoSuchFunction(self.NOT_FOUND, + "procedure %s not found" % procedurePath) + + def _ebRender(self, failure, id): + if isinstance(failure.value, InterfaceException): + return jsonrpclib.Fault(failure.value.code, failure.value.msg) + if isinstance(failure.value, ValidateException): + return jsonrpclib.Fault(failure.value.code, failure.value.msg) + return jsonrpc.JSONRPC._ebRender(self, failure, id) + + +def appendResource(root): + """adding JSONRPC to root.""" + root.putChild('jsonrpc', JSONRPCInterface()) + +if __name__ == '__main__': + from twisted.web import resource, server + from twisted.internet import reactor + + root = resource.Resource() + root = appendResource(root) + reactor.listenTCP(7080, server.Site(root)) + reactor.run() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/view/xmlrpc.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/view/xmlrpc.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,97 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- +from twisted.web import soap, xmlrpc + +from ..controller.viewinterface import Interface + +from ..error import InterfaceException, ValidateException + +class TwistedInterface(Interface): + """Class that addes needed function for XML-RPC/SOAP""" + def __init__(self): + Interface.__init__(self) + + def listMethods(self): + """Since we override lookupProcedure, its suggested to override + listProcedures too. + """ + return self.listProcedures() + + + def listProcedures(self): + """returns a list of all functions that are allowed to call via XML-RPC.""" + return ['listMethods','status','sms','fax','mail','routes','defaultRoute','bill','telnumber','email'] + + +class XMLRPCInterface(TwistedInterface,xmlrpc.XMLRPC): + """XML-RPC interface""" + def __init__(self): + xmlrpc.XMLRPC.__init__(self) + TwistedInterface.__init__(self) + + def lookupProcedure(self, procedurePath): + if procedurePath not in self.listProcedures(): + raise xmlrpc.NoSuchFunction(self.NOT_FOUND, + "procedure %s not found" % procedurePath) + try: + return getattr(self,procedurePath) + except KeyError: + raise xmlrpc.NoSuchFunction(self.NOT_FOUND, + "procedure %s not found" % procedurePath) + + def _ebRender(self, failure): + if isinstance(failure.value, InterfaceException): + return xmlrpc.Fault(failure.value.code, failure.value.msg) + if isinstance(failure.value, ValidateException): + return xmlrpc.Fault(failure.value.code, failure.value.msg) + return xmlrpc.XMLRPC._ebRender(self, failure) + + +class SOAPInterface(TwistedInterface,soap.SOAPPublisher): + """SOAP interface""" + def __init__(self): + soap.SOAPPublisher.__init__(self) + TwistedInterface.__init__(self) + + def lookupFunction(self, functionName): + if functionName in self.listProcedures(): + function = getattr(self, functionName, None) + if function: + return function, getattr(function, "useKeywords", False) + return None + else: + return None + +def appendResource(root): + """adding XML-RPC and SOAP to root.""" + root.putChild('RPC2', XMLRPCInterface()) + root.putChild('SOAP', SOAPInterface()) + +if __name__ == '__main__': + from twisted.web import resource, server + from twisted.internet import reactor + + root = resource.Resource() + root = appendResource(root) + reactor.listenTCP(7080, server.Site(root)) + reactor.run() diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/view/xmlrpc_old.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/view/xmlrpc_old.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,206 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- +""".. deprecated:: 1.0a""" + +import logging +logger=logging.getLogger("iro.user") +class NotSupportedFeature (Exception): + def __init__(self,name): + self.name=name + + def __str__(self): + return ("This is not a supported feature:", self.name) + +class NoID(Exception): + def __init__(self,i): + self.i=i + + def __str__(self): + return ("No Job with id:", self.i) + + +class User: + '''class for a xmlrpc user + ''' + def __init__(self, name, jobqueue): + self.jobqueue=jobqueue + self.jobs={} + self.name=name + self.features=["mail", "sms", "fax", ] + + def status(self,id=None,detailed=False): + '''Gibt den aktuellen Status eines Auftrages zurück. + + Keywords: + id[hash]: Eine Auftragsnummer + detailed[boolean]: Details ausgeben + + Return: + jobs[list]: Eine Liste der Aufträge. + job.name[string]: Angebener Name + job.status[string]: Status des Auftrages + + + ''' + try: + jobs={} + if id==None: + jobs=self.jobs + else: + try: + jobs={id:self.jobs[id]} + except: + logger.error("No Job ID %s",id) + #raise NoID(id) + ret={} + if not jobs: + return {} + + for key in jobs: + job=jobs[key] + ret[key]={"name":job.getName(),"status":job.getStatus(detailed)} + + return ret + except: + logger.exception("Fehler in iro.user.status") + return {} + + def stop(self,id): + '''Stoppt den angegeben Auftrag. + + Keywords: + id[hash]: Eine Auftragsnummer + + Return: + + ''' + try: + job=self.jobs[id] + job.stop() + except: + raise NoID(id) + job.stop() + + + def startSMS(self, message, recipients, provider="default"): + '''Versendet eine SMS. + + Keywords: + message[string]: Nachricht + recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123) + provider[string]: Provider über den geschickt werden soll + + Return: + id[hash]: Die ID des Auftrages + + ''' + if not "sms" in self.features: + raise NotSupportedFeature("sms") + id = self.jobqueue.newSMS(message,recipients,provider,user=self.name) + self.jobs[id]=self.jobqueue[id] + return id + + + def startFAX(self, subject, fax, recipients, provider="default"): + '''Versendet ein FAX. + + Keywords: + subject[string]: der Betreff + fax[string]: das pdf base64 kodiert + recipients[list]: eine Liste von Emfänger-Nummern (gemäß ITU-T E.123) + provider[string]: Provider über den geschickt werden soll + + Return: + id[hash]: Die ID des Auftrages + + ''' + logger.debug("startFAX(%s,%s,%s,%s)"%(subject, fax, recipients, provider)) + if not "fax" in self.features: + raise NotSupportedFeature("fax") + + if type(fax) != list: + fax=[fax] + f=[i.data for i in fax] + + id = self.jobqueue.newFAX(subject, f,recipients,provider,user=self.name) + self.jobs[id]=self.jobqueue[id] + return id + + def startMail(self, subject, body, recipients, frm, provider="default"): + '''Versendet eine Email. + + Keywords: + subject[string]: der Betreff + body[string]: der Email Body + recipients[list]: eine Liste von Emailadressen + frm[string]: Die Absender Emailadresse + provider[string]: Provider über den geschickt werden soll + + Return: + id[hash]: Die ID des Auftrages + + ''' + if not "mail" in self.features: + raise NotSupportedFeature("mail") + logger.debug("startMail(%s,%s,%s,%s,%s)"%(subject, body, recipients, frm, provider)) + id = self.jobqueue.newMail(subject, body, recipients, frm, provider,user=self.name) + self.jobs[id]=self.jobqueue[id] + return id + + def getProvider(self, typ): + '''Gibt eine Liste aller verfügbaren Provider zurück. + + Keywords: + typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll + Einer der Liste ["sms","fax","mail"] + + Return: + providerlist[list]: Eine Liste aller möglichen Provider + + ''' + if not typ in self.features: + raise NotSupportedFeature(typ) + + return self.jobqueue.providerlist.getProviderlist(typ) + + def getDefaultProvider(self, typ): + '''Gibt den Standardprovider zurück. + + Keywords: + typ[string]: Der Typ zu dem die Providerloste ausgeben werden soll + Einer der Liste ["sms","fax","mail"] + + Return: + provider[string]: Der Standardprovider für den angeben Typ + + + ''' + if not typ in self.features: + raise NotSupportedFeature(typ) + + return self.jobqueue.providerlist.getDefault(typ)["name"] + +class Admin(User): + def __init__(self, name, jobqueue): + User.__init__(self, name, jobqueue) + self.jobs=jobqueue.jobs diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/worker.py --- a/iro/worker.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# Worker code - -from multiprocessing import Process -import logging -logger = logging.getLogger("iro.worker") - -class Worker(Process): - def __init__(self,queue): - Process.__init__(self) - self.queue=queue - - def run(self): - logger.info('Workerprocess läuft nun...') - indifier=0 - while 1: - job=self.queue.get() - if job is None: - break # reached end of queue - indifier+=1 - logger.info('ein neuer Job(%d)' %(indifier)) - try: - job.start(indifier) - logger.info('Job(%d) fertig ;)'%(indifier)) - except: - job.setStatus("error") - logger.exception('Job(%d) fehlgeschlagen :('%(indifier)) - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/xmlrpc/AuthentificateXMLRPCServer.py --- a/iro/xmlrpc/AuthentificateXMLRPCServer.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -# Server code -import SimpleXMLRPCServer -import string,base64 - -class AuthentificateXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): - def do_POST(self): - try: - header = self.headers['Authorization'] - type, user_passwd = header.split() - username, password = string.split(base64.decodestring(user_passwd), ':') - if self.testUser(username,password): - SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.do_POST(self) - else: - self.report_error(401) - except: - self.report_error(401) - - def report_error (self,code): - ''' - Send back an errorcode - ''' - #it is important to read out the complete sended request , - # but throw the data away, because an error should be send back - try: - max_chunk_size = 10*1024*1024 - size_remaining = int(self.headers["content-length"]) - while size_remaining: - chunk_size = min(size_remaining, max_chunk_size) - size_remaining -= len(self.rfile.read(chunk_size)) - except: - pass - - #now just send the error back - special_errors={401:self.report_401, - 404:self.report_404} - if special_errors.has_key(code): - special_errors[code]() - else: - self.send_response(code) - self.end_headers() - self.connection.shutdown(1) - - def report_401(self): - self.send_response(401) - self.send_header("WWW-Authenticate", 'Basic realm="%s"'% self.server.relam) - response = 'Unauthorised' - self.send_header("Content-type", "text/plain") - self.send_header("Content-length", str(len(response))) - self.end_headers() - self.wfile.write(response) - # shut down the connection - self.wfile.flush() - self.connection.shutdown(1) - - def testUser(self,username,password): - """ - Function for testing authentification - """ - if username=="test" and password=="test": - return True - - return False - - - -def test(): - server = SimpleXMLRPCServer.SimpleXMLRPCServer(("localhost", 8000),AuthentificateXMLRPCRequestHandler) - server.relam="xmlrpc" - server.register_introspection_functions() - server.register_function(lambda x: x*x, 'square') - server.serve_forever() - -if __name__ == '__main__': - test() - - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/xmlrpc/SecureAuthentificateXMLRPCServer.py --- a/iro/xmlrpc/SecureAuthentificateXMLRPCServer.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -# Server code -import AuthentificateXMLRPCServer -import SecureXMLRPCServer - -class SecureAuthentificateXMLRPCRequestHandler(SecureXMLRPCServer.SecureXMLRPCRequestHandler,AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler): - def do_POST(self): - AuthentificateXMLRPCServer.AuthentificateXMLRPCRequestHandler.do_POST(self) - - -def test(): - server = SecureXMLRPCServer.SecureXMLRPCServer(("localhost", 8000),requestHandler=SecureAuthentificateXMLRPCRequestHandler,certificate="./certs/test.cert.pem",privatekey="./certs/test.key.pem") - server.relam="xmlrpc" - server.register_introspection_functions() - server.register_function(lambda x: x*x, 'square') - server.serve_forever() - -if __name__ == '__main__': - test() - - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/xmlrpc/SecureUserdbXMLRPCServer.py --- a/iro/xmlrpc/SecureUserdbXMLRPCServer.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -from SecureAuthentificateXMLRPCServer import SecureAuthentificateXMLRPCRequestHandler -from SecureXMLRPCServer import SecureXMLRPCServer -import os, hashlib - -class UserDB: - ''' - class for managing all xmlrpc users - - each user is indyfied via a hash value, which is created out of the username + password - ''' - def __init__(self,userClass, userlist,jobqueue): - self.salt=os.urandom(20) - self.jobqueue=jobqueue - self.userClass=userClass - self.userlist={} - for user in userlist: - self.createUser(user) - - def createHash(self,user): - """ - returns a hash out of username and the password and self.salt - user is a directory with two keys: username and password - """ - m=hashlib.sha512() - m.update(user["name"]) - m.update(self.salt) - m.update(user["password"]) - return m.hexdigest() - - def createUser(self,user): - self.userlist[self.createHash(user)]=self.userClass(user["name"],self.jobqueue) - - def __getitem__(self,key): - return self.userlist[key] - - -class SecureAuthentificateXMLRPCRequestHandler2(SecureAuthentificateXMLRPCRequestHandler): - def testUser(self,username,password): - """ - Function for testing authentification - """ - return self.server.activateUser(username,password) - -class SecureUserDBXMLRPCServer(SecureXMLRPCServer): - def __init__(self, addr, userdb, - requestHandler=SecureAuthentificateXMLRPCRequestHandler2, - certificate="server.cert", privatekey="server.pem", - logRequests=1): - SecureXMLRPCServer.__init__(self, addr, requestHandler, certificate, privatekey, logRequests) - self.relam="xmlrpc" - self.userdb=userdb - - def activateUser(self,username,password): - """ - Function is executed, if someone ant to login - -searches for a regular user in the userdb and then put all methods of the user as XMLRPC - returns weather a user was found or not - """ - try: - user = self.userdb[self.userdb.createHash({"name":username,"password":password})] - self.register_instance(user) - return True - except KeyError: - return False diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/xmlrpc/SecureXMLRPCServer.py --- a/iro/xmlrpc/SecureXMLRPCServer.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -""" -SecureXMLRPCServer module using pyOpenSSL 0.5 -Written 0907.2002 -by Michal Wallace -http://www.sabren.net/ - -This acts exactly like SimpleXMLRPCServer -from the standard python library, but -uses secure connections. The technique -and classes should work for any SocketServer -style server. However, the code has not -been extensively tested. - -This code is in the public domain. -It is provided AS-IS WITH NO WARRANTY WHATSOEVER. -""" -import SocketServer -import os, socket -import SimpleXMLRPCServer -from OpenSSL import SSL - -class SSLWrapper: - """ - This whole class exists just to filter out a parameter - passed in to the shutdown() method in SimpleXMLRPC.doPOST() - """ - def __init__(self, conn): - """ - Connection is not yet a new-style class, - so I'm making a proxy instead of subclassing. - """ - self.__dict__["conn"] = conn - def __getattr__(self,name): - return getattr(self.__dict__["conn"], name) - def __setattr__(self,name, value): - setattr(self.__dict__["conn"], name, value) - def shutdown(self, how=1): - """ - SimpleXMLRpcServer.doPOST calls shutdown(1), - and Connection.shutdown() doesn't take - an argument. So we just discard the argument. - """ - self.__dict__["conn"].shutdown() - def accept(self): - """ - This is the other part of the shutdown() workaround. - Since servers create new sockets, we have to infect - them with our magic. :) - """ - c, a = self.__dict__["conn"].accept() - return (SSLWrapper(c), a) - - - -class SecureTCPServer(SocketServer.TCPServer): - """ - Just like TCPServer, but use a socket. - This really ought to let you specify the key and certificate files. - """ - def __init__(self, server_address, RequestHandlerClass,certificate,privatekey): - SocketServer.BaseServer.__init__(self, server_address, RequestHandlerClass) - - ## Same as normal, but make it secure: - ctx = SSL.Context(SSL.SSLv23_METHOD) - ctx.set_options(SSL.OP_NO_SSLv2) - - ctx.use_privatekey_file (privatekey) - ctx.use_certificate_file(certificate) - - self.socket = SSLWrapper(SSL.Connection(ctx, socket.socket(self.address_family, - self.socket_type))) - self.server_bind() - self.server_activate() - - -class SecureXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): - def setup(self): - """ - We need to use socket._fileobject Because SSL.Connection - doesn't have a 'dup'. Not exactly sure WHY this is, but - this is backed up by comments in socket.py and SSL/connection.c - """ - self.connection = self.request # for doPOST - self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) - self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) - - -class SecureXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer, SecureTCPServer): - def __init__(self, addr, - requestHandler=SecureXMLRPCRequestHandler, - certificate="server.cert",privatekey="server.pem", - logRequests=1): - """ - This is the exact same code as SimpleXMLRPCServer.__init__ - except it calls SecureTCPServer.__init__ instead of plain - old TCPServer.__init__ - """ - self.funcs = {} - self.logRequests = logRequests - self.instance = None - SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self,False,None) - SecureTCPServer.__init__(self, addr, requestHandler,certificate=certificate,privatekey=privatekey) - - -def test(): - server = SecureXMLRPCServer.SecureXMLRPCServer(("localhost", 8000),requestHandler=SecureXMLRPCRequestHandler,certificate="./certs/test.cert.pem",privatekey="./certs/test.key.pem") - server.register_introspection_functions() - server.register_function(lambda x: x*x, 'square') - server.serve_forever() - -if __name__ == '__main__': - test() - diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/xmlrpc/__init__.py --- a/iro/xmlrpc/__init__.py Wed Dec 21 22:07:48 2011 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -from AuthentificateXMLRPCServer import AuthentificateXMLRPCRequestHandler -from SecureXMLRPCServer import SecureXMLRPCRequestHandler, SecureXMLRPCServer -from SecureAuthentificateXMLRPCServer import SecureAuthentificateXMLRPCRequestHandler -from SecureUserdbXMLRPCServer import SecureUserDBXMLRPCServer, UserDB diff -r eb04ac3a8327 -r 3f4bdea2abbf setup.py --- a/setup.py Wed Dec 21 22:07:48 2011 +0100 +++ b/setup.py Thu Sep 27 17:15:46 2012 +0200 @@ -1,2 +1,59 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + from setuptools import setup -setup(name="iro",version='0.1',install_requires=["xmlrpclib","ConfigParser","base64","multiprocessing"]) +from iro import __version__ + +def refresh_plugin_cache(): + from twisted.plugin import IPlugin, getPlugins + list(getPlugins(IPlugin)) + +setup(name='iro', + version=__version__, + packages=['iro','iro.controller','iro.view','iro.model','iro.offer','iro.tests', 'iro.test_helpers','twisted.plugins'], + setup_requires = ["ngmodules>=0.2","mock"], + install_requires=['twisted>=11.1.0',"ConfigParser","sqlalchemy","MySQL-python","SOAPpy",'decorator', 'txJSON-RPC'], + #test_suite="tests", #ToDo switch to trial + description='Non Blocking Interface for sending a bunsh of SMSes, FAXes and Mails.', + author='Sandro Knauß', + author_email='knauss@netzguerilla.net', + url='https://netzguerilla.net/iro', + scripts=["bin/iro-install"], + package_data={ + 'twisted': ['plugins/iro_plugin.py'], + }, + license = "MIT", + classifiers=[ + "Development Status :: 3 - Alpha", + "Environment :: No Input/Output (Daemon)", + "Framework :: Twisted", + "Topic :: Communications :: Fax", + "Topic :: Communications :: Email", + "Topic :: Communications :: Sms", + "Programming Language :: Python", + "License :: OSI Approved :: MIT License", + ] + ) + +refresh_plugin_cache() diff -r eb04ac3a8327 -r 3f4bdea2abbf sqlalchemy_schemadisplay3.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sqlalchemy_schemadisplay3.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,194 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# updated SQLA schema display to work with pydot 1.0.2 +# download from: http://www.sqlalchemy.org/trac/wiki/UsageRecipes/SchemaDisplay + + +from sqlalchemy.orm.properties import PropertyLoader +import pydot +import types + +__all__ = ['create_uml_graph', 'create_schema_graph', 'show_uml_graph', 'show_schema_graph'] + +def _mk_label(mapper, show_operations, show_attributes, show_datatypes, bordersize): + html = '<' % (bordersize, mapper.class_.__name__) + def format_col(col): + colstr = '+%s' % (col.name) + if show_datatypes: + colstr += ' : %s' % (col.type.__class__.__name__) + return colstr + + if show_attributes: + html += '' % '
'.join(format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key)) + else: + [format_col(col) for col in sorted(mapper.columns, key=lambda col:not col.primary_key)] + if show_operations: + html += '' % '
'.join( + '%s(%s)' % (name,", ".join(default is _mk_label and ("%s") % arg or ("%s=%s" % (arg,repr(default))) for default,arg in + zip((func.func_defaults and len(func.func_code.co_varnames)-1-(len(func.func_defaults) or 0) or func.func_code.co_argcount-1)*[_mk_label]+list(func.func_defaults or []), func.func_code.co_varnames[1:]) + )) + for name,func in mapper.class_.__dict__.items() if isinstance(func, types.FunctionType) and func.__module__ == mapper.class_.__module__ + ) + html+= '
%s
%s
%s
>' + return html + + +def create_uml_graph(mappers, show_operations=True, show_attributes=True, show_multiplicity_one=False, show_datatypes=True, linewidth=1.0, font="Bitstream-Vera Sans"): + graph = pydot.Dot(prog='neato',mode="major",overlap="0", sep="0.01",dim="3", pack="True", ratio=".75") + relations = set() + for mapper in mappers: + graph.add_node(pydot.Node(mapper.class_.__name__, + shape="plaintext", label=_mk_label(mapper, show_operations, show_attributes, show_datatypes, linewidth), + fontname=font, fontsize="8.0", + )) + if mapper.inherits: + graph.add_edge(pydot.Edge(mapper.inherits.class_.__name__,mapper.class_.__name__, + arrowhead='none',arrowtail='empty', style="setlinewidth(%s)" % linewidth, arrowsize=str(linewidth))) + for loader in mapper.iterate_properties: + if isinstance(loader, PropertyLoader) and loader.mapper in mappers: + if hasattr(loader, 'reverse_property'): + relations.add(frozenset([loader, loader.reverse_property])) + else: + relations.add(frozenset([loader])) + + for relation in relations: + #if len(loaders) > 2: + # raise Exception("Warning: too many loaders for join %s" % join) + args = {} + def multiplicity_indicator(prop): + if prop.uselist: + return ' *' + if any(col.nullable for col in prop.local_side): + return ' 0..1' + if show_multiplicity_one: + return ' 1' + return '' + + if len(relation) == 2: + src, dest = relation + from_name = src.parent.class_.__name__ + to_name = dest.parent.class_.__name__ + + def calc_label(src,dest): + return '+' + src.key + multiplicity_indicator(src) + args['headlabel'] = calc_label(src,dest) + + args['taillabel'] = calc_label(dest,src) + args['arrowtail'] = 'none' + args['arrowhead'] = 'none' + args['constraint'] = False + else: + prop, = relation + from_name = prop.parent.class_.__name__ + to_name = prop.mapper.class_.__name__ + args['headlabel'] = '+%s%s' % (prop.key, multiplicity_indicator(prop)) + args['arrowtail'] = 'none' + args['arrowhead'] = 'vee' + + graph.add_edge(pydot.Edge(from_name,to_name, + fontname=font, fontsize="7.0", style="setlinewidth(%s)"%linewidth, arrowsize=str(linewidth), + **args) + ) + + return graph + +#from sqlalchemy import Table, text + +def _render_table_html(table, metadata, show_indexes, show_datatypes): + def format_col_type(col): + try: + return col.type.get_col_spec() + except NotImplementedError: + return str(col.type) + except AttributeError: + return str(col.type) + def format_col_str(col): + if show_datatypes: + return "- %s : %s" % (col.name, format_col_type(col)) + else: + return "- %s" % col.name + html = '<' % table.name + + html += ''.join('' % (col.name, format_col_str(col)) for col in table.columns) + html += '
%s
%s
>' + return html + +def create_schema_graph(tables=None, metadata=None, show_indexes=True, show_datatypes=True, font="Bitstream-Vera Sans", + concentrate=True, relation_options={}, rankdir='TB'): + relation_kwargs = { + 'fontsize':"7.0" + } + relation_kwargs.update(relation_options) + + if not metadata and len(tables): + metadata = tables[0].metadata + elif not tables and metadata: + if not len(metadata.tables): + metadata.reflect() + tables = metadata.tables.values() + else: + raise Exception("You need to specify at least tables or metadata") + + graph = pydot.Dot(prog="dot",mode="ipsep",overlap="ipsep",sep="0.01",concentrate=str(concentrate), rankdir=rankdir) + for table in tables: + graph.add_node(pydot.Node(str(table.name), + shape="plaintext", + label=_render_table_html(table, metadata, show_indexes, show_datatypes), + fontname=font, fontsize="7.0" + )) + + for table in tables: + for fk in table.foreign_keys: + edge = [table.name, fk.column.table.name] + is_inheritance = fk.parent.primary_key and fk.column.primary_key + if is_inheritance: + edge = edge[::-1] + graph_edge = pydot.Edge( + headlabel="+ %s"%fk.column.name, taillabel='+ %s'%fk.parent.name, + arrowhead=is_inheritance and 'none' or 'odot' , + arrowtail=(fk.parent.primary_key or fk.parent.unique) and 'empty' or 'crow' , + fontname=font, + #samehead=fk.column.name, sametail=fk.parent.name, + *edge, **relation_kwargs + ) + graph.add_edge(graph_edge) + +# not sure what this part is for, doesn't work with pydot 1.0.2 +# graph_edge.parent_graph = graph.parent_graph +# if table.name not in [e.get_source() for e in graph.get_edge_list()]: +# graph.edge_src_list.append(table.name) +# if fk.column.table.name not in graph.edge_dst_list: +# graph.edge_dst_list.append(fk.column.table.name) +# graph.sorted_graph_elements.append(graph_edge) + return graph + +def show_uml_graph(*args, **kwargs): + from cStringIO import StringIO + from PIL import Image + iostream = StringIO(create_uml_graph(*args, **kwargs).create_png()) + Image.open(iostream).show(command=kwargs.get('command','gwenview')) + +def show_schema_graph(*args, **kwargs): + from cStringIO import StringIO + from PIL import Image + iostream = StringIO(create_schema_graph(*args, **kwargs).create_png()) + Image.open(iostream).show(command=kwargs.get('command','gwenview')) diff -r eb04ac3a8327 -r 3f4bdea2abbf twisted/plugins/iro_plugin.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/twisted/plugins/iro_plugin.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,47 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from zope.interface import implements + +from twisted.python import usage +from twisted.application.service import IServiceMaker +from twisted.plugin import IPlugin + +from iro import iro + +class Options(usage.Options): + synopsis = "[options]" + longdesc = "Make an iro server." + optParameters = [ + ['config', 'c', 'iro.conf', 'configuration file.'], + ] + +class MyServiceMaker(object): + implements(IServiceMaker, IPlugin) + + tapname = "iro" + description = "An iro server." + options = Options + + def makeService(self, config): + return iro.makeService(config) + +serviceMaker = MyServiceMaker() diff -r eb04ac3a8327 -r 3f4bdea2abbf web/css/960.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/css/960.css Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,1 @@ +.container_12,.container_16{margin-left:auto;margin-right:auto;width:960px}.grid_1,.grid_2,.grid_3,.grid_4,.grid_5,.grid_6,.grid_7,.grid_8,.grid_9,.grid_10,.grid_11,.grid_12,.grid_13,.grid_14,.grid_15,.grid_16{display:inline;float:left;margin-left:10px;margin-right:10px}.push_1,.pull_1,.push_2,.pull_2,.push_3,.pull_3,.push_4,.pull_4,.push_5,.pull_5,.push_6,.pull_6,.push_7,.pull_7,.push_8,.pull_8,.push_9,.pull_9,.push_10,.pull_10,.push_11,.pull_11,.push_12,.pull_12,.push_13,.pull_13,.push_14,.pull_14,.push_15,.pull_15{position:relative}.container_12 .grid_3,.container_16 .grid_4{width:220px}.container_12 .grid_6,.container_16 .grid_8{width:460px}.container_12 .grid_9,.container_16 .grid_12{width:700px}.container_12 .grid_12,.container_16 .grid_16{width:940px}.alpha{margin-left:0}.omega{margin-right:0}.container_12 .grid_1{width:60px}.container_12 .grid_2{width:140px}.container_12 .grid_4{width:300px}.container_12 .grid_5{width:380px}.container_12 .grid_7{width:540px}.container_12 .grid_8{width:620px}.container_12 .grid_10{width:780px}.container_12 .grid_11{width:860px}.container_16 .grid_1{width:40px}.container_16 .grid_2{width:100px}.container_16 .grid_3{width:160px}.container_16 .grid_5{width:280px}.container_16 .grid_6{width:340px}.container_16 .grid_7{width:400px}.container_16 .grid_9{width:520px}.container_16 .grid_10{width:580px}.container_16 .grid_11{width:640px}.container_16 .grid_13{width:760px}.container_16 .grid_14{width:820px}.container_16 .grid_15{width:880px}.container_12 .prefix_3,.container_16 .prefix_4{padding-left:240px}.container_12 .prefix_6,.container_16 .prefix_8{padding-left:480px}.container_12 .prefix_9,.container_16 .prefix_12{padding-left:720px}.container_12 .prefix_1{padding-left:80px}.container_12 .prefix_2{padding-left:160px}.container_12 .prefix_4{padding-left:320px}.container_12 .prefix_5{padding-left:400px}.container_12 .prefix_7{padding-left:560px}.container_12 .prefix_8{padding-left:640px}.container_12 .prefix_10{padding-left:800px}.container_12 .prefix_11{padding-left:880px}.container_16 .prefix_1{padding-left:60px}.container_16 .prefix_2{padding-left:120px}.container_16 .prefix_3{padding-left:180px}.container_16 .prefix_5{padding-left:300px}.container_16 .prefix_6{padding-left:360px}.container_16 .prefix_7{padding-left:420px}.container_16 .prefix_9{padding-left:540px}.container_16 .prefix_10{padding-left:600px}.container_16 .prefix_11{padding-left:660px}.container_16 .prefix_13{padding-left:780px}.container_16 .prefix_14{padding-left:840px}.container_16 .prefix_15{padding-left:900px}.container_12 .suffix_3,.container_16 .suffix_4{padding-right:240px}.container_12 .suffix_6,.container_16 .suffix_8{padding-right:480px}.container_12 .suffix_9,.container_16 .suffix_12{padding-right:720px}.container_12 .suffix_1{padding-right:80px}.container_12 .suffix_2{padding-right:160px}.container_12 .suffix_4{padding-right:320px}.container_12 .suffix_5{padding-right:400px}.container_12 .suffix_7{padding-right:560px}.container_12 .suffix_8{padding-right:640px}.container_12 .suffix_10{padding-right:800px}.container_12 .suffix_11{padding-right:880px}.container_16 .suffix_1{padding-right:60px}.container_16 .suffix_2{padding-right:120px}.container_16 .suffix_3{padding-right:180px}.container_16 .suffix_5{padding-right:300px}.container_16 .suffix_6{padding-right:360px}.container_16 .suffix_7{padding-right:420px}.container_16 .suffix_9{padding-right:540px}.container_16 .suffix_10{padding-right:600px}.container_16 .suffix_11{padding-right:660px}.container_16 .suffix_13{padding-right:780px}.container_16 .suffix_14{padding-right:840px}.container_16 .suffix_15{padding-right:900px}.container_12 .push_3,.container_16 .push_4{left:240px}.container_12 .push_6,.container_16 .push_8{left:480px}.container_12 .push_9,.container_16 .push_12{left:720px}.container_12 .push_1{left:80px}.container_12 .push_2{left:160px}.container_12 .push_4{left:320px}.container_12 .push_5{left:400px}.container_12 .push_7{left:560px}.container_12 .push_8{left:640px}.container_12 .push_10{left:800px}.container_12 .push_11{left:880px}.container_16 .push_1{left:60px}.container_16 .push_2{left:120px}.container_16 .push_3{left:180px}.container_16 .push_5{left:300px}.container_16 .push_6{left:360px}.container_16 .push_7{left:420px}.container_16 .push_9{left:540px}.container_16 .push_10{left:600px}.container_16 .push_11{left:660px}.container_16 .push_13{left:780px}.container_16 .push_14{left:840px}.container_16 .push_15{left:900px}.container_12 .pull_3,.container_16 .pull_4{left:-240px}.container_12 .pull_6,.container_16 .pull_8{left:-480px}.container_12 .pull_9,.container_16 .pull_12{left:-720px}.container_12 .pull_1{left:-80px}.container_12 .pull_2{left:-160px}.container_12 .pull_4{left:-320px}.container_12 .pull_5{left:-400px}.container_12 .pull_7{left:-560px}.container_12 .pull_8{left:-640px}.container_12 .pull_10{left:-800px}.container_12 .pull_11{left:-880px}.container_16 .pull_1{left:-60px}.container_16 .pull_2{left:-120px}.container_16 .pull_3{left:-180px}.container_16 .pull_5{left:-300px}.container_16 .pull_6{left:-360px}.container_16 .pull_7{left:-420px}.container_16 .pull_9{left:-540px}.container_16 .pull_10{left:-600px}.container_16 .pull_11{left:-660px}.container_16 .pull_13{left:-780px}.container_16 .pull_14{left:-840px}.container_16 .pull_15{left:-900px}.clear{clear:both;display:block;overflow:hidden;visibility:hidden;width:0;height:0}.clearfix:after{clear:both;content:' ';display:block;font-size:0;line-height:0;visibility:hidden;width:0;height:0}* html .clearfix,*:first-child+html .clearfix{zoom:1} \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf web/css/reset.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/css/reset.css Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,1 @@ +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;outline:0;font-size:100%;vertical-align:baseline;background:transparent}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}:focus{outline:0}ins{text-decoration:none}del{text-decoration:line-through}table{border-collapse:collapse;border-spacing:0} \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf web/css/style-ie.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/css/style-ie.css Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,15 @@ +#head #menu { + + width: 750px; + +} + + #head #menu li { + + display: inline; + position: relative; + float: left; + margin: 0px; + padding: 0px; + + } \ No newline at end of file diff -r eb04ac3a8327 -r 3f4bdea2abbf web/css/style.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/css/style.css Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,975 @@ + +body { + + background-color: #eee; + color: #111; + margin: 0px; + padding: 0px; + font-family: "DroidSans", "Trebuchet MS", sans-serif; + line-height: 1.6em; + font-size: 1.3em; + +} + + #head-container { + + display: block; + position: relative; + width: 100%; + height: 90px; + + background: #111 url(../images/head.png) top left repeat-x; + + } + + #head { + + display: block; + position: relative; + width: 960px; + height: 90px; + margin: 0px auto; + + } + + #head #logo { + + display: block; + position: absolute; + left: 0px; + bottom: 10px; + width: 200px; + height: 70px; + + } + + #head #logo a { + + display: block; + position: relative; + width: 200px; + padding-left: 70px; + height: 70px; + background: url(../images/netzguerilla-3.png) 0px 0px no-repeat; + text-decoration: none; + + } + + #head #logo a span { + + display: none; + + } + + #head #menu { + + display: block; + position: absolute; + right: 0px; + bottom: 0px; + margin: 0px; + padding: 0px; + + } + + #head #menu li { + + display: inline-block; + position: relative; + margin: 0px; + padding: 0px; + + } + + #head #menu li a { + + display: block; + position: relative; + + color: #c03; + text-decoration: none; + text-transform: uppercase; + padding: 0px 7px; + background: #333 url(../images/fade-top.png) bottom left repeat-x; + margin-left: 5px; + font-size: .8em; + font-weight: bold; + + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + + } + + #head #menu li a:hover { + + background: #c03 url(../images/fade-top.png) bottom left repeat-x; + color: #fff; + + } + + #head #menu li a.active { + + background-color: #eee; + background-image: none; + color: #000; + + } + + #content-container { + + display: block; + position: relative; + + } + #content { + + padding: 20px 0px; + + } + + #main {} + + #main h2 { + + display: block; + position: relative; + color: #a01; + font-size: 1.8em; + line-height: 1.4em; + margin-bottom: 15px; + text-shadow: 0px 0px 5px #fff; + + } + + #main h3 { + + display: block; + position: relative; + color: #b02; + font-size: 1.4em; + line-height: 1.4em; + margin: 10px 0px; + + } + + #main h4 { + + display: block; + position: relative; + color: #a01; + font-size: 1.3em; + line-height: 1.4em; + margin: 10px 0px; + + } + + #main h5 { + + display: block; + position: relative; + color: #900; + font-size: 1.2em; + line-height: 1.4em; + margin: 10px 0px; + + } + + #main .item { + + display: block; + position: relative; + + padding: 30px 40px; + + border-radius: 15px; + -o-border-radius: 15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; + + -moz-box-shadow: 0px 0px 20px #eee; + -o-box-shadow: 0px 0px 20px #eee; + -webkit-box-shadow: 0px 0px 20px #eee; + box-shadow: 0px 0px 20px #eee; + + background-color: #fff; + margin-bottom: 20px; + font-size: .9em; + + } + + #main .item p { + + margin: 5px 0px; + + } + + #main .item a { + + text-decoration: none; + color: #b02; + + } + + #main .item ol { + + list-style-type: none; + list-style-position: inside; + + } + + #main .item ol li { + + margin-left: 2em; + + } + + #main .item table { + + width: 100%; + margin: 0px; + + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + + -moz-box-shadow: 0px 0px 15px #ccc; + -o-box-shadow: 0px 0px 15px #ccc; + -webkit-box-shadow: 0px 0px 15px #ccc; + box-shadow: 0px 0px 15px #ccc; + + + } + + #main .item table.docs { + + font-size: .8em; + + } + + #main .item table.docs td { + + font-weight: bold; + padding-right: 0.2em; + min-width: 120px; + + } + + #main .item table.docs td+td { + + font-weight: normal; + padding-right: 0.2em; + min-width: 80px; + + } + + #main .item table.docs td+td+td { + + font-weight: normal; + padding-right: 0em; + white-space: normal; + + } + + #main .item table td { + + background-color: #eee; + line-height: 1.7em; + + } + + #main .item table thead tr td { + + background-color: #c03; + color: #fff; + font-size: .8em; + padding: 5px; + + } + + #main .item table tbody tr td { + + color: #222; + vertical-align: top; + + } + + #main .item table tbody tr td.content { + + padding: 10px; + font-size: .8em; + + } + + #main .item table tbody tr td.content strong.title { + + display: inline-block; + position: relative; + max-width: 140px; + overflow: hidden; + + } + + #main .item table tbody tr td.content span.meta { + + display: block; + position: relative; + max-width: 140px; + overflow: hidden; + line-height: 1.4em; + font-size: .8em; + + } + + #main .item table tbody tr td.content span.hate { + + display: block; + position: relative; + line-height: 1.4em; + border: 1px solid #ccc; + padding: 10px; + text-align: justify; + + } + #main .item table tbody tr:nth-child(odd) td { + + background-color: #f9f9f9; + + } + + #main .item table tbody tr:nth-child(even) td { + + background-color: #f6f6f6; + + } + + #main .item table tr td:first-child { + + padding-left: 5px; + + } + + #main .item table tr td:last-child { + + padding-right: 5px; + + } + + #main .item table tr td.controls { + + width: 80px; + text-align: right; + + } + + #main .item table tr td.controls a { + + display: inline-block; + width: 20px; + height: 20px; + overflow: hidden; + text-indent: 30px; + background-color: #eee; + + -moz-box-shadow: 0px 0px 5px #ccc; + -o-box-shadow: 0px 0px 5px #ccc; + -webkit-box-shadow: 0px 0px 5px #ccc; + box-shadow: 0px 0px 5px #ccc; + + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + + vertical-align: bottom; + margin: 0px 0px 4px 2px; + + background-image: url(../images/icons/bug.png); + background-position: center center; + background-repeat: no-repeat; + + } + + #main .item table tr td.controls a:hover { + + background-color: #c03; + + } + + #main .item table tr td.controls a.edit { + + background-image: url(../images/icons/pencil.png); + + } + + #main .item table tr td.controls a.delete { + + background-image: url(../images/icons/bin.png); + + } + + #main .item table tr td.controls a.details { + + background-image: url(../images/icons/zoom.png); + + } + + #main .item table tr td.controls a.accept { + + background-image: url(../images/icons/accept.png); + + } + + /* round corners */ + + #main .item table thead tr:first-child td:first-child { + + -webkit-border-top-left-radius: 5px; + -moz-border-radius-topleft: 5px; + border-top-left-radius: 5px; + + } + + #main .item table thead tr:first-child td:last-child { + + -webkit-border-top-right-radius: 5px; + -moz-border-radius-topright: 5px; + border-top-right-radius: 5px; + + } + + #main .item table tbody tr:last-child td:first-child { + + -webkit-border-bottom-left-radius: 5px; + -moz-border-radius-bottomleft: 5px; + border-bottom-left-radius: 5px; + + } + + #main .item table tbody tr:last-child td:last-child { + + -webkit-border-bottom-right-radius: 5px; + -moz-border-radius-bottomright: 5px; + border-bottom-right-radius: 5px; + + } + + #main .item code.copyme { + + display: block; + margin: 0px 0px 10px; + padding: 10px; + line-height: 1em; + border: 1px solid #ccc; + background-color: #eee; + + } + + /* main view */ + + #main .hate-pagination { + + display: block; + position: relative; + line-height: 1.2em; + padding: 10px 0px; + + } + + #main .hate-pagination-back { + + display: block; + position: relative; + width: 300px; + text-align: left; + float: left; + + } + + #main .hate-pagination-forward { + + display: block; + position: relative; + width: 300px; + text-align: right; + float: right; + + } + + #main .ad-item { + + display: block; + position: relative; + border-bottom: 1px solid #ccc; + line-height: 1.2em; + padding: 20px 0px; + text-align: center; + + } + + #main .hate-item { + + display: block; + position: relative; + border-bottom: 1px solid #ccc; + line-height: 1.2em; + padding: 10px 0px; + + } + + #main .hate-item .hate-item-icon { + + display: block; + position: relative; + width: 48px; + padding: 5px 0px 5px 10px; + float: left; + + } + + #main .hate-item .hate-item-main { + + display: block; + position: relative; + width: 550px; + float: right; + + } + + #main .hate-item .hate-item-author { + + display: block; + position: relative; + font-size: .8em; + font-weight: bold; + color: #222; + + } + + #main .hate-item .hate-item-author a { + + color: #222; + + } + + #main .hate-item .hate-item-content { + + display: block; + position: relative; + font-size: .8em; + font-weight: normal; + color: #444; + + } + + #main .hate-item .hate-item-meta { + + display: block; + position: relative; + font-size: .6em; + color: #666; + + } + + #main .hate-item .hate-item-meta a { + + color: #666; + + } + + #main .hate-item .hate-item-meta a.facepalm { + + display: inline-block; + padding-left: 20px; + line-height: 16px; + background: url(../images/icons/facepalm.png) top left no-repeat; + + } + + #main .hate-item:hover .hate-item-meta a.facepalm { + + background: url(../images/icons/facepalm-hl.png) top left no-repeat; + + } + + #main .hate-item .hate-item-meta span.godwin { + + display: inline-block; + padding-left: 20px; + line-height: 16px; + background: url(../images/icons/godwinbonus.png) top left no-repeat; + + } + + #main .hate-item:hover { + + background-color: #fce; + + } + + #main .hate-item:hover a { + + color: #c03; + + } + + #main .hate-detail { + + display: block; + position: relative; + border-bottom: 1px solid #ccc; + line-height: 1.2em; + padding: 10px 0px; + + } + + #main .hate-detail .hate-item-icon { + + display: block; + position: relative; + width: 48px; + padding: 5px 0px; + float: left; + + } + + #main .hate-detail .hate-item-main { + + display: block; + position: relative; + + } + + #main .hate-detail .hate-item-author { + + display: block; + position: relative; + font-size: 1.2em; + line-height: 50px; + font-weight: bold; + color: #222; + width: 560px; + float: right; + + } + + #main .hate-detail .hate-item-content { + + display: block; + position: relative; + font-size: 1.2em; + line-height: 1.4em; + font-weight: normal; + color: #444; + padding: 10px 0px; + + } + + #main .hate-detail .hate-item-meta { + + display: block; + position: relative; + font-size: .8em; + color: #666; + + } + + #main .hate-detail .hate-item-meta a { + + color: #666; + + } + + #main .hate-detail .hate-item-meta a.facepalm { + + display: inline-block; + padding-left: 20px; + line-height: 16px; + background: url(../images/icons/facepalm.png) top left no-repeat; + + } + + #main .hate-detail:hover .hate-item-meta a.facepalm { + + background: url(../images/icons/facepalm-hl.png) top left no-repeat; + + } + + #main .hate-detail .hate-item-meta span.godwin { + + display: inline-block; + padding-left: 20px; + line-height: 16px; + background: url(../images/icons/godwinbonus.png) top left no-repeat; + + } + + + /* special stuff */ + + #main span.redacted { + + display: inline-block; + background-color: #333; + color: #c03; + line-height: 1.1em; + padding: 0px 5px; + + } + + #main code.apikey { + + font-size: 1.1em; + background-color: #a01; + color: #fff; + display: inline-block; + padding: 0px 5px; + + } + + #main a.bookmarklet { + + font-size: 1.4em; + background-color: #ccc; + color: #666; + border: 1px solid #666; + display: inline-block; + padding: 5px 10px; + + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + + } + + #main form input[type=text], + #main form input[type=password] { + + display: block; + position: relative; + border: 1px solid #999; + font-size: .9em; + padding: 3px; + + border-radius: 5px; + -o-border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + + } + + #main form input[type=submit] { + + display: block; + position: relative; + border: 1px solid #ccc; + color: #111; + font-size: .9em; + padding: 2px; + + border-radius: 5px; + -o-border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + + + } + + #main form span.input { + + display: inline-block; + position: relative; + border: 1px solid #999; + font-size: .9em; + padding: 0px 2px; + background-color: #fff; + border-radius: 5px; + -o-border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + + + } + + #main form span.input input[type=text] { + + display: inline-block; + position: relative; + border-width: 0px; + font-size: 1em; + padding: 0px; + background-color: #fff; + + } + + #main form span.input input[type=text].right { + + text-align: right; + + } + + #main form span.input input.size-2 { + + width: 1.5em !important; + + } + + #main form span.input input.size-4 { + + width: 2.5em !important; + + } + + #main form label { + + display: block; + position: relative; + font-size: .75em; + color: #666; + + } + + #main form textarea { + + display: block; + position: relative; + border: 1px solid #999; + font-size: .9em; + font-family: sans-serif; + padding: 3px; + + border-radius: 5px; + -o-border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + + } + + + #main form fieldset { + + border: 1px solid #ccc; + padding: 5px; + margin: 5px 0px; + + } + + #main form fieldset legend { + + font-size: .75em; + color: #666; + + } + + #sidebar {} + + #sidebar h3 { + + display: block; + position: relative; + color: #fff; + font-size: 1.2em; + line-height: 1.4em; + margin: 5px 0px; + + } + + #sidebar .item { + + display: block; + position: relative; + + padding: 0px 10px; + + border-radius: 10px; + -o-border-radius: 10px; + -moz-border-radius: 10px; + -webkit-border-radius: 10px; + + -moz-box-shadow: 0px 0px 20px #eee; + -o-box-shadow: 0px 0px 20px #eee; + -webkit-box-shadow: 0px 0px 20px #eee; + box-shadow: 0px 0px 20px #eee; + + background-color: #333; + margin-bottom: 20px; + font-size: .8em; + + } + + #sidebar .center { + + text-align: center; + + } + + #sidebar .counter-item .content { + + font-size: 3em; + color: #c03; + font-weight: bold; + line-height: 1em; + padding-bottom: 5px; + text-align: center; + + } + + #sidebar .submenu-item { + + background-color: #ddd; + + } + + #sidebar .submenu-item ul {} + + #sidebar .submenu-item ul li {} + + #sidebar .submenu-item ul li+li { + + border-top: 1px dotted #999; + + } + + #sidebar .submenu-item ul a { + + color: #c03; + text-decoration: none; + text-transform: uppercase; + + } + + #sidebar .submenu-item ul a:hover { + + color: #a01; + + } + + #foot-container { + + display: block; + position: relative; + background: #222; + color: #ddd; + + } + + #foot { + + display: block; + position: relative; + width: 960px; + margin: 0px auto; + padding: 20px; + font-size: .8em; + + } + + #foot a { + + text-decoration: none; + color: #c03; + + } + diff -r eb04ac3a8327 -r 3f4bdea2abbf web/images/db-schema.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/images/db-schema.svg Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,99 @@ + + + + + + +G + + +message + +message + +- id : INTEGER +- recipient : VARCHAR(100) +- isBilled : BOOLEAN +- date : DATETIME +- price : NUMERIC(8, 4) +- count : INTEGER +- exID : VARCHAR(100) +- job : VARCHAR(40) +- offer : VARCHAR(100) + + +job + +job + +- id : INTEGER +- info : VARCHAR(100) +- status : VARCHAR(7) +- user : VARCHAR(100) + + +message->job + + ++ id ++ job + + +offer + +offer + +- name : VARCHAR(100) +- provider : VARCHAR(100) +- route : VARCHAR(100) +- typ : VARCHAR(100) + + +message->offer + + ++ name ++ offer + + +userright + +userright + +- user : VARCHAR(100) +- offer : VARCHAR(100) +- default : INTEGER + + +apiuser + +apiuser + +- name : VARCHAR(100) +- ng_kunde : INTEGER +- apikey : VARCHAR(50) + + +job->apiuser + + ++ name ++ user + + +apiuser->userright + ++ name ++ user + + +offer->userright + ++ name ++ offer + + + diff -r eb04ac3a8327 -r 3f4bdea2abbf web/images/favicon.png Binary file web/images/favicon.png has changed diff -r eb04ac3a8327 -r 3f4bdea2abbf web/images/netzguerilla-3.png Binary file web/images/netzguerilla-3.png has changed