iro/model/schema.py
changeset 302 3f4bdea2abbf
parent 294 0e75bd39767d
child 308 a891fdd0c1a9
equal deleted inserted replaced
90:eb04ac3a8327 302:3f4bdea2abbf
       
     1 # Copyright (c) 2012 netzguerilla.net <iro@netzguerilla.net>
       
     2 # 
       
     3 # This file is part of Iro.
       
     4 # 
       
     5 # Permission is hereby granted, free of charge, to any person obtaining a copy of
       
     6 # this software and associated documentation files (the "Software"), to deal in
       
     7 # the Software without restriction, including without limitation the rights to use,
       
     8 # copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
       
     9 # #Software, and to permit persons to whom the Software is furnished to do so,
       
    10 # subject to the following conditions:
       
    11 # 
       
    12 # The above copyright notice and this permission notice shall be included in
       
    13 # all copies or substantial portions of the Software.
       
    14 # 
       
    15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
       
    16 # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
       
    17 # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
       
    18 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
       
    19 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
       
    20 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
       
    21 
       
    22 # -*- coding: utf-8 -*- 
       
    23 
       
    24 from sqlalchemy import Column, Integer, String, Sequence, Boolean, DateTime, Numeric, Enum
       
    25 from sqlalchemy.ext.declarative import declarative_base
       
    26 
       
    27 #relationship
       
    28 from sqlalchemy import ForeignKey
       
    29 from sqlalchemy.orm import relationship, backref, object_session
       
    30 
       
    31 from sqlalchemy import and_
       
    32 from sqlalchemy.orm.exc import DetachedInstanceError 
       
    33 import sqlalchemy.sql.functions as func
       
    34 
       
    35 import job
       
    36 from ..error import JobNotFound
       
    37 
       
    38 Base = declarative_base()
       
    39 
       
    40 class Userright(Base):
       
    41     """Allowed offers for one user. Default routes are sorted by **default** value."""
       
    42     __tablename__ = 'userright'
       
    43     user_name = Column('user', String(100), ForeignKey('apiuser.name'), primary_key=True)
       
    44     """username"""
       
    45     offer_name = Column('offer', String(100), ForeignKey('offer.name'), primary_key=True)
       
    46     """offername"""
       
    47     default = Column(Integer)
       
    48     """sorting defaults routes with this value"""
       
    49     offer = relationship("Offer")
       
    50     """connected :class:`Offer` object"""
       
    51     user = relationship("User")
       
    52     """connected :class:`User` object"""
       
    53 
       
    54     def __init__(self, offer, default=None):
       
    55         """Constructor of Userright class.
       
    56 
       
    57         :param `Offer` offer: a offer object
       
    58         :param integer default: default value
       
    59         """
       
    60 
       
    61         self.offer = offer
       
    62         self.default = default
       
    63 
       
    64     @property
       
    65     def bill(self):
       
    66         """returns a list of unbilled messages grouped by Job.info"""
       
    67         query = [ func.count(Message.id).label('anz'),      # anz of messages
       
    68                   func.sum(Message.price).label("price"),   # price of the messages
       
    69                   Job.info.label('info'),                   # info tag
       
    70                   ]
       
    71 
       
    72         filters = [ Message.isBilled==False,                # only unbilled messages
       
    73                    Job.user==self.user,                     # only jobs connected to user
       
    74                    Message.offer==self.offer,               # only messages in the right offer
       
    75                    Message.job_id==Job.id,                  # join Message and Job
       
    76                 ]
       
    77         return object_session(self).query(*query).filter(and_(*filters)).group_by(Job.info)
       
    78 
       
    79 class Offer(Base):
       
    80     """All possible Offers over a Message can be sended. **provider**, **typ** and **route** are used to get the data form configuration file."""
       
    81     __tablename__ = "offer"
       
    82     name = Column(String(100), primary_key=True)
       
    83     """name of the offer"""
       
    84     provider = Column(String(100))
       
    85     """provider name"""
       
    86     route = Column(String(100))
       
    87     """route of the provider"""
       
    88     typ = Column(String(100))
       
    89     """typ of message"""
       
    90     
       
    91     def __init__(self, **kwargs):
       
    92         Base.__init__(self,**kwargs)
       
    93   
       
    94     @classmethod
       
    95     def get(cls, session, provider, route, typ):
       
    96         """returns a Offer object."""
       
    97         return session.query(cls).filter(and_(cls.provider==provider, cls.route==route, cls.typ==typ)).first()
       
    98 
       
    99     @classmethod
       
   100     def routes(cls, session, typ):
       
   101         """returns a query object of all possible offers.
       
   102         
       
   103         :param string typ: get all offers that support this typ
       
   104         """
       
   105         return session.query(cls).filter_by(typ=typ)
       
   106 
       
   107     @classmethod
       
   108     def typs(cls, session):
       
   109         """returns a list of all possible types.
       
   110         """
       
   111         return session.query(cls.typ).distinct()
       
   112 
       
   113     @classmethod
       
   114     def providers(cls, session, typ):
       
   115         """returns a list of all possible providers.
       
   116         
       
   117         :param string typ: get all providers that support this typ
       
   118         """
       
   119         return session.query(cls.provider).filter_by(typ=typ).distinct()
       
   120 
       
   121 class Message(Base):
       
   122     """A message that has created costs.
       
   123     
       
   124     **isBilled** is False since the bill is paid.
       
   125     """
       
   126     __tablename__ = "message"
       
   127     id = Column(Integer, Sequence('message_id_seq'), primary_key=True)
       
   128     """primary key of the message"""
       
   129 
       
   130     recipient = Column(String(100))
       
   131     """string representation of the recipient"""
       
   132     
       
   133     isBilled = Column(Boolean)
       
   134     """is bill paid?"""
       
   135 
       
   136     date = Column(DateTime)
       
   137     """date of sending the message"""
       
   138 
       
   139     price = Column(Numeric(8,4))
       
   140     """price of sending the message"""
       
   141 
       
   142     count = Column(Integer)
       
   143     """Count of sended messages"""
       
   144     exID = Column(String(100))
       
   145     """external API id """
       
   146 
       
   147     job_id = Column("job", String(40), ForeignKey('job.id'))
       
   148     """id of the connected job"""
       
   149     job = relationship("Job", backref=backref('messages'))
       
   150     """connected :class:`Job` object"""
       
   151     
       
   152     offer_id = Column("offer",String(100), ForeignKey('offer.name'))
       
   153     """sended message over this offer woth ithe offer.name"""
       
   154 
       
   155     offer = relationship("Offer", backref=backref('messages'))
       
   156     """connected :class:`Offer` object"""
       
   157     
       
   158     def __init__(self, **kwargs):
       
   159         Base.__init__(self,**kwargs)
       
   160 
       
   161 
       
   162 class Job(Base):
       
   163     """A complete Job.
       
   164 
       
   165     - **status** show the status of the job (``init``, ``started``, ``sending``, ``sended`` or ``error``). 
       
   166     - **info** is used to make it possible to create different billing groups for user.
       
   167     """
       
   168     __tablename__ = "job"
       
   169     id = Column(Integer, Sequence('job_id_seq'), primary_key=True)
       
   170     """job id"""
       
   171     info = Column(String(100))
       
   172     """job info, for billing porpuse"""
       
   173     status = Column(Enum("init","started","sending","sended","error"))
       
   174     """status of a job"""
       
   175     user_id = Column("user", String(100), ForeignKey('apiuser.name'))
       
   176     """connected user id"""
       
   177 
       
   178     user = relationship("User", backref=backref('jobs'))
       
   179     """connected :class:`User` object"""
       
   180     
       
   181     def __init__(self, **kwargs):
       
   182         """
       
   183         .. automethod:: __repr__
       
   184         """
       
   185         Base.__init__(self,**kwargs)
       
   186 
       
   187     @property
       
   188     def extend(self):
       
   189         """returns the connected :class:`iro.model.job.ExJob`"""
       
   190         return job.exJobs[self.id]
       
   191 
       
   192 
       
   193     def __repr__(self):
       
   194         """string representation of the Job class.
       
   195 
       
   196         :return: ``<Job('id' ,'info', 'status', 'user_id')>``
       
   197         """
       
   198         try:
       
   199             return "<Job('%s' ,'%s', '%s', '%s')>"%(self.id,self.info, self.status, self.user_id)
       
   200         except DetachedInstanceError:
       
   201             return Base.__repr__(self)
       
   202 
       
   203     @classmethod
       
   204     def get(cls, session, id):
       
   205         """returns a job object from a given id"""
       
   206         return session.query(cls).filter_by(id=id).first()
       
   207 
       
   208 class User(Base):
       
   209     """An user in iro."""
       
   210     __tablename__ = "apiuser"
       
   211     
       
   212     name = Column(String(100), primary_key=True)
       
   213     """Username"""
       
   214     
       
   215     ng_kunde = Column(Integer)
       
   216     """Connection to the netzguerilla userdatabase"""
       
   217 
       
   218     apikey = Column(String(50),unique=True)
       
   219     """apikey only [0-9a-f]"""
       
   220     
       
   221     rights = relationship('Userright')
       
   222     """all allowed offers to send with."""
       
   223 
       
   224     def __init__(self, name, apikey):
       
   225         """Constructor of User class.
       
   226 
       
   227         :param string name: username
       
   228         :param string apikey: apikey for the user
       
   229 
       
   230         .. automethod:: __repr__
       
   231         """
       
   232         self.name=name
       
   233         self.apikey=apikey
       
   234 
       
   235     def __repr__(self):
       
   236         """string representation of the user class.
       
   237 
       
   238         :return: ``<User('name', 'apikey')>``
       
   239         """
       
   240         try:
       
   241             return "<User('%s','%s')>"%(self.name,self.apikey)
       
   242         except DetachedInstanceError:
       
   243             return Base.__repr__(self)
       
   244     
       
   245     def routes(self, typ, default = False):
       
   246         """returns a query object to get all possible routes for a given typ
       
   247 
       
   248         :param string typ: the typ
       
   249         :param boolean default: use only default routes
       
   250         """
       
   251         filters=[User.name == self.name,
       
   252                 Offer.typ == typ, 
       
   253                 ]
       
   254         if default:
       
   255             filters.append(Userright.default != None)
       
   256         return object_session(self).query(Userright.offer_name).join(Offer,User).filter(and_(*filters)).order_by(Userright.default)
       
   257    
       
   258     def providers(self, typ, default = False):
       
   259         """return a query object for all possible providers for a given typ
       
   260 
       
   261         :param string typ: the typ
       
   262         :param boolean default: use only default routes
       
   263         """
       
   264         filters=[User.name == self.name,
       
   265                 Offer.typ == typ, 
       
   266                 ]
       
   267         if default:
       
   268             filters.append(Userright.default != None)
       
   269         return object_session(self).query(Offer.provider).join(Userright,User).filter(and_(*filters))
       
   270 
       
   271     def has_right(self, typ, offer_name = None, provider = None, route = None):
       
   272         """if a user has the right to use a offer, provider e. al. (arguments are and connected).
       
   273         
       
   274         :param string typ: the typ
       
   275         :param string offer_name: offer name
       
   276         :param string provider: provider name
       
   277         :param string route: a route name
       
   278         :return: offer_name or  None (not allwoed)
       
   279         :raises: :class:`sqlalchemy.orm.exc.MultipleResultsFound` if not a single offer match"""
       
   280         filters=[User.name == self.name,
       
   281                 Offer.typ == typ, 
       
   282                 ]
       
   283         if offer_name:
       
   284             filters.append(Userright.offer_name==offer_name)
       
   285         if provider:
       
   286             filters.append(Offer.provider==provider)
       
   287         if route:
       
   288             filters.append(Offer.route==route)
       
   289 
       
   290         return object_session(self).query(Userright.offer_name).join(Offer,User).filter(and_(*filters)).scalar()
       
   291 
       
   292     def job(self, id):
       
   293         """returns a job object.
       
   294 
       
   295         :param integer id: id of a Job
       
   296         :return: :class:`Job`
       
   297         :raises: :exc:`iro.error.JobNotFound`
       
   298         """
       
   299         job = object_session(self).query(Job).join(User).filter(and_(User.name == self.name, Job.id==id)).first()
       
   300         if job is None:
       
   301             raise JobNotFound()
       
   302         return job