diff -r eb04ac3a8327 -r 3f4bdea2abbf iro/model/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/iro/model/schema.py Thu Sep 27 17:15:46 2012 +0200 @@ -0,0 +1,302 @@ +# Copyright (c) 2012 netzguerilla.net +# +# This file is part of Iro. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of +# this software and associated documentation files (the "Software"), to deal in +# the Software without restriction, including without limitation the rights to use, +# copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the +# #Software, and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# -*- coding: utf-8 -*- + +from sqlalchemy import Column, Integer, String, Sequence, Boolean, DateTime, Numeric, Enum +from sqlalchemy.ext.declarative import declarative_base + +#relationship +from sqlalchemy import ForeignKey +from sqlalchemy.orm import relationship, backref, object_session + +from sqlalchemy import and_ +from sqlalchemy.orm.exc import DetachedInstanceError +import sqlalchemy.sql.functions as func + +import job +from ..error import JobNotFound + +Base = declarative_base() + +class Userright(Base): + """Allowed offers for one user. Default routes are sorted by **default** value.""" + __tablename__ = 'userright' + user_name = Column('user', String(100), ForeignKey('apiuser.name'), primary_key=True) + """username""" + offer_name = Column('offer', String(100), ForeignKey('offer.name'), primary_key=True) + """offername""" + default = Column(Integer) + """sorting defaults routes with this value""" + offer = relationship("Offer") + """connected :class:`Offer` object""" + user = relationship("User") + """connected :class:`User` object""" + + def __init__(self, offer, default=None): + """Constructor of Userright class. + + :param `Offer` offer: a offer object + :param integer default: default value + """ + + self.offer = offer + self.default = default + + @property + def bill(self): + """returns a list of unbilled messages grouped by Job.info""" + query = [ func.count(Message.id).label('anz'), # anz of messages + func.sum(Message.price).label("price"), # price of the messages + Job.info.label('info'), # info tag + ] + + filters = [ Message.isBilled==False, # only unbilled messages + Job.user==self.user, # only jobs connected to user + Message.offer==self.offer, # only messages in the right offer + Message.job_id==Job.id, # join Message and Job + ] + return object_session(self).query(*query).filter(and_(*filters)).group_by(Job.info) + +class Offer(Base): + """All possible Offers over a Message can be sended. **provider**, **typ** and **route** are used to get the data form configuration file.""" + __tablename__ = "offer" + name = Column(String(100), primary_key=True) + """name of the offer""" + provider = Column(String(100)) + """provider name""" + route = Column(String(100)) + """route of the provider""" + typ = Column(String(100)) + """typ of message""" + + def __init__(self, **kwargs): + Base.__init__(self,**kwargs) + + @classmethod + def get(cls, session, provider, route, typ): + """returns a Offer object.""" + return session.query(cls).filter(and_(cls.provider==provider, cls.route==route, cls.typ==typ)).first() + + @classmethod + def routes(cls, session, typ): + """returns a query object of all possible offers. + + :param string typ: get all offers that support this typ + """ + return session.query(cls).filter_by(typ=typ) + + @classmethod + def typs(cls, session): + """returns a list of all possible types. + """ + return session.query(cls.typ).distinct() + + @classmethod + def providers(cls, session, typ): + """returns a list of all possible providers. + + :param string typ: get all providers that support this typ + """ + return session.query(cls.provider).filter_by(typ=typ).distinct() + +class Message(Base): + """A message that has created costs. + + **isBilled** is False since the bill is paid. + """ + __tablename__ = "message" + id = Column(Integer, Sequence('message_id_seq'), primary_key=True) + """primary key of the message""" + + recipient = Column(String(100)) + """string representation of the recipient""" + + isBilled = Column(Boolean) + """is bill paid?""" + + date = Column(DateTime) + """date of sending the message""" + + price = Column(Numeric(8,4)) + """price of sending the message""" + + count = Column(Integer) + """Count of sended messages""" + exID = Column(String(100)) + """external API id """ + + job_id = Column("job", String(40), ForeignKey('job.id')) + """id of the connected job""" + job = relationship("Job", backref=backref('messages')) + """connected :class:`Job` object""" + + offer_id = Column("offer",String(100), ForeignKey('offer.name')) + """sended message over this offer woth ithe offer.name""" + + offer = relationship("Offer", backref=backref('messages')) + """connected :class:`Offer` object""" + + def __init__(self, **kwargs): + Base.__init__(self,**kwargs) + + +class Job(Base): + """A complete Job. + + - **status** show the status of the job (``init``, ``started``, ``sending``, ``sended`` or ``error``). + - **info** is used to make it possible to create different billing groups for user. + """ + __tablename__ = "job" + id = Column(Integer, Sequence('job_id_seq'), primary_key=True) + """job id""" + info = Column(String(100)) + """job info, for billing porpuse""" + status = Column(Enum("init","started","sending","sended","error")) + """status of a job""" + user_id = Column("user", String(100), ForeignKey('apiuser.name')) + """connected user id""" + + user = relationship("User", backref=backref('jobs')) + """connected :class:`User` object""" + + def __init__(self, **kwargs): + """ + .. automethod:: __repr__ + """ + Base.__init__(self,**kwargs) + + @property + def extend(self): + """returns the connected :class:`iro.model.job.ExJob`""" + return job.exJobs[self.id] + + + def __repr__(self): + """string representation of the Job class. + + :return: ```` + """ + try: + return ""%(self.id,self.info, self.status, self.user_id) + except DetachedInstanceError: + return Base.__repr__(self) + + @classmethod + def get(cls, session, id): + """returns a job object from a given id""" + return session.query(cls).filter_by(id=id).first() + +class User(Base): + """An user in iro.""" + __tablename__ = "apiuser" + + name = Column(String(100), primary_key=True) + """Username""" + + ng_kunde = Column(Integer) + """Connection to the netzguerilla userdatabase""" + + apikey = Column(String(50),unique=True) + """apikey only [0-9a-f]""" + + rights = relationship('Userright') + """all allowed offers to send with.""" + + def __init__(self, name, apikey): + """Constructor of User class. + + :param string name: username + :param string apikey: apikey for the user + + .. automethod:: __repr__ + """ + self.name=name + self.apikey=apikey + + def __repr__(self): + """string representation of the user class. + + :return: ```` + """ + try: + return ""%(self.name,self.apikey) + except DetachedInstanceError: + return Base.__repr__(self) + + def routes(self, typ, default = False): + """returns a query object to get all possible routes for a given typ + + :param string typ: the typ + :param boolean default: use only default routes + """ + filters=[User.name == self.name, + Offer.typ == typ, + ] + if default: + filters.append(Userright.default != None) + return object_session(self).query(Userright.offer_name).join(Offer,User).filter(and_(*filters)).order_by(Userright.default) + + def providers(self, typ, default = False): + """return a query object for all possible providers for a given typ + + :param string typ: the typ + :param boolean default: use only default routes + """ + filters=[User.name == self.name, + Offer.typ == typ, + ] + if default: + filters.append(Userright.default != None) + return object_session(self).query(Offer.provider).join(Userright,User).filter(and_(*filters)) + + def has_right(self, typ, offer_name = None, provider = None, route = None): + """if a user has the right to use a offer, provider e. al. (arguments are and connected). + + :param string typ: the typ + :param string offer_name: offer name + :param string provider: provider name + :param string route: a route name + :return: offer_name or None (not allwoed) + :raises: :class:`sqlalchemy.orm.exc.MultipleResultsFound` if not a single offer match""" + filters=[User.name == self.name, + Offer.typ == typ, + ] + if offer_name: + filters.append(Userright.offer_name==offer_name) + if provider: + filters.append(Offer.provider==provider) + if route: + filters.append(Offer.route==route) + + return object_session(self).query(Userright.offer_name).join(Offer,User).filter(and_(*filters)).scalar() + + def job(self, id): + """returns a job object. + + :param integer id: id of a Job + :return: :class:`Job` + :raises: :exc:`iro.error.JobNotFound` + """ + job = object_session(self).query(Job).join(User).filter(and_(User.name == self.name, Job.id==id)).first() + if job is None: + raise JobNotFound() + return job