Package vita ::
Package modules ::
Package s3 ::
Module s3track
|
|
1
2
3 """ Simple Generic Location Tracking System
4
5 @author: Dominic König <dominic[at]aidiq.com>
6
7 @copyright: 2011 (c) Sahana Software Foundation
8 @license: MIT
9
10 Permission is hereby granted, free of charge, to any person
11 obtaining a copy of this software and associated documentation
12 files (the "Software"), to deal in the Software without
13 restriction, including without limitation the rights to use,
14 copy, modify, merge, publish, distribute, sublicense, and/or sell
15 copies of the Software, and to permit persons to whom the
16 Software is furnished to do so, subject to the following
17 conditions:
18
19 The above copyright notice and this permission notice shall be
20 included in all copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
24 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
26 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
27 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
29 OTHER DEALINGS IN THE SOFTWARE.
30
31 """
32
33 from gluon.dal import Table, Query, Set, Expression, Rows, Row
34 from datetime import datetime, timedelta
35
36 __all__ = ["S3Tracking"]
40 """
41 Trackable types instance(s)
42 """
43
44 UID = "uuid"
45
46 TRACK_ID = "track_id"
47 LOCATION_ID = "location_id"
48
49 LOCATION = "gis_location"
50 PRESENCE = "sit_presence"
51
52 - def __init__(self, db, trackable, record_id=None, uid=None):
53 """
54 Constructor:
55
56 @param db: the database (DAL)
57 @param trackable: the trackable object
58 @param record_id: the record ID(s) (if object is a table or tablename)
59 @param uid: the record UID(s) (if object is a table or tablename)
60 """
61
62 self.db = db
63 self.records = []
64
65 self.table = db.sit_trackable
66
67 if isinstance(trackable, (Table, str)):
68 if hasattr(trackable, "_tablename"):
69 table = trackable
70 tablename = table._tablename
71 else:
72 table = db[trackable]
73 tablename = trackable
74 fields = self.__get_fields(table)
75 if not fields:
76 raise SyntaxError("No trackable type: %s" % tablename)
77 query = (table._id > 0)
78 if uid is None:
79 if record_id is not None:
80 if isinstance(record_id, (list, tuple)):
81 query = (table._id.belongs(record_id))
82 else:
83 query = (table._id == record_id)
84 elif UID in table.fields:
85 if not isinstance(uid, (list, tuple)):
86 query = (table[UID].belongs(uid))
87 else:
88 query = (table[UID] == uid)
89 fields = [table[f] for f in fields]
90 rows = self.db(query).select(*fields)
91
92 elif isinstance(trackable, Row):
93 fields = self.__get_fields(trackable)
94 if not fields:
95 raise SyntaxError("Required fields not present in the row")
96 rows = Rows(records=[trackable], compact=False)
97
98 elif isinstance(trackable, Rows):
99 rows = [r for r in trackable if self.__get_fields(r)]
100 fail = len(trackable) - len(rows)
101 if fail:
102 raise SyntaxError("Required fields not present in %d of the rows" % fail)
103 rows = Rows(records=rows, compact=False)
104
105 elif isinstance(trackable, (Query, Expression)):
106 tablename = self.db._adapter.get_table(trackable)
107 table = self.db[tablename]
108 fields = self.__get_fields(table)
109 if not fields:
110 raise SyntaxError("No trackable type: %s" % tablename)
111 query = trackable
112 fields = [table[f] for f in fields]
113 rows = self.db(query).select(*fields)
114
115 elif isinstance(trackable, Set):
116 query = trackable.query
117 tablename = self.db._adapter.get_table(query)
118 table = self.db[tablename]
119 fields = self.__get_fields(table)
120 if not fields:
121 raise SyntaxError("No trackable type: %s" % tablename)
122 fields = [table[f] for f in fields]
123 rows = trackable.select(*fields)
124
125 else:
126 raise SyntaxError("Invalid parameter type %s" % type(trackable))
127
128 records = []
129 for r in rows:
130 if self.__super_entity(r):
131 table = self.db[r.instance_type]
132 fields = self.__get_fields(table, super_entity=False)
133 if not fields:
134 raise SyntaxError("No trackable type: %s" % table._tablename)
135 fields = [table[f] for f in fields]
136 query = table[self.UID] == r[self.UID]
137 row = self.db(query).select(limitby=(0, 1), *fields).first()
138 if row:
139 records.append(row)
140 else:
141 records.append(r)
142
143 self.records = Rows(records=records, compact=False)
144
145
146
147 @classmethod
149 """
150 Check whether a trackable is a super-entity
151
152 @param trackable: the trackable object
153 """
154
155 if hasattr(trackable, "fields"):
156 keys = trackable.fields
157 else:
158 keys = trackable
159
160 return "instance_type" in keys
161
162
163
164 @classmethod
166 """
167 Check a trackable for presence of required fields
168
169 @param: the trackable object
170 """
171
172 fields = []
173
174 if hasattr(trackable, "fields"):
175 keys = trackable.fields
176 else:
177 keys = trackable
178
179 try:
180 if super_entity and \
181 cls.__super_entity(trackable) and cls.UID in keys:
182 return ("instance_type", cls.UID)
183 if cls.LOCATION_ID in keys:
184 fields.append(cls.LOCATION_ID)
185 if cls.TRACK_ID in keys:
186 fields.append(cls.TRACK_ID)
187 return fields
188 elif hasattr(trackable, "update_record") or \
189 isinstance(trackable, Table):
190 return fields
191 except:
192 pass
193 return None
194
195
196
197 - def get_location(self, timestmp=None,
198 _fields=None,
199 _filter=None,
200 as_rows=False,
201 exclude=[]):
202 """
203 Get the current location of the instance(s) (at the given time)
204
205 @param timestmp: last datetime for presence (defaults to current time)
206 @param _fields: fields to retrieve from the location records (None for ALL)
207 @param _filter: filter for the locations
208 @param as_rows: return the result as Rows object
209 @param exclude: interlocks to break at (avoids circular check-ins)
210
211 @returns: a location record, or a list of location records (if multiple)
212 """
213
214 ptable = self.db[self.PRESENCE]
215 ltable = self.db[self.LOCATION]
216
217 if timestmp is None:
218 timestmp = datetime.utcnow()
219
220 locations = []
221 for r in self.records:
222 location = None
223 if self.TRACK_ID in r:
224 query = ((ptable.deleted == False) &
225 (ptable[self.TRACK_ID] == r[self.TRACK_ID]) &
226 (ptable.timestmp <= timestmp))
227 presence = self.db(query).select(orderby=~ptable.timestmp,
228 limitby=(0, 1)).first()
229 if presence:
230 if presence.interlock:
231 exclude = [r[self.TRACK_ID]] + exclude
232 tablename, record = presence.interlock.split(",", 1)
233 trackable = S3Trackable(self.db, tablename, record)
234 record = trackable.records.first()
235 if self.TRACK_ID not in record or \
236 record[self.TRACK_ID] not in exclude:
237 location = trackable.get_location(timestmp=timestmp,
238 exclude=exclude)
239 elif presence.location_id:
240 query = (ltable.id == presence.location_id)
241 if _filter is not None:
242 query = query & _filter
243 if _fields is None:
244 location = self.db(query).select(ltable.ALL,
245 limitby=(0, 1)).first()
246 else:
247 location = self.db(query).select(limitby=(0, 1),
248 *_fields).first()
249
250 if not location:
251 if len(self.records) > 1:
252 trackable = S3Trackable(self.db, r)
253 else:
254 trackable = self
255 location = trackable.get_base_location()
256
257 if location:
258 locations.append(location)
259
260 if as_rows:
261 return Rows(records=locations, compact=False)
262
263 if not locations:
264 return None
265 elif len(locations) == 1:
266 return locations[0]
267 else:
268 return locations
269
270
271
273 """
274 Set the current location of instance(s) (at the given time)
275
276 @param location: the location (as Row or record ID)
277 @param timestmp: the datetime of the presence (defaults to current time)
278
279 @returns: nothing
280 """
281
282 ptable = self.db[self.PRESENCE]
283
284 if timestmp is None:
285 timestmp = datetime.utcnow()
286
287 if isinstance(location, S3Trackable):
288 location = location.get_base_location()
289 if isinstance(location, Rows):
290 location = location.first()
291 if isinstance(location, Row):
292 if "location_id" in location:
293 location = location.location_id
294 else:
295 location = location.id
296
297 if not location:
298 return
299 else:
300 data = dict(location_id=location, timestmp=timestmp)
301
302 for r in self.records:
303 if self.TRACK_ID not in r:
304
305 if len(self.records) > 1:
306 trackable = S3Trackable(r)
307 else:
308 trackable = self
309 trackable.set_base_location(location)
310 elif r[self.TRACK_ID]:
311 data.update({self.TRACK_ID:r[self.TRACK_ID]})
312 ptable.insert(**data)
313 self.__update_timestamp(r[self.TRACK_ID], timestmp)
314
315
316
317 - def check_in(self, table, record, timestmp=None):
318 """
319 Bind the presence of the instance(s) to another instance
320
321 @param table: table name of the other resource
322 @param record: record in the other resource (as Row or record ID)
323 @param timestmp: datetime of the check-in
324
325 @returns: nothing
326 """
327
328 ptable = self.db[self.PRESENCE]
329
330 if isinstance(table, str):
331 table = self.db[table]
332 fields = self.__get_fields(table)
333 if not fields:
334 raise SyntaxError("No location data in %s" % table._tablename)
335
336 interlock = None
337 if isinstance(record, Rows):
338 record = record.first()
339 if not isinstance(record, Row):
340 record = table[record]
341 if self.__super_entity(record):
342 table = self.db[record.instance_type]
343 fields = self.__get_fields(table, super_entity=False)
344 if not fields:
345 raise SyntaxError("No trackable type: %s" % table._tablename)
346 query = table[self.UID] == record[self.UID]
347 record = self.db(query).select(limitby=(0, 1)).first()
348 if record and table._id.name in record:
349 record = record[table._id.name]
350 if record:
351 interlock = "%s,%s" % (table, record)
352 else:
353 raise SyntaxError("No record specified for %s" % table._tablename)
354
355 if interlock:
356 if timestmp is None:
357 timestmp = datetime.utcnow()
358 data = dict(location_id=None,
359 timestmp=timestmp,
360 interlock=interlock)
361 q = ((ptable.deleted == False) & (ptable.timestmp <= timestmp))
362 for r in self.records:
363 if self.TRACK_ID not in r:
364
365 continue
366 query = q & (ptable[self.TRACK_ID] == r[self.TRACK_ID])
367 presence = self.db(query).select(orderby=~ptable.timestmp,
368 limitby=(0, 1)).first()
369 if presence and presence.interlock == interlock:
370
371 continue
372 data.update({self.TRACK_ID:r[self.TRACK_ID]})
373 ptable.insert(**data)
374 self.__update_timestamp(r[self.TRACK_ID], timestmp)
375
376
377
378 - def check_out(self, table=None, record=None, timestmp=None):
379 """
380 Make the last log entry before timestmp independent from
381 the referenced entity (if any)
382
383 @param timestmp: the date/time of the check-out, defaults
384 to current time
385
386 """
387
388 ptable = self.db[self.PRESENCE]
389
390 if timestmp is None:
391 timestmp = datetime.utcnow()
392
393 interlock = None
394 if table is not None:
395 if isinstance(table, str):
396 table = self.db[table]
397 if isinstance(record, Rows):
398 record = record.first()
399 if self.__super_entity(table):
400 if not isinstance(record, Row):
401 record = table[record]
402 table = self.db[record.instance_type]
403 fields = self.__get_fields(table, super_entity=False)
404 if not fields:
405 raise SyntaxError("No trackable type: %s" % table._tablename)
406 query = table[self.UID] == record[self.UID]
407 record = self.db(query).select(limitby=(0, 1)).first()
408 if isinstance(record, Row) and table._id.name in record:
409 record = record[table._id.name]
410 if record:
411 interlock = "%s,%s" % (table, record)
412 else:
413 return
414
415 q = ((ptable.deleted == False) & (ptable.timestmp <= timestmp))
416
417 for r in self.records:
418 if self.TRACK_ID not in r:
419
420 continue
421 query = q & (ptable[self.TRACK_ID] == r[self.TRACK_ID])
422 presence = self.db(query).select(orderby=~ptable.timestmp,
423 limitby=(0, 1)).first()
424 if presence and presence.interlock:
425 if interlock and presence.interlock != interlock:
426 continue
427 elif not interlock and table and \
428 not presence.interlock.startswith("%s" % table):
429 continue
430 tablename, record = presence.interlock.split(",", 1)
431 trackable = S3Trackable(self.db, tablename, record)
432 location = trackable.get_location(timestmp=timestmp)
433 if timestmp - presence.timestmp < timedelta(seconds=1):
434 timestmp = timestmp + timedelta(seconds=1)
435 data = dict(location_id=location,
436 timestmp=timestmp,
437 interlock=None)
438 data.update({self.TRACK_ID:r[self.TRACK_ID]})
439 ptable.insert(**data)
440 self.__update_timestamp(r[self.TRACK_ID], timestmp)
441
442
443
445 """
446 Remove a location from the presence log of the instance(s)
447
448 @todo: implement
449 """
450 raise NotImplementedError
451
452
453
458 """
459 Get the base location of the instance(s)
460
461 @param _fields: fields to retrieve from the location records (None for ALL)
462 @param _filter: filter for the locations
463 @param as_rows: return the result as Rows object
464
465 @returns: the base location(s) of the current instance
466 """
467
468 ltable = self.db[self.LOCATION]
469
470 locations = []
471 for r in self.records:
472 location = None
473 query = None
474 if self.LOCATION_ID in r:
475 query = (ltable.id == r[self.LOCATION_ID])
476 elif self.TRACK_ID in r:
477 q = (self.table[self.TRACK_ID] == r[self.TRACK_ID])
478 trackable = self.db(q).select(limitby=(0, 1)).first()
479 table = self.db[trackable.instance_type]
480 if self.LOCATION_ID in table.fields:
481 query = ((table[self.TRACK_ID] == r[self.TRACK_ID]) &
482 (table[self.LOCATION_ID] == ltable.id))
483 if query:
484 if _filter is not None:
485 query = query & _filter
486 if not _fields:
487 location = self.db(query).select(ltable.ALL,
488 limitby=(0, 1)).first()
489 else:
490 location = self.db(query).select(limitby=(0, 1),
491 *_fields).first()
492 if location:
493 locations.append(location)
494
495 if as_rows:
496 return Rows(records=locations, compact=False)
497
498 if not locations:
499 return None
500 elif len(locations) == 1:
501 return locations[0]
502 else:
503 return locations
504
505
506
508 """
509 Set the base location of the instance(s)
510
511 @param location: the location for the base location as Row or record ID
512
513 @returns: nothing
514
515 @note: instance tables without a location_id field will be ignored
516 """
517
518 if isinstance(location, S3Trackable):
519 location = location.get_base_location()
520 if isinstance(location, Rows):
521 location = location.first()
522 if isinstance(location, Row):
523 location = location.id
524
525 if not location:
526 return
527 else:
528 data = {self.LOCATION_ID:location}
529
530
531 for r in self.records:
532 if self.TRACK_ID in r:
533 continue
534 elif self.LOCATION_ID in r and hasattr(r, "update_record"):
535 r.update_record(**data)
536
537
538
539 track_ids = [r[self.TRACK_ID] for r in self.records
540 if self.TRACK_ID in r]
541 rows = self.db(self.table[self.TRACK_ID].belongs(track_ids)).select()
542 tables = []
543 for r in rows:
544 instance_type = r.instance_type
545 table = self.db[instance_type]
546 if instance_type not in tables and \
547 self.LOCATION_ID in table.fields:
548 tables.append(table)
549 else:
550
551 continue
552
553
554 for table in tables:
555 self.db(table[self.TRACK_ID].belongs(track_ids)).update(**data)
556
557
558 for r in self.records:
559 if self.LOCATION_ID in r:
560 r[self.LOCATION_ID] = location
561
562
563
565 """
566 Update the timestamp of a trackable
567
568 @param track_id: the trackable ID (super-entity key)
569 @param timestamp: the timestamp
570 """
571
572 if timestamp is None:
573 timestamp = datetime.utcnow()
574 if track_id:
575 trackable = self.table[track_id]
576 if trackable:
577 trackable.update_record(track_timestmp=timestamp)
578
582 """
583 S3 Tracking system, to be instantiated once as global "s3_trackable" object
584
585 """
586
588 """
589 Constructor
590
591 @param env: the global environment (globals())
592
593 """
594
595 self.db = env["db"]
596
597
598
599 - def __call__(self, trackable, record_id=None, uid=None):
600 """
601 Get a tracking interface for a record or set of records
602
603 @param trackable: a Row, Rows, Query, Expression, Set object or
604 a Table or a tablename
605 @param record_id: a record ID or a list/tuple of record IDs (together
606 with Table or tablename)
607 @param uid: a record UID or a list/tuple of record UIDs (together with
608 Table or tablename)
609
610 @returns: a S3Trackable instance for the specified record(s)
611
612 """
613
614 return S3Trackable(self.db, trackable,
615 record_id=record_id,
616 uid=uid)
617
618
619
620 - def get_all(self, entity,
621 location=None,
622 bbox=None,
623 timestmp=None):
624 """
625 Get all instances of the given entity at the given location and time
626
627 """
628 raise NotImplementedError
629
630
631
632 - def get_checked_in(self, table, record,
633 instance_type=None,
634 timestmp=None):
635 """
636 Get all trackables of the given type that are checked-in
637 to the given instance at the given time
638
639 """
640 raise NotImplementedError
641
642
643
644