class AcarsPosition(model.Model): flight_id = model.StringProperty() message_id = model.StringProperty() system_time = model.DateTimeProperty(auto_now_add=True) remote_time = model.DateTimeProperty() message_type = model.StringProperty() flight_status = model.IntegerProperty() waypoint = model.StringProperty() lat_lon = model.GeoPtProperty() hdg = model.IntegerProperty() alt = model.IntegerProperty() vs = model.IntegerProperty() gs = model.IntegerProperty() ias = model.IntegerProperty() tas = model.IntegerProperty() fob = model.IntegerProperty() wnd = model.StringProperty() oat = model.IntegerProperty() tat = model.IntegerProperty() distance_from_dept = model.IntegerProperty() distance_total = model.IntegerProperty() pause_mode = model.IntegerProperty() airport = model.StringProperty() message = model.TextProperty() def add_position(self): self.put_async() if self.lat_lon: acars_flight = AcarsFlight.query( AcarsFlight.flight_id == self.flight_id).get() flight_position = FlightPosition(lat_lon=self.lat_lon, altitude=self.alt) acars_flight.flight_path.append(flight_position) acars_flight.put_async()
class EnkiModelPost(model.Model): author = model.IntegerProperty() body = model.TextProperty() thread = model.IntegerProperty() # thread the post belongs to time_created = model.DateTimeProperty(auto_now_add=True) time_updated = model.DateTimeProperty(auto_now=True)
class Fact(model.Model): author = model.StringProperty() poster_ip = model.StringProperty() posted_on = model.DateTimeProperty(auto_now_add=True) last_changed = model.DateTimeProperty(auto_now=True) text = model.TextProperty() language = model.StringProperty(default='en') # The language of the post # For selecting random instances random_index = model.ComputedProperty( lambda self: random.randint(0, sys.maxint)) # For the Elo rating system # see http://en.wikipedia.org/wiki/Elo_rating_system total_opponent_ratings = model.FloatProperty(default=0.) wins = model.IntegerProperty(default=0) losses = model.IntegerProperty(default=0) games = model.IntegerProperty(default=0) elo_rating = model.FloatProperty(default=400.) @property def k_factor(self): "Gives the correction (K) factor" if self.elo_rating <= 2100: return 32. elif self.elo_rating <= 2400: return 24. else: return 16. def expected_chance_against(self, fact): "Gives the expected odds of this fact winning a match with fact" return 1 / (1 + 10**((self.elo_rating + fact.elo_rating) / 400.)) @tasklets.tasklet def won_over(self, fact): """ Self won a match over another fact. Recalculates Elo ratings and saves them fact1.won_over(fact2) """ if self.key == fact.key: raise ValueError('A fact cannot compete with itself') previous_elo_rating = self.elo_rating # +------+-----+ # |Result|Score| # +------+-----+ # |Win |1 | # +------+-----+ # |Draw |0.5 | # +------+-----+ # |Loss |0 | # +------+-----+ self.elo_rating = self.elo_rating + self.k_factor * \ (1 - self.expected_chance_against(fact)) self.total_opponent_ratings += fact.elo_rating self.games += 1 self.wins += 1 f1 = self.put_async() fact.elo_rating = fact.elo_rating - fact.k_factor * \ (1 - fact.expected_chance_against(self)) # TODO: check if Elo ratings can become negative fact.elo_rating = 0 if fact.elo_rating < 0 else fact.elo_rating fact.total_opponent_ratings += previous_elo_rating fact.games += 1 fact.losses += 1 f2 = fact.put_async() yield f1, f2 def sync_won_over(self, fact): """ Self won a match over another fact. Recalculates Elo ratings and saves them, but do it synchronously fact1.sync_won_over(fact2) """ if self.key == fact.key: raise ValueError('A fact cannot compete with itself') previous_elo_rating = self.elo_rating self.elo_rating = self.elo_rating + self.k_factor * \ (1 - self.expected_chance_against(fact)) self.total_opponent_ratings += fact.elo_rating self.games += 1 self.wins += 1 self.put() fact.elo_rating = fact.elo_rating - fact.k_factor * \ (1 - fact.expected_chance_against(self)) # TODO: check if Elo ratings can become negative fact.elo_rating = 0 if fact.elo_rating < 0 else fact.elo_rating fact.total_opponent_ratings += previous_elo_rating fact.games += 1 fact.losses += 1 f2 = fact.put() @classmethod def random(cls, exclude=[]): "Returns a random instance" # TODO: do this asychronously f = None while not f: position = random.randint(1, sys.maxint) f = cls.query(cls.random_index >= position).get() if f and f.key in [e.key for e in exclude]: logging.error('got an excluded: ' + str(f)) f = None # Try again return f @classmethod def random_pair(cls): "Returns two random distinct facts" # TODO: do this asynchronously pos1 = random.randint(1, sys.maxint) f1 = cls.query(cls.random_index <= pos1).get() if not f1: f1 = cls.query(cls.random_index > pos1).get() pos2 = random.randint(1, sys.maxint) f2 = cls.query(cls.random_index <= pos1).get() if not f2: f2 = cls.query(cls.random_index > pos1).get() # f1 and f2 only must be resolved here if f1 == f2: f2 = cls.random(exclude=[f1]) return (f1, f2)
class EnkiModelPost(model.Model): #=== MODEL ==================================================================== author = model.IntegerProperty() body = model.TextProperty() thread = model.IntegerProperty() # thread the post belongs to sticky_order = model.IntegerProperty( default=0) # admin can set sticky_order > 0 to get threads 'stuck' time_created = model.DateTimeProperty(auto_now_add=True) time_updated = model.DateTimeProperty(auto_now=True) #=== CONSTANTS ================================================================ POST_LENGTH_MAX = 10000 POST_DELETED = '[deleted]' POSTS_PER_PAGE = 10 POST_DEFAULT = 1 POST_LAST = 'last' PAGES_BEFORE = 3 PAGES_AFTER = 3 ERROR_POST_LENGTH = -51 ERROR_POST_CREATION = -52 ERROR_POST_EDITION = -53 ERROR_POST_DELETION = -54 #=== QUERIES ================================================================== @classmethod def fetch_by_thread(cls, thread, limit, offset): return cls.query(cls.thread == thread).order( -cls.sticky_order, cls.time_created).fetch(limit=limit, offset=offset) @classmethod def fetch_by_author(cls, author): return cls.query(cls.author == author).order(-cls.time_created).fetch() @classmethod def fetch_keys_by_author(cls, author): return cls.query(cls.author == author).fetch(keys_only=True) #=== UTILITIES ================================================================ #--- DISPLAY DATA ------------------------------------------------------------- @classmethod def get_thread_data(cls, thread_selected, post_requested=POST_DEFAULT, post_count=POSTS_PER_PAGE): # get posts by thread forums_url = enki.libutil.get_local_url('forums') thread = EnkiModelThread.get_by_id(int(thread_selected)) thread_url = enki.libutil.get_local_url( 'thread', {'thread': str(thread_selected)}) forum = EnkiModelForum.get_by_id(thread.forum) forum_url = enki.libutil.get_local_url('forum', {'forum': str(forum.key.id())}) if post_requested == cls.POST_LAST: post_requested = thread.num_posts list = cls.fetch_by_thread(int(thread_selected), offset=(int(post_requested) - 1), limit=int(post_count)) if list: for i, item in enumerate(list): item.author_data = EnkiModelDisplayName.get_user_id_display_name_url( EnkiModelDisplayName.get_by_user_id_current(item.author)) item.post_page = enki.libutil.get_local_url( 'post', {'post': str(item.key.id())}) item.sticky = True if (item.sticky_order > 0) else False list[i] = item thread_data = threadData(forums_url, forum, forum_url, thread, thread_url, list, enki.libutil.markdown_escaped_extras, thread_selected) return thread_data @classmethod def get_post_data(cls, post_selected): # get a post forums_url = enki.libutil.get_local_url('forums') post = cls.get_by_id(int(post_selected)) sticky = True if (post.sticky_order > 0) else False post_page = enki.libutil.get_local_url('post', {'post': str(post.key.id())}) thread = EnkiModelThread.get_by_id(post.thread) thread_url = enki.libutil.get_local_url( 'thread', {'thread': str(thread.key.id())}) forum = EnkiModelForum.get_by_id(thread.forum) forum_url = enki.libutil.get_local_url('forum', {'forum': str(forum.key.id())}) author_data = EnkiModelDisplayName.get_user_id_display_name_url( EnkiModelDisplayName.get_by_user_id_current(post.author)) post_data = postData(forums_url, forum, forum_url, thread, thread_url, post, sticky, post_page, author_data, enki.libutil.markdown_escaped_extras) return post_data @classmethod def get_author_posts(cls, author_selected): # get posts by author to display on their profile. If the author hasn't set a display name, return nothing author_display_name = EnkiModelDisplayName.get_by_user_id_current( int(author_selected)) if author_display_name: forums_url = enki.libutil.get_local_url('forums') author_data = EnkiModelDisplayName.get_user_id_display_name_url( author_display_name) list = cls.fetch_by_author(int(author_selected)) if list: for i, item in enumerate(list): thread = EnkiModelThread.get_by_id(item.thread) forum = EnkiModelForum.get_by_id(thread.forum) item.thread_title = thread.title item.thread_url = enki.libutil.get_local_url( 'thread', {'thread': str(item.thread)}) item.forum_title = forum.title item.forum_group = forum.group item.forum_url = enki.libutil.get_local_url( 'forum', {'forum': str(forum.key.id())}) item.post_page = enki.libutil.get_local_url( 'post', {'post': str(item.key.id())}) item.sticky = True if (item.sticky_order > 0) else False list[i] = item author_posts_data = authorpostsData( forums_url, author_data, list, enki.libutil.markdown_escaped_extras) return author_posts_data @classmethod def get_page(cls, thread, post_requested, post_count): if post_requested == cls.POST_LAST: post_requested = thread.num_posts page = 1 if post_count == 1: page = post_requested elif post_count > 1 and post_count <= thread.num_posts: mod_req_post = post_requested % post_count if mod_req_post == 0: page = (post_requested - mod_req_post + 1 - post_count) / post_count + 1 else: page = (post_requested - mod_req_post + 1) / post_count + 1 return page @classmethod def get_first_post_on_page(cls, page, post_count): post = (page - 1) * post_count + 1 return post @classmethod def get_thread_pagination_data(cls, thread_selected, post_requested=POST_DEFAULT, post_count=POSTS_PER_PAGE): thread = EnkiModelThread.get_by_id(int(thread_selected)) post_requested = thread.num_posts if post_requested == cls.POST_LAST else int( post_requested) post_count = int(post_count) page_first = '' page_previous = '' page_current = [] page_next = '' page_last = '' page_list = [] # first page first_post_first_page = 1 if post_requested <> 1: page_first = enki.libutil.get_local_url( 'thread', { 'thread': thread_selected, 'start': str(first_post_first_page), 'count': str(post_count) }) # last page first_post_last_page = cls.get_first_post_on_page( cls.get_page(thread, thread.num_posts, post_count), post_count) if post_requested + post_count <= thread.num_posts: page_last = enki.libutil.get_local_url( 'thread', { 'thread': thread_selected, 'start': str(first_post_last_page), 'count': str(post_count) }) # current, previous and next pages first_post_previous_page = cls.get_first_post_on_page( cls.get_page(thread, post_requested, post_count), post_count) first_post_next_page = cls.get_first_post_on_page( cls.get_page(thread, (post_requested + post_count), post_count), post_count) if cls.get_first_post_on_page( cls.get_page(thread, post_requested, post_count), post_count) == post_requested: page = enki.libutil.get_local_url( 'thread', { 'thread': thread_selected, 'start': str(post_requested), 'count': str(post_count) }) page_current = [ page, cls.get_page(thread, post_requested, post_count) ] if page_current[1] > first_post_first_page: first_post_previous_page = cls.get_first_post_on_page( page_current[1] - 1, post_count) if page_current[1] < cls.get_page(thread, thread.num_posts, post_count): first_post_next_page = cls.get_first_post_on_page( page_current[1] + 1, post_count) if page_first: page_previous = enki.libutil.get_local_url( 'thread', { 'thread': thread_selected, 'start': str(first_post_previous_page), 'count': str(post_count) }) if page_last: page_next = enki.libutil.get_local_url( 'thread', { 'thread': thread_selected, 'start': str(first_post_next_page), 'count': str(post_count) }) # list of posts start = cls.get_page(thread, post_requested, post_count) - cls.PAGES_BEFORE while start < 1: start += 1 stop = cls.get_page(thread, post_requested, post_count) + cls.PAGES_AFTER while stop > cls.get_page(thread, thread.num_posts, post_count): stop -= 1 index = start while index <= stop: first_post = cls.get_first_post_on_page(index, post_count) page = enki.libutil.get_local_url( 'thread', { 'thread': thread_selected, 'start': str(first_post), 'count': str(post_count) }) page_list.append([page, index]) index += 1 result = pagination(page_first, page_previous, page_current, page_list, page_next, page_last) return result #--- ADD DATA ----------------------------------------------------------------- @classmethod def add_thread_and_post(cls, user_id, forum, thread_title, thread_sticky_order, post_body, post_sticky_order): result = enki.libutil.ENKILIB_OK if user_id and forum and thread_title and post_body: if len(thread_title ) <= EnkiModelThread.THREAD_TITLE_LENGTH_MAX and len( post_body) <= cls.POST_LENGTH_MAX: if EnkiModelDisplayName.get_by_user_id_current(user_id): thread = EnkiModelThread( author=user_id, forum=int(forum), title=thread_title, num_posts=1, sticky_order=int(thread_sticky_order)) thread.put() post = cls(author=user_id, body=post_body, thread=thread.key.id(), sticky_order=int(post_sticky_order)) post.put() forum_selected = ndb.Key(EnkiModelForum, int(forum)).get() forum_selected.num_posts += 1 forum_selected.num_threads += 1 forum_selected.put() else: result = cls.ERROR_POST_CREATION else: result = cls.ERROR_POST_LENGTH else: result = cls.ERROR_POST_CREATION return result @classmethod def add_post(cls, user_id, thread, post_body, sticky_order): result = enki.libutil.ENKILIB_OK if user_id and thread and post_body: if len(post_body) <= cls.POST_LENGTH_MAX: post = cls(author=user_id, thread=int(thread), body=post_body, sticky_order=int(sticky_order)) post.put() thread_selected = ndb.Key(EnkiModelThread, int(thread)).get() thread_selected.num_posts += 1 thread_selected.put() forum_selected = ndb.Key(EnkiModelForum, thread_selected.forum).get() forum_selected.num_posts += 1 forum_selected.put() else: result = cls.ERROR_POST_LENGTH else: result = cls.ERROR_POST_CREATION return result @classmethod def edit_post(cls, user_id, post_id, post_body, sticky_order): thread = '' result = enki.libutil.ENKILIB_OK if user_id and post_id and post_body: if len(post_body) <= cls.POST_LENGTH_MAX: post = cls.get_by_id(int(post_id)) if post: post.body = post_body post.sticky_order = int(sticky_order) thread = str(post.thread) post.put() else: result = cls.ERROR_POST_LENGTH else: result = cls.ERROR_POST_EDITION return result, thread @classmethod def delete_post(cls, user_id, post_id): thread = '' result = enki.libutil.ENKILIB_OK if user_id and post_id: post = cls.get_by_id(int(post_id)) if post: post.body = cls.POST_DELETED thread = str(post.thread) post.put() else: result = cls.ERROR_POST_DELETION return result, thread @classmethod def delete_user_posts(cls, user_id): result = enki.libutil.ENKILIB_OK posts = cls.fetch_keys_by_author(user_id) if posts: for post in posts: result = cls.delete_post(user_id, post.id()) if result == cls.ERROR_POST_DELETION: return result return result
class EnkiModelProductKey(model.Model): #=== MODEL ==================================================================== licence_key = model.StringProperty() # mandatory product_name = model.StringProperty() # mandatory purchaser_email = model.StringProperty() # mandatory purchaser_user_id = model.IntegerProperty( ) # if the purchaser is logged in shop_name = model.StringProperty( choices=['FastSpring', 'Emulator', 'Generator']) purchase_price = model.StringProperty() quantity = model.IntegerProperty() order_id = model.StringProperty() order_type = model.StringProperty( choices=['purchase', 'test', 'free-press', 'free-gift', 'free-promo']) info = model.TextProperty() activated_by_user = model.IntegerProperty(default=-1) time_created = model.DateTimeProperty(auto_now_add=True) time_updated = model.DateTimeProperty(auto_now=True) #=== CONSTANTS ================================================================ LICENCE_KEY_LENGTH = 15 LICENCE_KEY_DASHES_LENGTH = LICENCE_KEY_LENGTH + 2 # licence including two inserted dashes SEPARATOR_LICENCE_KEYS = '\n' #=== QUERIES ================================================================== @classmethod def exist_by_licence_key(cls, licence_key): count = cls.query( cls.licence_key == licence_key.replace('-', '')).count(1) return count > 0 @classmethod def get_by_licence_key(cls, licence_key): return cls.query(cls.licence_key == licence_key.replace('-', '')).get() @classmethod def exist_product_by_activator(cls, user_id, product_name): count = cls.query( ndb.AND(cls.activated_by_user == user_id, cls.product_name == product_name)).count(1) return count > 0 @classmethod def fetch_by_purchaser(cls, user_id): return cls.query(cls.purchaser_user_id == user_id).order( cls.product_name).fetch() @classmethod def exist_by_activator(cls, user_id): count = cls.query(cls.activated_by_user == user_id).count(1) return count > 0 @classmethod def count_by_activator(cls, user_id): return cls.query(cls.activated_by_user == user_id).count() @classmethod def fetch_by_activator(cls, user_id): return cls.query(cls.activated_by_user == user_id).order( cls.product_name).fetch() @classmethod def fetch_by_activator_products_list(cls, user_id, products_list): return cls.query( ndb.AND(cls.activated_by_user == user_id, cls.product_name.IN(products_list))).fetch() @classmethod def exist_by_purchaser_or_activator(cls, user_id): count = cls.query( ndb.OR(cls.purchaser_user_id == user_id, cls.activated_by_user == user_id)).count(1) return count > 0 @classmethod def exist_by_purchaser_not_activated(cls, user_id): count = cls.query( ndb.AND(cls.purchaser_user_id == user_id, cls.activated_by_user == -1)).count(1) return count > 0 @classmethod def count_by_purchaser_not_activated(cls, user_id): return cls.query( ndb.AND(cls.purchaser_user_id == user_id, cls.activated_by_user == -1)).count() @classmethod def count_by_shop_name_order_type_activated(cls, shop_name, order_type): return cls.query( ndb.AND(cls.shop_name == shop_name, cls.order_type == order_type, cls.activated_by_user >= 0)).count() @classmethod def count_by_shop_name_order_type_not_activated(cls, shop_name, order_type): return cls.query( ndb.AND(cls.shop_name == shop_name, cls.order_type == order_type, cls.activated_by_user == -1)).count() #=== UTILITIES ================================================================ @classmethod def generate_licence_key(cls): attempt = 0 while attempt < 1000: code = webapp2_extras.security.generate_random_string( length=cls.LICENCE_KEY_LENGTH, pool=webapp2_extras.security.UPPERCASE_ALPHANUMERIC) if not cls.exist_by_licence_key(code): return code attempt += 1 logging.error( 'Could not generate unique Licence Key. LICENCE_KEY_LENGTH = ' + str(cls.LICENCE_KEY_LENGTH)) return 'LICENGENERERROR' # in case unique licence code cannot be generated (unlikely) @classmethod def insert_dashes_5_10(cls, string): result_10 = string[:10] + '-' + string[10:] result = result_10[:5] + '-' + result_10[5:] return result @classmethod def generate_licence_keys(cls, quantity): licence_keys = '' if quantity: quantity = int(quantity) while quantity > 0: licence_keys += cls.insert_dashes_5_10( cls.generate_licence_key()) + cls.SEPARATOR_LICENCE_KEYS quantity -= 1 return licence_keys @classmethod def count_licence_keys(cls, shop_name, product_key, activated=True): if activated: return cls.count_by_shop_name_order_type_activated( shop_name, product_key) else: return cls.count_by_shop_name_order_type_not_activated( shop_name, product_key)