class Ticket(Persistent): id_code = None owner = None payment = None issue_date = None creation_date = None # __parent__ points at the pool if un-issued, else at owner guest_info = None change_enabled = False # Whether or not the guest details can be changed for this ticket tick_type = None addons = None checked_in = None checkin_data = None notes = None def __init__(self): coding = Coding() self.__name__ = self.id_code = coding.generateUniqueCode() self.owner = None self.payment = None self.issue_date = None self.creation_date = datetime.now() self.guest_info = None self.change_enabled = False self.tick_type = None self.addons = PersistentMapping() self.checked_in = False self.checkin_data = None self.notes = "" def csv(self, admin=False): address = self.owner.profile.address str_address = "None" if address != None: str_address = '"%s, %s, %s, %s, %s, %s"' % ( address.line_one, address.line_two, address.city, address.county, address.country, address.postal_code) addons_str = "None" if self.addons != None and len(self.addons) > 0: addon_names = [x.name for x in self.addons.values()] addons_str = " + ".join(addon_names) if admin: return ",".join([ self.id_code, self.payment.ref_code, self.owner.profile.title, self.owner.profile.fullname, self.owner.profile.email, self.guest_info.title, self.guest_info.fullname, self.guest_info.email, self.tick_type.name, addons_str, "%.02f" % (self.total_cost / 100.0), self.issue_date.strftime("%d/%m/%Y"), str_address ]) + "\n" else: return ",".join([ self.id_code, self.payment.ref_code, self.owner.profile.title, self.owner.profile.fullname, "HIDDEN", self.guest_info.title, self.guest_info.fullname, "HIDDEN", self.tick_type.name, addons_str, "%.02f" % (self.total_cost / 100.0), self.issue_date.strftime("%d/%m/%Y"), "HIDDEN" ]) + "\n" # Release all add-ons that this ticket holds def release_addons(self): if self.addons == None: self.addons = PersistentMapping() return to_remove = [] for addon_key in self.addons: addon = self.addons[addon_key] addon.allocated.remove(self) to_remove.append(addon_key) for addon_key in to_remove: self.addons.pop(addon_key, None) @property def addon_cost(self): total = 0 if self.addons != None: for addon in self.addons.values(): total += addon.cost return total @property def total_cost(self): return (self.tick_type.cost + self.addon_cost) @property def checkin_status(self): return (hasattr(self, "checked_in") and self.checked_in == True) @property def locked_down(self): locked = self.tick_type.locked_down if not locked and self.addons: for addon in self.addons.values(): if addon.locked_down: locked = True break return locked
def testTheWorld(self): # Test constructors u = PersistentMapping() u0 = PersistentMapping(l0) u1 = PersistentMapping(l1) u2 = PersistentMapping(l2) uu = PersistentMapping(u) uu0 = PersistentMapping(u0) uu1 = PersistentMapping(u1) uu2 = PersistentMapping(u2) class OtherMapping: def __init__(self, initmapping): self.__data = initmapping def items(self): return self.__data.items() v0 = PersistentMapping(OtherMapping(u0)) vv = PersistentMapping([(0, 0), (1, 1)]) # Test __repr__ eq = self.assertEqual eq(str(u0), str(l0), "str(u0) == str(l0)") eq(repr(u1), repr(l1), "repr(u1) == repr(l1)") eq( ` u2 `, ` l2 `, "`u2` == `l2`") # Test __cmp__ and __len__ def mycmp(a, b): r = cmp(a, b) if r < 0: return -1 if r > 0: return 1 return r all = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2] for a in all: for b in all: eq(mycmp(a, b), mycmp(len(a), len(b)), "mycmp(a, b) == mycmp(len(a), len(b))") # Test __getitem__ for i in range(len(u2)): eq(u2[i], i, "u2[i] == i") # Test get for i in range(len(u2)): eq(u2.get(i), i, "u2.get(i) == i") eq(u2.get(i, 5), i, "u2.get(i, 5) == i") for i in min(u2) - 1, max(u2) + 1: eq(u2.get(i), None, "u2.get(i) == None") eq(u2.get(i, 5), 5, "u2.get(i, 5) == 5") # Test __setitem__ uu2[0] = 0 uu2[1] = 100 uu2[2] = 200 # Test __delitem__ del uu2[1] del uu2[0] try: del uu2[0] except KeyError: pass else: raise TestFailed("uu2[0] shouldn't be deletable") # Test __contains__ for i in u2: self.failUnless(i in u2, "i in u2") for i in min(u2) - 1, max(u2) + 1: self.failUnless(i not in u2, "i not in u2") # Test update l = {"a": "b"} u = PersistentMapping(l) u.update(u2) for i in u: self.failUnless(i in l or i in u2, "i in l or i in u2") for i in l: self.failUnless(i in u, "i in u") for i in u2: self.failUnless(i in u, "i in u") # Test setdefault x = u2.setdefault(0, 5) eq(x, 0, "u2.setdefault(0, 5) == 0") x = u2.setdefault(5, 5) eq(x, 5, "u2.setdefault(5, 5) == 5") self.failUnless(5 in u2, "5 in u2") # Test pop x = u2.pop(1) eq(x, 1, "u2.pop(1) == 1") self.failUnless(1 not in u2, "1 not in u2") try: u2.pop(1) except KeyError: pass else: raise TestFailed("1 should not be poppable from u2") x = u2.pop(1, 7) eq(x, 7, "u2.pop(1, 7) == 7") # Test popitem items = u2.items() key, value = u2.popitem() self.failUnless((key, value) in items, "key, value in items") self.failUnless(key not in u2, "key not in u2") # Test clear u2.clear() eq(u2, {}, "u2 == {}")
class Game(Persistent): """ A Game aggregates the players and matches that are part of a competition. For example, a Game could be 'Football' or 'Hockey' """ def __init__(self, name): self.name = name # Player name -> Player self.players = PersistentMapping() # List of all matches for this game self.matches = PersistentList() # Whether to use average instead of sum-of-skill for this game self.use_average_team_skill = True def delete_match(self, match): if not match in self.matches: return self.matches.remove(match) players = match.teams[0] + match.teams[1] for p in players: if match in p.matches: p.matches.remove(match) self.recalculate_ratings() for p in list(self.players.keys()): if not self.players[p].matches: self.players.pop(p) def add_match(self, teams, score): players_a = [self.get_player(name) for name in teams[0]] players_b = [self.get_player(name) for name in teams[1]] # Add Match to the Database match = Match(self, [players_a, players_b], score) self.matches.append(match) self.update_player_ratings(match) match.update_rating_delta() def update_player_ratings(self, match): ratings_a = [p.get_rating() for p in match.teams[0]] ratings_b = [p.get_rating() for p in match.teams[1]] # Sort by score and get rank indices rank = list(zip(match.score, range(len(match.score)))) rank.sort(key=lambda r: r[0], reverse=True) rank_indices = list(zip(*rank))[1] # Check for Draw # TODO: make this generic for more than 2 teams if match.score[0] == match.score[1]: rank_indices = [0, 0] # Calculate new Ratings using trueskill algorithm new_ratings = trueskill.rate([ratings_a, ratings_b], ranks=rank_indices, weights=self.team_weights( ratings_a, ratings_b)) for r, p in zip(new_ratings[0], match.teams[0]): p.set_rating(r) p.add_match(match) for r, p in zip(new_ratings[1], match.teams[1]): p.set_rating(r) p.add_match(match) def recalculate_ratings(self): for player in self.players.values(): player.reset_rating() player.matches.clear() player.history.clear() for match in self.matches: match.init_stats() self.update_player_ratings(match) match.update_rating_delta() def get_player(self, name): if not name in self.players: self.players[name] = Player(name) return self.players[name] # Calcualte player weights for a match based on whether average or additive ratings # are used for this game def team_weights(self, team1, team2): ratings = [team1, team2] weights = [[1 for _ in r] for r in ratings] if self.use_average_team_skill: # Adjust weights by team-size. This effectively causes the TrueSkill algorithm to # look at the average instead of the sum of skills min_team_size = min(map(len, ratings)) weights = [[min_team_size / float(len(r)) for _ in r] for r in ratings] return weights def win_probability(self, team1, team2): """" Calculate the win probability of team1 over team2 given the skill ratings of all the players in the teams. """ def skill_sum(team, weights): return sum([v.skill() * w for (v, w) in zip(team, weights)]) weights = self.team_weights(team1, team2) delta_mu = skill_sum(team1, weights[0]) - skill_sum(team2, weights[1]) sum_sigma = sum((r.confidence() * w)**2 for ( r, w) in zip(itertools.chain(team1, team2), itertools.chain( *weights))) size = len(team1) + len(team2) denom = math.sqrt(size * (trueskill.BETA * trueskill.BETA) + sum_sigma) ts = trueskill.global_env() return ts.cdf(delta_mu / denom) def draw_probability(self, team1, team2): r1 = [p.get_rating() for p in team1] r2 = [p.get_rating() for p in team2] return trueskill.quality([r1, r2], weights=self.team_weights(team1, team2))
class Box(Persistent): implements(IDepositBox) def __init__(self, max_age=config.MAX_AGE, purge_days=config.PURGE_DAYS): self.data = PersistentMapping() self._last_purge = int(time.time()) self.max_age = max_age self.purge_days = purge_days def _generate_new_id(self): """Generate new id. """ new_id = id_generator() while new_id in self.data.keys(): new_id = id_generator() return new_id def put(self, value, token=None): """Put value in box, with optional token, and return generated id. Calling this method also does a purge once a day (well, when the last purge was at least 24 hours ago). The frequency can be controlled with the purge_days attribute. """ cutoff = int(time.time()) - (self.purge_days * 86400) if self._last_purge < cutoff: self.purge() if value is None: raise ValueError id = self._generate_new_id() self.data[id] = BoxItem(token, value, confirmed=False) return id def edit(self, secret, value, token=None): """Edit value in the box, when secret and optional token match. """ if value is None: raise ValueError stored = self.get(secret, token=token) if value == stored: # No change return self.data[secret] = BoxItem(token, value, confirmed=True) def get(self, secret, token=None): stored = self.data.get(secret) if stored is None: return None if stored.token != token: # raise Exception return None if not stored.confirmed: # Purge this item when it is expired: cutoff = int(time.time()) - self.max_age * 86400 if stored.timestamp < cutoff: del self.data[secret] return None if token: # When there is a token, the item must be confirmed # before we return the value. Main use case: email # confirmation. return None return stored.value def confirm(self, secret, token=None): """Confirm the item/token and return whether this succeeded or not. """ stored = self.data.get(secret) if stored is None: return None if stored.token != token: # raise Exception? return None if not stored.confirmed: # First check if the confirmation comes too late. cutoff = int(time.time()) - self.max_age * 86400 if stored.timestamp < cutoff: del self.data[secret] # Report back that we have failed, in case anyone # wants to know. return False stored.confirmed = True return True def pop(self, secret, token=None): stored = self.get(secret, token=token) if stored is None: return None self.data.pop(secret) return stored def get_all_confirmed(self): for key, stored in self.data.items(): if stored.confirmed: yield stored.value def purge(self): """Purge items that have expired. Confirmed items are not purged. """ cutoff = int(time.time()) - self.max_age * 86400 logger.info("Started purging data.") for key, stored in self.data.items(): if not stored.confirmed and stored.timestamp < cutoff: logger.info("Purged data with secret %r", key) del self.data[key] self._last_purge = int(time.time()) logger.info("Finished purging data.")