--- 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
--- /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 <iro@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.
--- /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 *
+
--- 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)
--- 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.
--- /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]*
--- /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 <iro@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))
--- /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 <iro@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.")
+
+
--- 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 <iro@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<name>[a-zA-Z0-9-_.]*)\[(?P<typ>[a-zA-Z0-9-_|]*)\]:(?P<d>.*)$",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 '<Keyword("%s", "%s", "%s")>'%(self.name, self.typ, self.description)
def keywords(f):
- doc=f.__doc__
- kwds=re.search("Keywords:\n(?P<keywords>(?P<whitespace>\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<ret>(?P<whitespace>\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__':
--- /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 <iro@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')
--- /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 <iro@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
+# "<project> v<release> 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 <link> 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 ")}
--- 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
--- 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
--- 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
--- 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;
-
- }
-
--- 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 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-
-
- <head>
- <title>Iro · API docs</title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <meta charset="utf-8">
- <meta name="description" content="">
- <meta name="keywords" content="">
- <link rel="shortcut icon" type="image/x-icon" href="images/favicon.png">
- <link rel="stylesheet" href="css/reset.css" type="text/css" media="screen">
- <link rel="stylesheet" href="css/960.css" type="text/css" media="screen">
- <link rel="stylesheet" href="css/style.css" type="text/css" media="screen">
- <!--[if IE]><link rel="stylesheet" href="css/style-ie.css" type="text/css" media="screen" /><![endif] -->
- </head>
- <body>
- <div id="head-container">
- <div id="head">
- <h1 id="logo"><a href="index.html" class="logo" title="Netzguerilla"><span>Netzguerilla</span></a></h1>
- <ul id="menu">
- <li><a href="index.html" class="menu">Iro</a></li><li><a href="current.html" class="menu active">API Documentation</a></li><li><a href="new.html" class="menu">geplante API Documentation</a></li><li><a href="impressum.html" class="menu">Impressum</a></li>
- </ul>
- </div>
- </div>
- <div id="content-container">
- <div id="content" class="container_12">
- <div id="main" class="grid_9">
- <h2>API Dokumentation</h2>
- <div class="item">
- <p>
- </p>
- <ol>
- <li value="1">1. <a href="#api-intro">Einführung</a></li>
- <li value="2">2.
- <a href="#api-interfaces">Interfaces</a>
- <ol>
- <li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
- </ol>
- </li>
- <li value="3">3.
- <a href="#api-methods">Methoden</a>
- <ol>
- <li value="3.1">3.1 <a href="#method-startSMS">StartSMS</a></li><li value="3.2">3.2 <a href="#method-startFAX">StartFAX</a></li><li value="3.3">3.3 <a href="#method-startMail">StartMail</a></li><li value="3.4">3.4 <a href="#method-status">Status</a></li><li value="3.5">3.5 <a href="#method-stop">Stop</a></li><li value="3.6">3.6 <a href="#method-getProvider">GetProvider</a></li><li value="3.7">3.7 <a href="#method-getDefaultProvider">GetDefaultProvider</a></li>
- </ol>
- </li>
- </ol>
- </div><div class="item" id="api-intro">
- <h3>Einführung</h3>
- <p>
- Die Iro API enthält Funktion, die für den Massenversand nützlich sind.
- </p>
- </div><div class="item" id="api-interfaces">
- <h3>Interfaces</h3>
- <p>
- Die Iro API stellt zur Zeit nur ein Interfaces bereit.
- </p>
- <div class="item" id="interface-xmlrpc">
- <h4>XML-RPC</h4>
- <p>
- Interface-URI: <code>https://<em><benutzer></em>:<em><passwort></em>@localhost:8000</code>
- </p>
- <p>
- Die aufgerufene Methode wird dabei im <code><methodName /></code> übergeben.
- </p>
- </div>
- </div><div class="item" id="api-methods">
- <h3>Methoden</h3>
- <div class="item" id="method-startSMS">
- <h4>StartSMS</h4>
- <p><code>startSMS(message, recipients, provider='default')</code></p>
- <p>Versendet eine SMS.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>message</td>
- <td>string</td>
- <td> Nachricht</td>
- </tr><tr>
- <td>recipients</td>
- <td>list</td>
- <td> eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)</td>
- </tr><tr>
- <td>provider</td>
- <td>string</td>
- <td> Provider über den geschickt werden soll</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>id</td>
- <td>hash</td>
- <td> Die ID des Auftrages</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-startFAX">
- <h4>StartFAX</h4>
- <p><code>startFAX(subject, fax, recipients, provider='default')</code></p>
- <p>Versendet ein FAX.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>subject</td>
- <td>string</td>
- <td> der Betreff</td>
- </tr><tr>
- <td>fax</td>
- <td>string</td>
- <td> das pdf base64 kodiert</td>
- </tr><tr>
- <td>recipients</td>
- <td>list</td>
- <td> eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)</td>
- </tr><tr>
- <td>provider</td>
- <td>string</td>
- <td> Provider über den geschickt werden soll</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>id</td>
- <td>hash</td>
- <td> Die ID des Auftrages</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-startMail">
- <h4>StartMail</h4>
- <p><code>startMail(subject, body, recipients, frm, provider='default')</code></p>
- <p>Versendet eine Email.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>subject</td>
- <td>string</td>
- <td> der Betreff</td>
- </tr><tr>
- <td>body</td>
- <td>string</td>
- <td> der Email Body</td>
- </tr><tr>
- <td>recipients</td>
- <td>list</td>
- <td> eine Liste von Emailadressen</td>
- </tr><tr>
- <td>frm</td>
- <td>string</td>
- <td> Die Absender Emailadresse</td>
- </tr><tr>
- <td>provider</td>
- <td>string</td>
- <td> Provider über den geschickt werden soll</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>id</td>
- <td>hash</td>
- <td> Die ID des Auftrages</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-status">
- <h4>Status</h4>
- <p><code>status(id=None, detailed=False)</code></p>
- <p>Gibt den aktuellen Status eines Auftrages zurück.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>id</td>
- <td>hash</td>
- <td> Eine Auftragsnummer</td>
- </tr><tr>
- <td>detailed</td>
- <td>boolean</td>
- <td> Details ausgeben</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>jobs</td>
- <td>list</td>
- <td> Eine Liste der Aufträge.</td>
- </tr><tr>
- <td>job.name</td>
- <td>string</td>
- <td> Angebener Name</td>
- </tr><tr>
- <td>job.status</td>
- <td>string</td>
- <td> Status des Auftrages</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-stop">
- <h4>Stop</h4>
- <p><code>stop(id)</code></p>
- <p>Stoppt den angegeben Auftrag.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>id</td>
- <td>hash</td>
- <td> Eine Auftragsnummer</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-getProvider">
- <h4>GetProvider</h4>
- <p><code>getProvider(typ)</code></p>
- <p>Gibt eine Liste aller verfügbaren Provider zurück.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>typ</td>
- <td>string</td>
- <td> Der Typ zu dem die Providerloste ausgeben werden soll
-Einer der Liste ["sms","fax","mail"]</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>providerlist</td>
- <td>list</td>
- <td> Eine Liste aller möglichen Provider</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-getDefaultProvider">
- <h4>GetDefaultProvider</h4>
- <p><code>getDefaultProvider(typ)</code></p>
- <p>Gibt den Standardprovider zurück.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>typ</td>
- <td>string</td>
- <td> Der Typ zu dem die Providerloste ausgeben werden soll
-Einer der Liste ["sms","fax","mail"]</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>provider</td>
- <td>string</td>
- <td> Der Standardprovider für den angeben Typ</td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
- </div>
- <div class="clear"></div>
- </div>
- </div>
- <div id="foot-container">
- <div id="foot">
- <p>© 2010-2011 <a href="impressum.html">Netzguerilla.net</a>.</p>
- </div>
- </div>
- </body>
-</html>
\ No newline at end of file
Binary file doc/images/favicon.png has changed
Binary file doc/images/netzguerilla-3.png has changed
--- 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 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-
-
- <head>
- <title>Iro · Impressum</title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <meta charset="utf-8">
- <meta name="description" content="">
- <meta name="keywords" content="">
- <link rel="shortcut icon" type="image/x-icon" href="images/favicon.png">
- <link rel="stylesheet" href="css/reset.css" type="text/css" media="screen">
- <link rel="stylesheet" href="css/960.css" type="text/css" media="screen">
- <link rel="stylesheet" href="css/style.css" type="text/css" media="screen">
- <!--[if IE]><link rel="stylesheet" href="css/style-ie.css" type="text/css" media="screen" /><![endif] -->
- </head>
- <body>
- <div id="head-container">
- <div id="head">
- <h1 id="logo"><a href="index.html" class="logo" title="Netzguerilla"><span>Netzguerilla</span></a></h1>
- <ul id="menu">
- <li><a href="index.html" class="menu">Iro</a></li><li><a href="current.html" class="menu">API Documentation</a></li><li><a href="new.html" class="menu">geplante API Documentation</a></li><li><a href="impressum.html" class="menu active">Impressum</a></li>
- </ul>
- </div>
- </div>
- <div id="content-container">
- <div id="content" class="container_12">
- <div id="main" class="grid_9">
- <h2>Impressum</h2>
- <div class="item">
- <h3>Kontakt</h3>
- <p>
- <strong>Netzguerilla.net</strong> Clerc Knauß Vollnhals GbR<br>
- Manteuffelstraße 40<br>
- 10997 Berlin
- </p>
- <p>
- WWW: <a href="https://netzguerilla.net/">netzguerilla.net</a><br>
- </p>
- <p>
- E-Mail: <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a><br>
- </p>
- <p>
- Telefon: <a href="tel:+493069201075">+49 30 69201075</a><br>
- </p>
- <h3>Inhaltlich verantwortlich</h3>
- <p>
- <small>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV bzw. §5 TMG sowie beauftragt mit dem Jugendschutz gemäß TMG, JMStV sowie GjSM:</small><br>
- <strong>Sandro Knauß</strong>, Anschrift wie oben, <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a>
- </p>
- </div>
- </div>
- <div class="clear"></div>
- </div>
- </div>
- <div id="foot-container">
- <div id="foot">
- <p>© 2010-2011 <a href="impressum.html">Netzguerilla.net</a>.</p>
- </div>
- </div>
- </body>
-</html>
\ No newline at end of file
--- 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 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-
-
- <head>
- <title>Iro · work in progress</title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <meta charset="utf-8">
- <meta name="description" content="">
- <meta name="keywords" content="">
- <link rel="shortcut icon" type="image/x-icon" href="images/favicon.png">
- <link rel="stylesheet" href="css/reset.css" type="text/css" media="screen">
- <link rel="stylesheet" href="css/960.css" type="text/css" media="screen">
- <link rel="stylesheet" href="css/style.css" type="text/css" media="screen">
- <!--[if IE]><link rel="stylesheet" href="css/style-ie.css" type="text/css" media="screen" /><![endif] -->
- </head>
- <body>
- <div id="head-container">
- <div id="head">
- <h1 id="logo"><a href="index.html" class="logo" title="Netzguerilla"><span>Netzguerilla</span></a></h1>
- <ul id="menu">
- <li><a href="index.html" class="menu active">Iro</a></li><li><a href="current.html" class="menu">API Documentation</a></li><li><a href="new.html" class="menu">geplante API Documentation</a></li><li><a href="impressum.html" class="menu">Impressum</a></li>
- </ul>
- </div>
- </div>
- <div id="content-container">
- <div id="content" class="container_12">
- <div id="main" class="grid_9">
- <h2>Iro</h2>
- <div class="item">
- <p>
- Iro ist toll, ist aber noch nicht fertig.
- </p>
- </div>
- </div>
- <div class="clear"></div>
- </div>
- </div>
- <div id="foot-container">
- <div id="foot">
- <p>© 2010-2011 <a href="impressum.html">Netzguerilla.net</a>.</p>
- </div>
- </div>
- </body>
-</html>
\ No newline at end of file
--- /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`
+
--- /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
--- /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:
+
--- /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:
+
--- /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:
+
--- /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
+
--- /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:
+
--- 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 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<html>
-
-
- <head>
- <title>Iro · api docs new</title>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <meta charset="utf-8">
- <meta name="description" content="">
- <meta name="keywords" content="">
- <link rel="shortcut icon" type="image/x-icon" href="images/favicon.png">
- <link rel="stylesheet" href="css/reset.css" type="text/css" media="screen">
- <link rel="stylesheet" href="css/960.css" type="text/css" media="screen">
- <link rel="stylesheet" href="css/style.css" type="text/css" media="screen">
- <!--[if IE]><link rel="stylesheet" href="css/style-ie.css" type="text/css" media="screen" /><![endif] -->
- </head>
- <body>
- <div id="head-container">
- <div id="head">
- <h1 id="logo"><a href="index.html" class="logo" title="Netzguerilla"><span>Netzguerilla</span></a></h1>
- <ul id="menu">
- <li><a href="index.html" class="menu">Iro</a></li><li><a href="current.html" class="menu">API Documentation</a></li><li><a href="new.html" class="menu active">geplante API Documentation</a></li><li><a href="impressum.html" class="menu">Impressum</a></li>
- </ul>
- </div>
- </div>
- <div id="content-container">
- <div id="content" class="container_12">
- <div id="main" class="grid_9">
- <h2>geplante API Dokumentation</h2>
- <div class="item">
- <p>
- </p>
- <ol>
- <li value="1">1. <a href="#api-intro">Einführung</a></li>
- <li value="2">2.
- <a href="#api-interfaces">Interfaces</a>
- <ol>
- <li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
- <li value="2.1">2.1 <a href="#interface-soap">SOAP</a></li>
- <li value="2.2">2.2 <a href="#interface-xml">XML</a></li>
- <li value="2.3">2.3 <a href="#interface-json">JSON</a></li>
- <li value="2.4">2.4 <a href="#interface-jsonp">JSONP</a></li>
- <li value="2.5">2.5 <a href="#interface-php">PHP</a></li>
- </ol>
- </li>
- <li value="3">3.
- <a href="#api-methods">Methoden</a>
- <ol>
- <li value="3.1">3.1 <a href="#method-sms">Sms</a></li><li value="3.2">3.2 <a href="#method-fax">Fax</a></li><li value="3.3">3.3 <a href="#method-mail">Mail</a></li><li value="3.4">3.4 <a href="#method-status">Status</a></li><li value="3.5">3.5 <a href="#method-stop">Stop</a></li><li value="3.6">3.6 <a href="#method-routes">Routes</a></li><li value="3.7">3.7 <a href="#method-defaultRoute">DefaultRoute</a></li>
- </ol>
- </li>
- </ol>
- </div><div class="item" id="api-intro">
- <h3>Einführung</h3>
- <p>
- Die Iro API enthält Funktion, die für den Massenversand nützlich sind.
- </p>
- </div><div class="item" id="api-interfaces">
- <h3>Interfaces</h3>
- <p>
- Die Iro API wird über verschiedene Interfaces bereit gestellt, die unterschiedlich angesprochen werden, aber das selbe tun.
- </p>
- <div class="item" id="interface-xmlrpc">
- <h4>XML-RPC</h4>
- <p>
- Interface-URI: <code>http://localhost:8000/xmlrpc</code>
- </p>
- <p>
- Die aufgerufene Methode wird dabei im <code><methodName /></code> übergeben.
- </p>
- </div>
- <div class="item" id="interface-soap">
- <h4>SOAP</h4>
- <p>
- Interface-URI: <code>http://localhost:8000/soap</code>
- </p>
- <p>
- Die aufgerufene Methode wird dabei im <code><methodName /></code> übergeben.
- </p>
- </div>
- <div class="item" id="interface-xml">
- <h4>XML</h4>
- <p>
- Interface-URI: <code>http://localhost:8000/xml/<em><methode></em></code>
- </p>
- <p>
- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
- </p>
- <p>
- Parameter können via HTTP per GET oder POST im Format <em>application/x-www-form-urlencoded</em> übergeben werden.
- </p>
- <p>
- Die Ausgabe erfolgt als XML Markup.
- </p>
- </div>
- <div class="item" id="interface-json">
- <h4>JSON</h4>
- <p>
- Interface-URI: <code>http://localhost:8000/json/<em><methode></em></code>
- </p>
- <p>
- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
- </p>
- <p>
- Parameter können via HTTP per GET oder POST im Format <em>application/x-www-form-urlencoded</em> oder JSON-Objekt übergeben werden.
- </p>
- <p>
- Die Ausgabe erfolgt als JSON-Objekt.
- </p>
- </div>
- <div class="item" id="interface-jsonp">
- <h4>JSONP</h4>
- <p>
- Interface-URI: <code>http://localhost:8000/jsonp/<em><methode></em>?callback=<callback></code>
- </p>
- <p>
- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
- </p>
- <p>
- Der Name für die Callback-Methode wird als Parameter Callback übergeben.
- </p>
- <p>
- Parameter können via HTTP per GET im Format <em>application/x-www-form-urlencoded</em> übergeben werden.
- </p>
- <p>
- Die Ausgabe erfolgt als Javascript-Funktionsaufruf mit einem JSON-Objekt als Parameter.
- </p>
- </div>
- </div><div class="item" id="api-methods">
- <h3>Methoden</h3>
- <div class="item" id="method-sms">
- <h4>Sms</h4>
- <p><code>sms(apikey, message, recipients, route='default')</code></p>
- <p>Versendet eine SMS.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>apikey</td>
- <td>string</td>
- <td> Der API Key</td>
- </tr><tr>
- <td>message</td>
- <td>string</td>
- <td> Nachricht</td>
- </tr><tr>
- <td>recipients</td>
- <td>list</td>
- <td> eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)</td>
- </tr><tr>
- <td>route</td>
- <td>string|list</td>
- <td> Route über den geschickt werden soll,
-oder eine Liste von Routen, um Fallbacks anzugeben</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>id</td>
- <td>hash</td>
- <td> Die ID des Auftrages</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-fax">
- <h4>Fax</h4>
- <p><code>fax(apikey, subject, fax, recipients, route='default')</code></p>
- <p>Versendet ein FAX.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>apikey</td>
- <td>string</td>
- <td> Der API Key</td>
- </tr><tr>
- <td>subject</td>
- <td>string</td>
- <td> Der Betreff</td>
- </tr><tr>
- <td>fax</td>
- <td>string</td>
- <td> Das PDF base64 kodiert</td>
- </tr><tr>
- <td>recipients</td>
- <td>list</td>
- <td> Eine Liste von Emfänger-Nummern (gemäß ITU-T E.123)</td>
- </tr><tr>
- <td>route</td>
- <td>string|list</td>
- <td> Route über den geschickt werden soll,
-oder eine Liste von Routen, um Fallbacks anzugeben</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>id</td>
- <td>hash</td>
- <td> Die ID des Auftrages</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-mail">
- <h4>Mail</h4>
- <p><code>mail(apikey, subject, body, recipients, frm, route='default')</code></p>
- <p>Versendet eine Email.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>apikey</td>
- <td>string</td>
- <td> Der API Key</td>
- </tr><tr>
- <td>subject</td>
- <td>string</td>
- <td> Der Betreff</td>
- </tr><tr>
- <td>body</td>
- <td>string</td>
- <td> Der Email Body</td>
- </tr><tr>
- <td>recipients</td>
- <td>list</td>
- <td> Eine Liste von Emailadressen</td>
- </tr><tr>
- <td>frm</td>
- <td>string</td>
- <td> Die Absender Emailadresse</td>
- </tr><tr>
- <td>route</td>
- <td>string|list</td>
- <td> Route über den geschickt werden soll,
-oder eine Liste von Routen, um Fallbacks anzugeben</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>id</td>
- <td>hash</td>
- <td> Die ID des Auftrages</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-status">
- <h4>Status</h4>
- <p><code>status(apikey, id=None, detailed=False)</code></p>
- <p>Gibt den aktuellen Status eines Auftrages zurück.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>apikey</td>
- <td>string</td>
- <td> Der API Key</td>
- </tr><tr>
- <td>id</td>
- <td>hash</td>
- <td> Eine Auftragsnummer</td>
- </tr><tr>
- <td>detailed</td>
- <td>boolean</td>
- <td> Details ausgeben</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>jobs</td>
- <td>list</td>
- <td> Eine Liste der Aufträge.</td>
- </tr><tr>
- <td>job.name</td>
- <td>string</td>
- <td> Angebener Name</td>
- </tr><tr>
- <td>job.status</td>
- <td>string</td>
- <td> Status des Auftrages</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-stop">
- <h4>Stop</h4>
- <p><code>stop(apikey, id)</code></p>
- <p>Stoppt den angegeben Auftrag.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>apikey</td>
- <td>string</td>
- <td> Der API Key</td>
- </tr><tr>
- <td>id</td>
- <td>hash</td>
- <td> Eine Auftragsnummer</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-routes">
- <h4>Routes</h4>
- <p><code>routes(apikey, typ)</code></p>
- <p>Gibt eine Liste aller verfügbaren Provider zurück.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>apikey</td>
- <td>string</td>
- <td> Der API Key</td>
- </tr><tr>
- <td>typ</td>
- <td>string</td>
- <td> Der Typ zu dem die Providerloste ausgeben werden soll
-Einer der Liste ["sms","fax","mail"]</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>providerlist</td>
- <td>list</td>
- <td> Eine Liste aller möglichen Provider</td>
- </tr>
- </tbody>
- </table>
- </div><div class="item" id="method-defaultRoute">
- <h4>DefaultRoute</h4>
- <p><code>defaultRoute(apikey, typ)</code></p>
- <p>Gibt den Standardprovider zurück.</p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>apikey</td>
- <td>string</td>
- <td> Der API Key</td>
- </tr><tr>
- <td>typ</td>
- <td>string</td>
- <td> Der Typ zu dem die Providerloste ausgeben werden soll
-Einer der Liste ["sms","fax","mail"]</td>
- </tr>
- </tbody>
- </table>
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>provider</td>
- <td>string</td>
- <td> Der Standardprovider für den angeben Typ</td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
- </div>
- <div class="clear"></div>
- </div>
- </div>
- <div id="foot-container">
- <div id="foot">
- <p>© 2010-2011 <a href="impressum.html">Netzguerilla.net</a>.</p>
- </div>
- </div>
- </body>
-</html>
\ No newline at end of file
--- /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`.
--- /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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <xi:include href="layout.tmpl" />
+ <head>
+ <title>About us</title>
+ </head>
+ <body>
+ <title>About us</title>
+
+ <div class="item">
+ <h3>Contact</h3>
+ <p>
+ <strong>Netzguerilla.net</strong> Clerc Knauß Vollnhals GbR<br />
+ Manteuffelstraße 40<br />
+ 10997 Berlin
+ </p>
+ <p>
+ WWW: <a href="https://netzguerilla.net/">netzguerilla.net</a><br />
+ </p>
+ <p>
+ E-Mail: <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a><br />
+ </p>
+ <p>
+ Telefon: <a href="tel:+493069201075">+49 30 69201075</a><br />
+ </p>
+ <h3>German legal notice - Inhaltlich verantwortlich</h3>
+ <p>
+ <small>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV bzw. §5 TMG sowie beauftragt mit dem Jugendschutz gemäß TMG, JMStV sowie GjSM:</small><br />
+ <strong>Sandro Knauß</strong>, Anschrift wie oben, <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a>
+ </p>
+ </div>
+ </body>
+</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/">
<xi:include href="layout.tmpl" />
<head>
- <title>API docs</title>
+ <title>api docs current</title>
</head>
<body>
- <title>API Dokumentation</title>
+ <title>API Documentation</title>
<div class="item">
<p>
-
+
</p>
<ol>
- <li value="1">1. <a href="#api-intro">Einführung</a></li>
+ <li value="1">1. <a href="#api-intro">Intro</a></li>
<li value="2">2.
- <a href="#api-interfaces">Interfaces</a>
- <ol>
- <li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
- </ol>
+ <a href="#api-interfaces">Interfaces</a>
+ <ol>
+ <li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
+ <li value="2.1">2.2 <a href="#interface-soap">SOAP</a></li>
+ <li value="2.3">2.3 <a href="#interface-json">JSON</a></li>
+ <li value="2.4">2.4 <a href="#interface-jsonp">JSONP</a></li>
+ </ol>
</li>
<li value="3">3.
- <a href="#api-methods">Methoden</a>
+ <a href="#api-methods">Methods</a>
<ol>
<li py:for="(key,method) in enumerate(current)" value="3.${key+1}">3.${key+1} <a href="#method-${method.name}">${method.title}</a></li>
</ol>
</li>
+
</ol>
</div>
<div class="item" id="api-intro">
- <h3>Einführung</h3>
+ <h3>Intro</h3>
<p>
- 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.
</p>
</div>
<div class="item" id="api-interfaces">
<h3>Interfaces</h3>
<p>
- Die Iro API stellt zur Zeit nur ein Interfaces bereit.
+ You can use diffrent interfaces to get to same result.
</p>
<div class="item" id="interface-xmlrpc">
<h4>XML-RPC</h4>
<p>
- Interface-URI: <code>https://<em><benutzer></em>:<em><passwort></em>@localhost:8000</code>
+ Interface-URI: <code>http://localhost:8000/xmlrpc</code>
+ </p>
+ </div>
+ <div class="item" id="interface-soap">
+ <h4>SOAP</h4>
+ <p>
+ Interface-URI: <code>http://localhost:8000/soap</code>
+ </p>
+ </div>
+ <div class="item" id="interface-json">
+ <h4>JSON</h4>
+ <p>
+ Interface-URI: <code>http://localhost:8000/json/<em><methode></em></code>
</p>
<p>
- Die aufgerufene Methode wird dabei im <code><methodName /></code> übergeben.
+ Not yet implementet
+ </p>
+ </div>
+ <div class="item" id="interface-jsonp">
+ <h4>JSONP</h4>
+ <p>
+ Interface-URI: <code>http://localhost:8000/jsonp/<em><methode></em>?callback=<callback></code>
+ </p>
+ <p>
+ Not yet implementet
</p>
</div>
</div>
<div class="item" id="api-methods">
- <h3>Methoden</h3>
+ <h3>Methods</h3>
<div py:for="method in current" class="item" id="method-${method.name}">
<h4>${method.title}</h4>
<p><code>${method.name}${method.func_line}</code></p>
<p py:content="method.description">
- Diese Methode at bis jetzt noch keine Beschreibung.
+ No description
</p>
<h5>Parameter</h5>
<table class="docs">
<thead>
<tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
+ <td>parameter</td>
+ <td>type</td>
+ <td>description</td>
</tr>
</thead>
<tbody>
@@ -76,13 +101,13 @@
</tbody>
</table>
<py:if test="method.rets">
- <h5>Ausgabe</h5>
+ <h5>Return</h5>
<table class="docs">
<thead>
<tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
+ <td>parameter</td>
+ <td>type</td>
+ <td>description</td>
</tr>
</thead>
<tbody>
--- /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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <xi:include href="layout.tmpl" />
+ <head>
+ <title>Datenbase</title>
+ </head>
+ <body>
+ <title>Datenbase Schema</title>
+ <div class="item">
+ <p>
+
+ </p>
+ <ol>
+ <li value="1">1. <a href="#schema">Datenbase schema</a></li>
+ <li value="3">3.
+ <a href="#tables">Tables</a>
+ <ol>
+ <li py:for="(key,table) in enumerate(tables)" value="2.${key+1}">2.${key+1} <a href="#table-${table.tablename}">${table.title}</a></li>
+ </ol>
+ </li>
+ </ol>
+ </div>
+
+
+ <div class="item" id="schema">
+ <h3>Schema</h3>
+ <img src="images/db-schema.svg" />
+ <p>Overview of used tables.</p>
+ </div>
+ <div class="item" id="tables">
+ <h3>Tables</h3>
+ <div py:for="table in tables" class="item" id="table-${table.tablename}">
+ <h4>${table.title}</h4>
+ <p py:content="table.description">
+ No description available.
+ </p>
+ </div>
+ </div>
+ </body>
+</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 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
-"http://www.w3.org/TR/html4/strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
- <xi:include href="layout.tmpl" />
- <head>
- <title>Impressum</title>
- </head>
- <body>
- <title>Impressum</title>
-
- <div class="item">
- <h3>Kontakt</h3>
- <p>
- <strong>Netzguerilla.net</strong> Clerc Knauß Vollnhals GbR<br />
- Manteuffelstraße 40<br />
- 10997 Berlin
- </p>
- <p>
- WWW: <a href="https://netzguerilla.net/">netzguerilla.net</a><br />
- </p>
- <p>
- E-Mail: <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a><br />
- </p>
- <p>
- Telefon: <a href="tel:+493069201075">+49 30 69201075</a><br />
- </p>
- <h3>Inhaltlich verantwortlich</h3>
- <p>
- <small>Verantwortlich für den Inhalt nach § 55 Abs. 2 RStV bzw. §5 TMG sowie beauftragt mit dem Jugendschutz gemäß TMG, JMStV sowie GjSM:</small><br />
- <strong>Sandro Knauß</strong>, Anschrift wie oben, <a href="mailto:iro@netzguerilla.net">iro@netzguerilla.net</a>
- </p>
- </div>
- </body>
-</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 @@
<body>
<title>Iro</title>
<div class="item">
+ <h3>What it is all about?</h3>
<p>
- 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 <a href="http://castorticker.de">castorticker.de</a>. 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.
</p>
</div>
- </body>
+ <div class="item" id="backends">
+ <h3>Supported Backends</h3>
+ <p>
+ A backend is a provider, that actually sends the message.
+ <ul>
+ <li><b>smtp</b> with TLS and SSL</li>
+ <li><b><a href="http://smstrade.de">smstrade</a></b> all diffrent routes are selectable</li>
+ <li><b><a href="http://sipgate.de">sipgate</a></b> fax and sms</li>
+ </ul>
+ </p>
+
+ </div>
+ <div class="item" id="installation">
+ <h3>Installation</h3>
+ <p>
+ Just download <a href="files/${code}">${code}</a> see also installtion instruction under <a href="dev/install.html">Installing Iro</a>.
+ </p>
+
+ <h3>Documentation</h3>
+ <p>
+ If you want just use iro see <a href="current.html">API Documentation</a>.
+ For extending iro see the <a href="dev/index.html">developer documenation</a>.
+ </p>
+ </div>
+ <div class="item" id="files">
+ <h3>Files</h3>
+ <p>
+ <ul>
+ <li py:for="f in files" value="${f.version}">${f.version} <a href="files/${f.name}">${f.name}</a></li>
+ </ul>
+ You can also get the code from our <a href="https://netzguerilla.net/iro/hg" >repository</a>, <a href="http://bitbucket.com/hefee/iro" >bitbucket</a>, or <a href="http://github.com/netzguerilla/iro" >github</a>.
+ </p>
+ </div>
+ <div class="item" id="features">
+ <h3>Planned features for 1.0</h3>
+ <p>
+ <ul>
+ <li>admin interface for creating and modifying users</li>
+ <li>heartbeat</li>
+ </ul>
+ For any further ideas, bugs or anything - send at <a href="mailto:iro@netzgerilla.net">iro@netzgerilla.net</a>.
+ </p>
+ </div>
+</body>
</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 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
-"http://www.w3.org/TR/html4/strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- xmlns:py="http://genshi.edgewall.org/">
- <xi:include href="layout.tmpl" />
- <head>
- <title>api docs new</title>
- </head>
- <body>
- <title>geplante API Dokumentation</title>
- <div class="item">
- <p>
-
- </p>
- <ol>
- <li value="1">1. <a href="#api-intro">Einführung</a></li>
- <li value="2">2.
- <a href="#api-interfaces">Interfaces</a>
- <ol>
- <li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
- <li value="2.1">2.1 <a href="#interface-soap">SOAP</a></li>
- <li value="2.2">2.2 <a href="#interface-xml">XML</a></li>
- <li value="2.3">2.3 <a href="#interface-json">JSON</a></li>
- <li value="2.4">2.4 <a href="#interface-jsonp">JSONP</a></li>
- <li value="2.5">2.5 <a href="#interface-php">PHP</a></li>
- </ol>
- </li>
- <li value="3">3.
- <a href="#api-methods">Methoden</a>
- <ol>
- <li py:for="(key,method) in enumerate(new)" value="3.${key+1}">3.${key+1} <a href="#method-${method.name}">${method.title}</a></li>
- </ol>
- </li>
-
- </ol>
- </div>
- <div class="item" id="api-intro">
- <h3>Einführung</h3>
- <p>
- Die Iro API enthält Funktion, die für den Massenversand nützlich sind.
- </p>
- </div>
- <div class="item" id="api-interfaces">
- <h3>Interfaces</h3>
- <p>
-
- Die Iro API wird über verschiedene Interfaces bereit gestellt, die unterschiedlich angesprochen werden, aber das selbe tun.
- </p>
- <div class="item" id="interface-xmlrpc">
- <h4>XML-RPC</h4>
- <p>
- Interface-URI: <code>http://localhost:8000/xmlrpc</code>
- </p>
- <p>
- Die aufgerufene Methode wird dabei im <code><methodName /></code> übergeben.
- </p>
- </div>
- <div class="item" id="interface-soap">
- <h4>SOAP</h4>
- <p>
- Interface-URI: <code>http://localhost:8000/soap</code>
- </p>
- <p>
- Die aufgerufene Methode wird dabei im <code><methodName /></code> übergeben.
- </p>
- </div>
- <div class="item" id="interface-xml">
- <h4>XML</h4>
- <p>
- Interface-URI: <code>http://localhost:8000/xml/<em><methode></em></code>
- </p>
- <p>
- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
- </p>
- <p>
- Parameter können via HTTP per GET oder POST im Format <em>application/x-www-form-urlencoded</em> übergeben werden.
- </p>
- <p>
- Die Ausgabe erfolgt als XML Markup.
- </p>
- </div>
- <div class="item" id="interface-json">
- <h4>JSON</h4>
- <p>
- Interface-URI: <code>http://localhost:8000/json/<em><methode></em></code>
- </p>
- <p>
- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
- </p>
- <p>
- Parameter können via HTTP per GET oder POST im Format <em>application/x-www-form-urlencoded</em> oder JSON-Objekt übergeben werden.
- </p>
- <p>
- Die Ausgabe erfolgt als JSON-Objekt.
- </p>
- </div>
- <div class="item" id="interface-jsonp">
- <h4>JSONP</h4>
- <p>
- Interface-URI: <code>http://localhost:8000/jsonp/<em><methode></em>?callback=<callback></code>
- </p>
- <p>
- Die aufgerufene Methode wird im Pfad der Interface-URI übergeben.
- </p>
- <p>
- Der Name für die Callback-Methode wird als Parameter Callback übergeben.
- </p>
- <p>
- Parameter können via HTTP per GET im Format <em>application/x-www-form-urlencoded</em> übergeben werden.
- </p>
- <p>
- Die Ausgabe erfolgt als Javascript-Funktionsaufruf mit einem JSON-Objekt als Parameter.
- </p>
- </div>
- </div>
- <div class="item" id="api-methods">
- <h3>Methoden</h3>
- <div py:for="method in new" class="item" id="method-${method.name}">
- <h4>${method.title}</h4>
- <p><code>${method.name}${method.func_line}</code></p>
- <p py:content="method.description">
- Diese Methode at bis jetzt noch keine Beschreibung.
- </p>
- <h5>Parameter</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr py:for="arg in method.args">
- <td>${arg.name}</td>
- <td>${arg.typ}</td>
- <td>${arg.description}</td>
- </tr>
- </tbody>
- </table>
- <py:if test="method.rets">
- <h5>Ausgabe</h5>
- <table class="docs">
- <thead>
- <tr>
- <td>Parameter</td>
- <td>Typ</td>
- <td>Beschreibung</td>
- </tr>
- </thead>
- <tbody>
- <tr py:for="arg in method.rets">
- <td>${arg.name}</td>
- <td>${arg.typ}</td>
- <td>${arg.description}</td>
- </tr>
- </tbody>
- </table>
- </py:if>
- </div>
- </div>
- </body>
-</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 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:py="http://genshi.edgewall.org/">
+ <xi:include href="layout.tmpl" />
+ <head>
+ <title>API docs old</title>
+ </head>
+ <body>
+ <title>Alte API Dokumentation</title>
+ <div class="item">
+ <p>
+
+ </p>
+ <ol>
+ <li value="1">1. <a href="#api-intro">Einführung</a></li>
+ <li value="2">2.
+ <a href="#api-interfaces">Interfaces</a>
+ <ol>
+ <li value="2.1">2.1 <a href="#interface-xmlrpc">XML-RPC</a></li>
+ </ol>
+ </li>
+ <li value="3">3.
+ <a href="#api-methods">Methoden</a>
+ <ol>
+ <li py:for="(key,method) in enumerate(old)" value="3.${key+1}">3.${key+1} <a href="#method-${method.name}">${method.title}</a></li>
+ </ol>
+ </li>
+ </ol>
+ </div>
+ <div class="item" id="api-intro">
+ <h3>Einführung</h3>
+ <p>
+ Die Iro API enthält Funktion, die für den Massenversand nützlich sind.
+ </p>
+ </div>
+ <div class="item" id="api-interfaces">
+ <h3>Interfaces</h3>
+ <p>
+ Die Iro API stellt zur Zeit nur ein Interfaces bereit.
+ </p>
+ <div class="item" id="interface-xmlrpc">
+ <h4>XML-RPC</h4>
+ <p>
+ Interface-URI: <code>https://<em><benutzer></em>:<em><passwort></em>@localhost:8000</code>
+ </p>
+ <p>
+ Die aufgerufene Methode wird dabei im <code><methodName /></code> übergeben.
+ </p>
+ </div>
+ </div>
+ <div class="item" id="api-methods">
+ <h3>Methoden</h3>
+ <div py:for="method in old" class="item" id="method-${method.name}">
+ <h4>${method.title}</h4>
+ <p><code>${method.name}${method.func_line}</code></p>
+ <p py:content="method.description">
+ Diese Methode at bis jetzt noch keine Beschreibung.
+ </p>
+ <h5>Parameter</h5>
+ <table class="docs">
+ <thead>
+ <tr>
+ <td>Parameter</td>
+ <td>Typ</td>
+ <td>Beschreibung</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr py:for="arg in method.args">
+ <td>${arg.name}</td>
+ <td>${arg.typ}</td>
+ <td>${arg.description}</td>
+ </tr>
+ </tbody>
+ </table>
+ <py:if test="method.rets">
+ <h5>Ausgabe</h5>
+ <table class="docs">
+ <thead>
+ <tr>
+ <td>Parameter</td>
+ <td>Typ</td>
+ <td>Beschreibung</td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr py:for="arg in method.rets">
+ <td>${arg.name}</td>
+ <td>${arg.typ}</td>
+ <td>${arg.description}</td>
+ </tr>
+ </tbody>
+ </table>
+ </py:if>
+ </div>
+ </div>
+ </body>
+</html>
--- /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 <iro@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()
--- /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 <iro@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()
--- 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 =
+
--- 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 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE UserProject SYSTEM "UserProject-4.0.dtd">
-<!-- eric4 user project file for project iro -->
-<!-- Saved: 2009-10-10, 13:26:41 -->
-<!-- Copyright (C) 2009 Sandro Knauß, bugs@sandroknauss.de -->
-<UserProject version="4.0">
-</UserProject>
--- 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 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE UserProject SYSTEM "UserProject-4.0.dtd">
-<!-- eric4 user project file for project iro -->
-<!-- Saved: 2009-11-24, 02:01:06 -->
-<!-- Copyright (C) 2009 Sandro Knauß, bugs@sandroknauss.de -->
-<UserProject version="4.0">
-</UserProject>
\ No newline at end of file
--- 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 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Tasks SYSTEM "Tasks-4.2.dtd">
-<!-- eric4 tasks file for project iro -->
-<!-- Saved: 2009-11-24, 02:01:07 -->
-<Tasks version="4.2">
-</Tasks>
\ No newline at end of file
--- 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
--- 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 <iro@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'
--- 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()
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-from sipgate import sipgate
-from geonet import geonet
-from FAX_de import FAX_de
-from smtp import SMTP
-from smstrade import smstrade
-import content
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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)
--- 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)
-
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-
-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
--- 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 <http://www.gnu.org/licenses/>.
-
-
-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
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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
-
-
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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<land>[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)
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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()
-
-
--- /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 <iro@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"""
--- /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 <iro@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.
--- /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 <iro@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"]
--- /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 <iro@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)
--- /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 <iro@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
--- 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
-
-
--- 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()
-
--- /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 <iro@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
--- /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 <iro@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
+
+
--- /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 <iro@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
+
--- 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
--- 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 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Project SYSTEM "Project-4.6.dtd">
-<!-- eric4 project file for project iro -->
-<!-- Saved: 2009-11-23, 00:15:34 -->
-<!-- Copyright (C) 2009 Sandro Knauß, bugs@sandroknauss.de -->
-<Project version="4.6">
- <Language></Language>
- <ProgLanguage mixed="0">Python</ProgLanguage>
- <ProjectType>Console</ProjectType>
- <Description>Ein Daemon zum Senden von Massensms, -faxen und emails</Description>
- <Version>0.1</Version>
- <Author>Sandro Knauß</Author>
- <Email>bugs@sandroknauss.de</Email>
- <Sources>
- <Source>iro.py</Source>
- <Source>__init__.py</Source>
- <Source>xmlrpc/SecureXMLRPCServer.py</Source>
- <Source>xmlrpc/__init__.py</Source>
- <Source>xmlrpc/SecureAuthentificateXMLRPCServer.py</Source>
- <Source>xmlrpc/AuthentificateXMLRPCServer.py</Source>
- <Source>xmlrpc/SecureUserdbXMLRPCServer.py</Source>
- <Source>worker.py</Source>
- <Source>user.py</Source>
- <Source>test.py</Source>
- <Source>providerlist.py</Source>
- <Source>joblist.py</Source>
- <Source>job.py</Source>
- <Source>anbieter/smtp.py</Source>
- <Source>anbieter/geonet.py</Source>
- <Source>anbieter/content.py</Source>
- <Source>anbieter/__init__.py</Source>
- <Source>anbieter/telnumber.py</Source>
- <Source>anbieter/test.py</Source>
- <Source>anbieter/FAX_de.py</Source>
- <Source>anbieter/sipgate.py</Source>
- <Source>anbieter/gsm0338.py</Source>
- <Source>anbieter/anbieter.py</Source>
- <Source>anbieter/smstrade.py</Source>
- </Sources>
- <Forms>
- </Forms>
- <Translations>
- </Translations>
- <Resources>
- </Resources>
- <Interfaces>
- </Interfaces>
- <Others>
- <Other>iro.conf.inst</Other>
- <Other>iro.conf</Other>
- </Others>
- <MainScript>iro.py</MainScript>
- <Vcs>
- <VcsType>None</VcsType>
- <VcsOptions>
- <dict>
- <key>
- <string>add</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>checkout</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>commit</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>diff</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>export</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>global</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>history</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>log</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>remove</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>status</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>tag</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- <key>
- <string>update</string>
- </key>
- <value>
- <list>
- <string></string>
- </list>
- </value>
- </dict>
- </VcsOptions>
- <VcsOtherData>
- <dict>
- <key>
- <string>standardLayout</string>
- </key>
- <value>
- <bool>True</bool>
- </value>
- </dict>
- </VcsOtherData>
- </Vcs>
- <FiletypeAssociations>
- <FiletypeAssociation pattern="*.pyw" type="SOURCES" />
- <FiletypeAssociation pattern="*.idl" type="INTERFACES" />
- <FiletypeAssociation pattern="*.py" type="SOURCES" />
- <FiletypeAssociation pattern="*.ptl" type="SOURCES" />
- </FiletypeAssociations>
- <Documentation>
- <DocumentationParams>
- <dict>
- <key>
- <string>ERIC4API</string>
- </key>
- <value>
- <dict>
- <key>
- <string>basePackage</string>
- </key>
- <value>
- <unicode>iro</unicode>
- </value>
- <key>
- <string>ignoreFilePatterns</string>
- </key>
- <value>
- <list>
- <unicode></unicode>
- </list>
- </value>
- <key>
- <string>includePrivate</string>
- </key>
- <value>
- <bool>True</bool>
- </value>
- <key>
- <string>languages</string>
- </key>
- <value>
- <list>
- <unicode>Python</unicode>
- </list>
- </value>
- <key>
- <string>outputFile</string>
- </key>
- <value>
- <unicode>iro.api</unicode>
- </value>
- <key>
- <string>useRecursion</string>
- </key>
- <value>
- <bool>True</bool>
- </value>
- </dict>
- </value>
- </dict>
- </DocumentationParams>
- </Documentation>
-</Project>
\ No newline at end of file
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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 <iro@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
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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)
-
-
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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()
--- /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 <iro@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)
--- 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
-
--- 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
--- /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 <iro@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
--- /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 <iro@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
+
+
--- /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 <iro@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
--- /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 <iro@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."""
--- /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 <iro@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: ``<Mail(subject, body, frm)>``
+ """
+ return "<Mail(%s, %s, %s)>"%(self.subject,self.body,self.frm)
+
+__all__=["Message", "SMS", "Fax", "Mail"]
--- /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 <iro@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)
+
--- /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 <iro@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)
+
+
--- /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 <iro@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: ``<Job('id' ,'info', 'status', 'user_id')>``
+ """
+ try:
+ return "<Job('%s' ,'%s', '%s', '%s')>"%(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: ``<User('name', 'apikey')>``
+ """
+ try:
+ return "<User('%s','%s')>"%(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
--- /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 <iro@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
--- /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 <iro@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)
+
+
--- /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 <iro@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()
+
+
+
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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
--- /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 <iro@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
--- /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 <iro@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: ``<Offer(name, provider, route, typ)>`` """
+ return "<Offer(%s, %s, %s, %s)>"%(self.name,self.provider,self.route,self.typ)
+
--- /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 <iro@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`
+
+"""
--- /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 <iro@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
+
--- /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 <iro@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"]="<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
--- /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 <iro@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
+
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-
-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"]
--- /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 <iro@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<land>[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: `<Telnumber 0012345667>`
+ """
+ return "<Telnumber %s>"%str(self)
--- /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 <iro@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.
--- /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 <iro@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)
--- /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 <iro@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()
--- /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 <iro@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)
--- 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 <iro@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]
--- /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 <iro@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",""])
--- /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 <iro@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()
--- /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 <iro@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;g<h>i[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)
--- /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 <iro@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()})
+
+
--- /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 <iro@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)
--- /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 <iro@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()
--- /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 <iro@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()
--- /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 <iro@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")
+
--- /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 <iro@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
+
--- /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 <iro@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"
--- /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 <iro@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")
--- /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 <iro@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"
+
--- /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 <iro@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)
--- /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 <iro@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")
--- /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 <iro@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")
--- 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
-
--- /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 <iro@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)
--- /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 <iro@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),"<Telnumber 004934594103640>")
--- 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()
--- 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()
--- 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()
--- 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
-
--- /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 <iro@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
--- /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 <iro@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), ["<User('test','abcdef123456789')>",'abcde', True])
+ #self.assertEqual(self.__rpc2().status('abcdef123456789', '', 'true'), ["<User('test','abcdef123456789')>", '', True])
+ #self.assertEqual(self.__rpc2().status('abcdef123456789', '', 1), ["<User('test','abcdef123456789')>", '', 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)
--- /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 <iro@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), ["<User('test','abcdef123456789')>",'abcde', True])
+ #self.failUnlessEqual(self.__rpc2().status('abcdef123456789', '', 'true'), ["<User('test','abcdef123456789')>", '', True])
+ #self.failUnlessEqual(self.__rpc2().status('abcdef123456789', '', 1), ["<User('test','abcdef123456789')>", '', 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()
--- 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ß <bugs@sandroknauss.de>
-
-#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 <http://www.gnu.org/licenses/>.
-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
--- /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 <iro@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)<minlength:
+ raise ValidateException(field=field)
+ if maxlength 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
+
--- /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 <iro@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.
--- /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 <iro@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()
--- /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 <iro@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()
--- /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 <iro@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()
--- /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 <iro@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
--- 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))
-
--- 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()
-
-
--- 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()
-
-
--- 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
--- 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()
-
--- 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
--- 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 <iro@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()
--- /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 <iro@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 = '<<TABLE CELLSPACING="0" CELLPADDING="1" BORDER="0" CELLBORDER="%d" BALIGN="LEFT"><TR><TD><FONT POINT-SIZE="10">%s</FONT></TD></TR>' % (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 += '<TR><TD ALIGN="LEFT">%s</TD></TR>' % '<BR ALIGN="LEFT"/>'.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 += '<TR><TD ALIGN="LEFT">%s</TD></TR>' % '<BR ALIGN="LEFT"/>'.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+= '</TABLE>>'
+ 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 BORDER="1" CELLBORDER="0" CELLSPACING="0"><TR><TD ALIGN="CENTER">%s</TD></TR><TR><TD BORDER="1" CELLPADDING="0"></TD></TR>' % table.name
+
+ html += ''.join('<TR><TD ALIGN="LEFT" PORT="%s">%s</TD></TR>' % (col.name, format_col_str(col)) for col in table.columns)
+ html += '</TABLE>>'
+ 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'))
--- /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 <iro@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()
--- /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
--- /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
--- /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
--- /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;
+
+ }
+
--- /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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.26.3 (20100126.1600)
+ -->
+<!-- Title: G Pages: 1 -->
+<svg width="468pt" height="126pt"
+ viewBox="0.00 0.00 468.00 125.74" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph1" class="graph" transform="scale(0.83274 0.83274) rotate(0) translate(4 147)">
+<title>G</title>
+<polygon fill="white" stroke="white" points="-4,5 -4,-147 559,-147 559,5 -4,5"/>
+<!-- message -->
+<g id="node1" class="node"><title>message</title>
+<polygon fill="none" stroke="black" points="8,-18.5 8,-133.5 112,-133.5 112,-18.5 8,-18.5"/>
+<text text-anchor="start" x="43.5" y="-124.367" font-family="Bitstream-Vera Sans" font-size="7.00">message</text>
+<polygon fill="none" stroke="black" points="9,-118 9,-120 111,-120 111,-118 9,-118"/>
+<text text-anchor="start" x="11" y="-110.867" font-family="Bitstream-Vera Sans" font-size="7.00">- id : INTEGER</text>
+<text text-anchor="start" x="11" y="-99.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- recipient : VARCHAR(100)</text>
+<text text-anchor="start" x="11" y="-88.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- isBilled : BOOLEAN</text>
+<text text-anchor="start" x="11" y="-77.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- date : DATETIME</text>
+<text text-anchor="start" x="11" y="-66.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- price : NUMERIC(8, 4)</text>
+<text text-anchor="start" x="11" y="-55.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- count : INTEGER</text>
+<text text-anchor="start" x="11" y="-44.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- exID : VARCHAR(100)</text>
+<text text-anchor="start" x="11" y="-33.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- job : VARCHAR(40)</text>
+<text text-anchor="start" x="11" y="-22.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- offer : VARCHAR(100)</text>
+</g>
+<!-- job -->
+<g id="node3" class="node"><title>job</title>
+<polygon fill="none" stroke="black" points="164.5,-79 164.5,-139 251.5,-139 251.5,-79 164.5,-79"/>
+<text text-anchor="start" x="202.5" y="-130.367" font-family="Bitstream-Vera Sans" font-size="7.00">job</text>
+<polygon fill="none" stroke="black" points="166,-124 166,-126 251,-126 251,-124 166,-124"/>
+<text text-anchor="start" x="168" y="-116.867" font-family="Bitstream-Vera Sans" font-size="7.00">- id : INTEGER</text>
+<text text-anchor="start" x="168" y="-105.867" font-family="Bitstream-Vera Sans" font-size="7.00">- info : VARCHAR(100)</text>
+<text text-anchor="start" x="168" y="-94.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- status : VARCHAR(7)</text>
+<text text-anchor="start" x="168" y="-83.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- user : VARCHAR(100)</text>
+</g>
+<!-- message->job -->
+<g id="edge2" class="edge"><title>message->job</title>
+<path fill="none" stroke="black" d="M120.197,-89.4224C129.482,-91.4926 139.067,-93.6298 148.34,-95.6974"/>
+<ellipse fill="none" stroke="black" cx="152.488" cy="-96.6224" rx="4.00001" ry="4.00001"/>
+<text text-anchor="middle" x="146.627" y="-97.3455" font-family="Bitstream-Vera Sans" font-size="7.00">+ id</text>
+<text text-anchor="middle" x="129.963" y="-84.9699" font-family="Bitstream-Vera Sans" font-size="7.00">+ job</text>
+</g>
+<!-- offer -->
+<g id="node5" class="node"><title>offer</title>
+<polygon fill="none" stroke="black" points="304.5,-4 304.5,-64 405.5,-64 405.5,-4 304.5,-4"/>
+<text text-anchor="start" x="347" y="-55.3667" font-family="Bitstream-Vera Sans" font-size="7.00">offer</text>
+<polygon fill="none" stroke="black" points="306,-49 306,-51 405,-51 405,-49 306,-49"/>
+<text text-anchor="start" x="308" y="-41.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- name : VARCHAR(100)</text>
+<text text-anchor="start" x="308" y="-30.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- provider : VARCHAR(100)</text>
+<text text-anchor="start" x="308" y="-19.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- route : VARCHAR(100)</text>
+<text text-anchor="start" x="308" y="-8.86667" font-family="Bitstream-Vera Sans" font-size="7.00">- typ : VARCHAR(100)</text>
+</g>
+<!-- message->offer -->
+<g id="edge4" class="edge"><title>message->offer</title>
+<path fill="none" stroke="black" d="M120.053,-67.45C168.906,-60.4948 237.788,-50.6879 288.339,-43.4907"/>
+<ellipse fill="none" stroke="black" cx="292.329" cy="-42.9227" rx="4.00001" ry="4.00001"/>
+<text text-anchor="middle" x="287.912" y="-45.5203" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="128.43" y="-59.6886" font-family="Bitstream-Vera Sans" font-size="7.00">+ offer</text>
+</g>
+<!-- userright -->
+<g id="node2" class="node"><title>userright</title>
+<polygon fill="none" stroke="black" points="458,-49.5 458,-98.5 546,-98.5 546,-49.5 458,-49.5"/>
+<text text-anchor="start" x="485" y="-89.3667" font-family="Bitstream-Vera Sans" font-size="7.00">userright</text>
+<polygon fill="none" stroke="black" points="459,-83 459,-85 545,-85 545,-83 459,-83"/>
+<text text-anchor="start" x="461" y="-75.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- user : VARCHAR(100)</text>
+<text text-anchor="start" x="461" y="-64.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- offer : VARCHAR(100)</text>
+<text text-anchor="start" x="461" y="-53.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- default : INTEGER</text>
+</g>
+<!-- apiuser -->
+<g id="node4" class="node"><title>apiuser</title>
+<polygon fill="none" stroke="black" points="309,-89.5 309,-138.5 401,-138.5 401,-89.5 309,-89.5"/>
+<text text-anchor="start" x="341" y="-129.367" font-family="Bitstream-Vera Sans" font-size="7.00">apiuser</text>
+<polygon fill="none" stroke="black" points="310,-123 310,-125 400,-125 400,-123 310,-123"/>
+<text text-anchor="start" x="312" y="-115.867" font-family="Bitstream-Vera Sans" font-size="7.00">- name : VARCHAR(100)</text>
+<text text-anchor="start" x="312" y="-104.867" font-family="Bitstream-Vera Sans" font-size="7.00">- ng_kunde : INTEGER</text>
+<text text-anchor="start" x="312" y="-93.8667" font-family="Bitstream-Vera Sans" font-size="7.00">- apikey : VARCHAR(50)</text>
+</g>
+<!-- job->apiuser -->
+<g id="edge10" class="edge"><title>job->apiuser</title>
+<path fill="none" stroke="black" d="M259.827,-110.763C270.366,-111.121 281.558,-111.502 292.424,-111.872"/>
+<ellipse fill="none" stroke="black" cx="296.657" cy="-112.015" rx="4" ry="4"/>
+<text text-anchor="middle" x="291.453" y="-113.767" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="269.028" y="-104.547" font-family="Bitstream-Vera Sans" font-size="7.00">+ user</text>
+</g>
+<!-- apiuser->userright -->
+<g id="edge8" class="edge"><title>apiuser->userright</title>
+<path fill="none" stroke="black" d="M409.32,-99.2191C422.569,-95.6138 436.713,-91.7653 449.858,-88.1883"/>
+<text text-anchor="middle" x="442.223" y="-92.3458" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="416.955" y="-90.4616" font-family="Bitstream-Vera Sans" font-size="7.00">+ user</text>
+</g>
+<!-- offer->userright -->
+<g id="edge6" class="edge"><title>offer->userright</title>
+<path fill="none" stroke="black" d="M413.521,-49.924C425.531,-53.192 438.098,-56.6116 449.865,-59.8135"/>
+<text text-anchor="middle" x="440.01" y="-59.2118" font-family="Bitstream-Vera Sans" font-size="7.00">+ name</text>
+<text text-anchor="middle" x="423.375" y="-45.9257" font-family="Bitstream-Vera Sans" font-size="7.00">+ offer</text>
+</g>
+</g>
+</svg>
Binary file web/images/favicon.png has changed
Binary file web/images/netzguerilla-3.png has changed