|
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 |