# 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()
[docs]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
[docs] 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)
[docs]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
[docs] 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
[docs] 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
[docs] def typs(cls, session):
"""returns a list of all possible types.
"""
return session.query(cls.typ).distinct()
@classmethod
[docs] 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()
[docs]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)
[docs]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
[docs] def extend(self):
"""returns the connected :class:`iro.model.job.ExJob`"""
return job.exJobs[self.id]
[docs] 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
[docs] def get(cls, session, id):
"""returns a job object from a given id"""
return session.query(cls).filter_by(id=id).first()
[docs]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
[docs] 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)
[docs] 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)
[docs] 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))
[docs] 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()
[docs] 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