class ExercisesCreatedHistogram(request_handler.RequestHandler): @user_util.open_access def get(self): past_days = self.request_int('past_days', 7) today_dt = dt.datetime.combine(dt.date.today(), dt.time()) earliest_dt = today_dt - dt.timedelta(days=past_days) self.response.out.write( self.get_histogram_spline_for_highcharts(earliest_dt)) @layer_cache.cache_with_key_fxn(lambda self, date: "%s|%s" % (Setting.cached_exercises_date(), date), expiration=CACHE_EXPIRATION_SECS, layer=layer_cache.Layers.Memcache) def get_histogram_spline_for_highcharts(self, earliest_dt=dt.datetime.min): histogram = {} for ex in Exercise.get_all_use_cache(): if ex.creation_date: creation_day = dt.datetime.combine(ex.creation_date, dt.time()) timestamp = to_unix_secs(creation_day) * 1000 histogram[timestamp] = histogram.get(timestamp, 0) + 1 total_exercises = {} prev_value = 0 for day, value in sorted(histogram.items()): prev_value = total_exercises[day] = prev_value + value # Only retain recent dates earliest_unix = to_unix_secs(earliest_dt) * 1000 histogram = [[k, v] for k, v in histogram.items() if k >= earliest_unix] total_exercises = [[k, v] for k, v in total_exercises.items() if k >= earliest_unix] context = { 'series': [{ 'name': 'Histogram (created per day)', 'type': 'column', 'data_values': json.dumps(histogram), 'axis': 0, }, { 'name': 'Total exercises', 'type': 'spline', 'data_values': json.dumps(total_exercises), 'axis': 1, }], # Let highcharts determine the scales for now. 'axes': [ { 'max': 'null' }, { 'max': 'null' }, ], } return self.render_jinja2_template_to_string( 'exercisestats/highcharts_exercises_created_histogram.json', context)
def post(self): # Protected for admins only by app.yaml so taskqueue can hit this URL step = self.request_int("step", default = 0) if step == YouTubeSyncStep.START: self.startYouTubeSync() elif step == YouTubeSyncStep.UPDATE_VIDEO_STATS: self.updateVideoStats() log = YouTubeSyncStepLog() log.step = step log.generation = int(Setting.last_youtube_sync_generation_start()) log.put() # check to see if we have more steps to go if step < YouTubeSyncStep.UPDATE_VIDEO_STATS: self.task_step(step + 1)
class ExerciseNumberTrivia(request_handler.RequestHandler): @user_util.open_access def get(self): number = self.request_int('num', len(Exercise.get_all_use_cache())) self.render_json(self.number_facts_for_geckboard_text(number)) @staticmethod @layer_cache.cache_with_key_fxn(lambda number: "%s|%s" % (Setting.cached_exercises_date(), number), expiration=CACHE_EXPIRATION_SECS, layer=layer_cache.Layers.Memcache) def number_facts_for_geckboard_text(number): import exercisestats.number_trivia as number_trivia math_fact = number_trivia.math_facts.get( number, 'This number is interesting. Why? Suppose there exists uninteresting ' 'natural numbers. Then the smallest in that set would be ' 'interesting by virtue of being the first: a contradiction. ' 'Thus all natural numbers are interesting.') year_fact = number_trivia.year_facts.get( number, 'nothing interesting happened') misc_fact_keys = sorted(number_trivia.misc_facts.keys()) first_available_num = misc_fact_keys[ bisect.bisect_left(misc_fact_keys, number) - 1] greater_than_fact = number_trivia.misc_facts[first_available_num] text1 = 'We now have more exercises than %s (%s)!' % ( cgi.escape(greater_than_fact), str(first_available_num)) text2 = math_fact text3 = "In year %d, %s" % (number, cgi.escape(year_fact)) return { 'item': [ { 'text': text1 }, { 'text': text2 }, { 'text': text3 }, ] }
class ExercisesLastAuthorCounter(request_handler.RequestHandler): @user_util.open_access def get(self): self.render_json(self.exercise_counter_for_geckoboard_rag()) @staticmethod @layer_cache.cache_with_key_fxn( lambda: "last_author_%s" % Setting.cached_exercises_date(), expiration=CACHE_EXPIRATION_SECS, layer=layer_cache.Layers.Memcache) def exercise_counter_for_geckoboard_rag(): exercises = Exercise.get_all_use_cache() exercises.sort(key=lambda ex: ex.creation_date, reverse=True) last_exercise = exercises[0] num_exercises = len(exercises) last_exercise_author = last_exercise.author.nickname( ) if last_exercise.author else 'random person' text = "Thanks %s for %s!" % (last_exercise_author, last_exercise.display_name) return { 'item': [ { 'value': None, 'text': '', }, { 'value': None, 'text': '', }, { 'value': num_exercises, 'text': text, }, ] }
class ExerciseStatsMapGraph(request_handler.RequestHandler): # TODO: Just move this logic into get and make get_use_cache take a day parameter. def get_request_params(self): default_day = dt.date.today() - dt.timedelta(days=2) interested_day = self.request_date('date', "%Y/%m/%d", default_day) return {'interested_day': interested_day} @user_util.open_access def get(self): self.response.out.write(self.get_use_cache()) @layer_cache.cache_with_key_fxn( lambda self: "%s|%s" % (Setting.cached_exercises_date(), self.get_request_params()), expiration=CACHE_EXPIRATION_SECS, layer=layer_cache.Layers.Memcache) def get_use_cache(self): params = self.get_request_params() # Get the maximum so we know how the data label circles should be scaled most_new_users = 1 ex_stat_dict = {} for ex in Exercise.get_all_use_cache(): stat = ExerciseStatistic.get_by_date(ex.name, params['interested_day']) ex_stat_dict[ex.name] = stat if stat: most_new_users = max(most_new_users, stat.num_new_users()) data_points = [] min_y, max_y = -1, 1 for ex in Exercise.get_all_use_cache(): stat = ex_stat_dict[ex.name] y, x = -int(ex.h_position), int(ex.v_position) min_y, max_y = min(y, min_y), max(y, max_y) # Set the area of the circle proportional to the data value radius = 1 if stat: radius = math.sqrt( float(stat.num_new_users()) / most_new_users) * MAX_POINT_RADIUS point = { 'x': x, 'y': y, 'name': ex.display_name, 'marker': { 'radius': max(radius, 1) }, } data_points.append(point) context = { 'title': 'Exercises map - First attempts', 'series': { 'name': 'First attempts', 'data_values': json.dumps(data_points), }, 'minYValue': min_y - 1, 'maxYValue': max_y + 1, } return self.render_jinja2_template_to_string( 'exercisestats/highcharts_scatter_map.json', context)
# return last item in the list return prev_topic # A number to increment if the layout of the page, as expected by the client # side JS changes, and the JS is changed to update it. This version is # independent of topic content version, and is to do with code versions _layout_version = 2 @layer_cache.cache_with_key_fxn( lambda ajax=False, version_number=None: "library_content_by_topic_%s_v%s.%s" % ( "ajax" if ajax else "inline", version_number if version_number else Setting.topic_tree_version(), _layout_version)) def library_content_html(ajax=False, version_number=None): """" Returns the HTML for the structure of the topics as they will be populated on the homepage. Does not actually contain the list of video names as those are filled in later asynchronously via the cache. """ if version_number: version = topic_models.TopicVersion.get_by_number(version_number) else: version = topic_models.TopicVersion.get_default_version() root = topic_models.Topic.get_root(version) if root == None: return "" tree = root.make_tree(types=["Topics", "Video", "Url"]) topics = flatten_tree(tree)
raise SmartHistoryLoadException( "Post attempt failed to SmartHistory with :" + str(e)) return self.response.out.write(data) @staticmethod def clearCache(): Setting.smarthistory_version(int(Setting.smarthistory_version()) + 1) #load the resource from smart history's server and then cache it #in the data store. If it is an image then cache it in the blob #store and store the blobkey in the data store @layer_cache.cache_with_key_fxn( lambda self: "smart_history_v%s_%s" % ( Setting.smarthistory_version(), self.request.path_qs), layer=layer_cache.Layers.Datastore, expiration=SMARTHISTORY_CACHE_EXPIRATION_TIME, persist_across_app_versions=True, permanent_key_fxn=lambda self: "smart_history_permanent_%s" % ( self.request.path_qs)) def load_resource(self): path = self.request.path_qs #img is in users browser cache - we don't want to cache a #Not-Modified response otherwise people who don't have image #in browser cache won't get it headers = dict((k, v) for (k, v) in self.request.headers.iteritems() if k not in ["If-Modified-Since", "If-None-Match", "Content-Length", "Host"])
except Exception, e: raise SmartHistoryLoadException( "Post attempt failed to SmartHistory with :" + str(e)) return self.response.out.write(data) @staticmethod def clearCache(): Setting.smarthistory_version(int(Setting.smarthistory_version()) + 1) #load the resource from smart history's server and then cache it in the data store #if it is an image then cache it in the blob store and store the blobkey in the data store @layer_cache.cache_with_key_fxn( lambda self: "smart_history_v%s_%s" % (Setting.smarthistory_version(), self.request.path_qs), layer=layer_cache.Layers.Datastore, expiration=SMARTHISTORY_CACHE_EXPIRATION_TIME, persist_across_app_versions=True, permanent_key_fxn=lambda self: "smart_history_permanent_%s" % (self.request.path_qs)) def load_resource(self): path = self.request.path_qs #img is in users browser cache - we don't want to cache a Not-Modified response otherwise people who don't have image in browser cache won't get it headers = dict( (k, v) for (k, v) in self.request.headers.iteritems() if k not in ["If-Modified-Since", "If-None-Match", "Content-Length", "Host"]) logging.info("getting resource " + str(path) + " from " + SMARTHISTORY_URL)
import layer_cache import topic_models import video_models from url_model import Url from setting_model import Setting @layer_cache.cache_with_key_fxn(lambda version_number=None: "video_title_dicts_%s" % ( version_number or Setting.topic_tree_version())) def video_title_dicts(version_number=None): if version_number: version = topic_models.TopicVersion.get_by_number(version_number) else: version = None return map(lambda video: { "title": video.title, "key": str(video.key()), "relative_url": "/video/%s" % video.readable_id, "id": video.readable_id }, [v for v in video_models.Video.get_all_live(version=version) if v is not None]) @layer_cache.cache_with_key_fxn(lambda version_number=None: "url_title_dicts_%s" % ( version_number or Setting.topic_tree_version())) def url_title_dicts(version_number=None): if version_number: version = topic_models.TopicVersion.get_by_number(version_number)
def startYouTubeSync(self): Setting.last_youtube_sync_generation_start(int(Setting.last_youtube_sync_generation_start()) + 1)
# return last item in the list return prev_topic # A number to increment if the layout of the page, as expected by the client # side JS changes, and the JS is changed to update it. This version is # independent of topic content version, and is to do with code versions _layout_version = 1 @layer_cache.cache_with_key_fxn( lambda ajax=False, version_number=None: "library_content_by_topic_%s_v%s.%s" % ("ajax" if ajax else "inline", version_number if version_number else Setting.topic_tree_version(), _layout_version)) def library_content_html(ajax=False, version_number=None): """" Returns the HTML for the structure of the topics as they will be populated on the homepage. Does not actually contain the list of video names as those are filled in later asynchronously via the cache. """ if version_number: version = topic_models.TopicVersion.get_by_number(version_number) else: version = topic_models.TopicVersion.get_default_version() tree = topic_models.Topic.get_root(version).make_tree( types=["Topics", "Video", "Url"]) topics = flatten_tree(tree) # TODO(tomyedwab): Remove this once the confusion over the old
else: raise SmartHistoryLoadException(response.status_code) except Exception, e: raise SmartHistoryLoadException("Post attempt failed to SmartHistory with :" + str(e)) return self.response.out.write(data) @staticmethod def clearCache(): Setting.smarthistory_version(int(Setting.smarthistory_version()) + 1) # load the resource from smart history's server and then cache it in the data store # if it is an image then cache it in the blob store and store the blobkey in the data store @layer_cache.cache_with_key_fxn( lambda self: "smart_history_v%s_%s" % (Setting.smarthistory_version(), self.request.path_qs), layer=layer_cache.Layers.Datastore, expiration=SMARTHISTORY_CACHE_EXPIRATION_TIME, persist_across_app_versions=True, permanent_key_fxn=lambda self: "smart_history_permanent_%s" % (self.request.path_qs), ) def load_resource(self): path = self.request.path_qs # img is in users browser cache - we don't want to cache a Not-Modified response otherwise people who don't have image in browser cache won't get it headers = dict( (k, v) for (k, v) in self.request.headers.iteritems() if k not in ["If-Modified-Since", "If-None-Match", "Content-Length", "Host"] )
import layer_cache import topic_models import video_models from url_model import Url from setting_model import Setting @layer_cache.cache_with_key_fxn( lambda version_number=None: "video_title_dicts_%s" % (version_number or Setting.topic_tree_version())) def video_title_dicts(version_number=None): if version_number: version = topic_models.TopicVersion.get_by_number(version_number) else: version = None return map( lambda video: { "title": video.title, "key": str(video.key()), "relative_url": "/video/%s" % video.readable_id, "id": video.readable_id }, [ v for v in video_models.Video.get_all_live(version=version) if v is not None ]) @layer_cache.cache_with_key_fxn( lambda version_number=None: "url_title_dicts_%s" % (version_number or Setting.topic_tree_version()))
def clearCache(): Setting.smarthistory_version(int(Setting.smarthistory_version()) + 1)
# return last item in the list return prev_topic # A number to increment if the layout of the page, as expected by the client # side JS changes, and the JS is changed to update it. This version is # independent of topic content version, and is to do with code versions _layout_version = 2 @layer_cache.cache_with_key_fxn( lambda ajax=False, version_number=None: "library_content_by_topic_%s_v%s.%s" % ( "ajax" if ajax else "inline", version_number if version_number else Setting.topic_tree_version(), _layout_version)) def library_content_html(ajax=False, version_number=None): """" Returns the HTML for the structure of the topics as they will be populated on the homepage. Does not actually contain the list of video names as those are filled in later asynchronously via the cache. """ if version_number: version = topic_models.TopicVersion.get_by_number(version_number) else: version = topic_models.TopicVersion.get_default_version() tree = topic_models.Topic.get_root(version).make_tree( types=["Topics", "Video", "Url"]) topics = flatten_tree(tree)
def get_cache_key(self, exids, dates, title='', showLegend=False): return "%s|%s|%s|%s|%s" % (Setting.cached_exercises_date(), sorted(exids), sorted(dates), title, showLegend)
def get_cache_key(self, exids, dates, title="", showLegend=False): return "%s|%s|%s|%s|%s" % (Setting.cached_exercises_date(), sorted(exids), sorted(dates), title, showLegend)