# Copyright 2009 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import datetime from google.appengine.api import memcache from google.appengine.ext import db import models import hashlib import parser import utils class Settings(object): MAX_FETCH_LENGTH = 20 MAX_TEAM_MEMBERS = 50 MAX_INTERESTING_TEAMS = 5 MAX_UPDATES_PER_WEEK = 20 MAX_RESULTS_PER_PAGE = 50 class ConstraintError(Exception): pass def admin_get_all_teams(): return models.Team.all().fetch(1000) def admin_get_all_activities(account): gql = 'WHERE account = :1 ORDER BY time DESC' return models.Activity.gql(gql, account.key()).fetch(1000) def admin_delete_activities(activities): return map(db.delete, activities) def user_get_or_insert(container_id, user_id): key_name = "user|%s|%s" % (container_id, user_id) user = models.User.get_by_key_name(key_name) if user is None: account = models.Account() account.name = "%s@%s" % (user_id, container_id) account.put() user = models.User(key_name = key_name, container_id = container_id, opensocial_id = user_id, account = account) user.put() return user def model_get_by_id(model, ids): """ Returns teams specified by their IDs. """ if isinstance(ids, list): if len(ids) > Settings.MAX_FETCH_LENGTH: raise ConstraintError("Trying to fetch too many records.") try: entities = model.get_by_id(ids) except db.BadKeyError: entities = [] if isinstance(entities, list): entities = [entity for entity in entities if entity] else: entities = [entities] return entities def team_create(user, team_name): """ Creates a team and assigns the creating user to the team. """ team = models.Team(name=team_name, count=0) team.put() team_join(user, team) return team def team_adjust_user_count(team, num): """ It isn't critical that count reflects the actual count, it is just used as a general 'interestingness' indicator. """ def txn(team_id, num): txn_team = models.Team.get_by_id(team_id) txn_team.count += num txn_team.put() db.run_in_transaction(txn, team.team_id, num) def team_count_users(team): """ Returns the number of users on the given team. """ gql = models.Account.gql("WHERE team = :1", team.key()) count = gql.count(Settings.MAX_TEAM_MEMBERS) if count != team.count: team.count = count team.put() return count def team_join(user, team): """ Moves the current user to the specified team. """ if team_count_users(team) >= Settings.MAX_TEAM_MEMBERS: raise ConstraintError("This team is full.") old_team = user.account.team user.account.team = team user.account.put() team_adjust_user_count(team, 1) if old_team: old_team_users = team_count_users(old_team) if old_team_users == 0: old_team.delete() def team_get_interesting(team_ids=None): """ Returns a number of 'interesting' teams for suggestion purposes. """ if team_ids: teams = model_get_by_id(models.Team, team_ids) else: teams = [] gql = models.Team.gql("ORDER BY count DESC") interesting_teams = gql.fetch(Settings.MAX_INTERESTING_TEAMS) if interesting_teams: teams = teams + interesting_teams return teams def activity_count(user, days): querytime = datetime.datetime.utcnow().replace(tzinfo = models.UTC()) querytime = querytime - datetime.timedelta(days=days) query = "WHERE time >= :1 AND account = :2 ORDER BY time DESC" gql = models.Activity.gql(query, querytime, user.account.key()) return gql.count() def activity_delete(user, activity_id): activity = models.Activity.get_by_id(activity_id) if activity is None: raise ConstraintError("You specified an invalid ID.") if activity.account.account_id != user.account.account_id: raise ConstraintError("You can only delete your own activities!") activity.delete() return True def activity_create(user, text): if user.account.team is None: raise ConstraintError("You do not belong to a team.") if activity_count(user, 7) >= Settings.MAX_UPDATES_PER_WEEK: raise ConstraintError("You cannot post more than %s activities per week." % Settings.MAX_UPDATES_PER_WEEK) data = parser.parse_activity(text) if data is None: raise ConstraintError("Your input could not be parsed.") if data.has_key('distance'): activity = models.DistanceActivity() activity.activity = data['distance']['activity'] activity.distance = data['miles'] if data.has_key('speed'): activity.speed = data['speed'] elif data.has_key('duration'): activity.speed = activity.distance / data['duration'] elif data['distance'].has_key('speed'): activity.speed = data['distance']['speed'] else: raise ConstraintError('I could not determine how fast you went.') elif data.has_key('count'): activity = models.CountActivity() activity.activity = data['count']['activity'] activity.count = data['times'] else: raise ConstraintError('I could not determine which activity you performed.') if data.has_key('datetime'): activity.time = data['datetime'] else: activity.time = datetime.datetime.utcnow().replace(tzinfo = models.UTC()) activity.text = text activity.account = user.account activity.team = user.account.team activity.put() return activity def activity_get_by_model(model, name, model_id, start=None, end=None, page=0, activity=None): if end is None: end = datetime.datetime.utcnow().replace(tzinfo = models.UTC()) if start is None: start = end - datetime.timedelta(days=30) if isinstance(model_id, list): model_id = model_id[0] model_entity = model_get_by_id(model, model_id)[0] if not model_entity: raise ConstraintError('Invalid ID.') limit = Settings.MAX_RESULTS_PER_PAGE offset = page * limit query = db.Query(models.Activity) query.filter('time >=', start) query.filter('time <=', end) query.order('-time') query.filter(name + ' =', model_entity.key()) if activity: query.filter('activity =', activity) ret = { 'activities' : query.fetch(limit, offset), 'page' : page, 'more' : len(query.fetch(1, offset + limit)) == 1, 'start' : start, 'end' : end, } ret[name] = model_entity return ret def activity_get_by_account(account_id, **kwargs): ret = activity_get_by_model(models.Account, 'account', account_id, **kwargs) ret.update({ 'team' : ret['account'].team, }) return ret def activity_get_by_team(team_id, **kwargs): ret = activity_get_by_model(models.Team, 'team', team_id, **kwargs) return ret def data_from_activities(activities): ret = { 'data' : [], 'accounts' : {}, } if len(activities) == 0: return ret activities.sort(key=lambda x: x.time) start = activities[0].date() end = activities[-1].date() currentdate = start times = {} sequential_times = [] temp_data = {} data = [] accounts = {} while currentdate <= end: timestamp = utils.datetime_to_date_timestamp(currentdate) times[timestamp] = currentdate sequential_times.append(timestamp) currentdate = currentdate + datetime.timedelta(days=1) for activity in activities: account_id = activity.account.account_id timestamp = activity.date_timestamp if temp_data.has_key(account_id): if temp_data[account_id].has_key(timestamp): foodles = temp_data[account_id][timestamp] + activity.foodles temp_data[account_id][timestamp] = foodles else: temp_data[account_id][timestamp] = activity.foodles else: temp_data[account_id] = { timestamp : activity.foodles } accounts[account_id] = activity.account for time in sequential_times: result = [times[time]] for account in temp_data: if temp_data[account].has_key(time): result.append(temp_data[account][time]) else: result.append(0) data.append(result) ret['data'] = data ret['accounts'] = accounts return ret def data_get_by_account(account_id, **kwargs): kwargs['page'] = 0 more = True activities = [] while more: result = activity_get_by_model(models.Account, 'account', account_id, **kwargs) activities.extend(result['activities']) more = result['more'] return data_from_activities(activities) def data_get_by_team(team_id, **kwargs): kwargs['page'] = 0 more = True activities = [] while more: result = activity_get_by_model(models.Team, 'team', team_id, **kwargs) activities.extend(result['activities']) more = result['more'] ret = data_from_activities(activities) ret['team'] = result['team'].json() del ret['team']['members'] return ret def account_update_name(user, user_name): user.account.name = user_name user.account.put() return True