def post(self): """ Get a proposed username and test to see whether it is valid. If so, return that it is available, otherwise return that it is not via a simple JSON response. """ user = UserPrefs.get() if not user: self.abort(404) username = cgi.escape(self.request.get('username')) count = 0 # Does an existing user have that username? if not user.name == username: count = UserPrefs.all().filter('name =', username).count() # Is the name too short or one of the disallowed names? if not username or len(username) < 4 \ or username in settings.RESERVED_USERNAMES: count = 1 self.render_json({ 'username': username, 'available': count == 0 })
def process(self, username, action): """ Follow the given user. """ user = UserPrefs.get() publicuser = UserPrefs.all().filter('name =', username).get() if not user or not publicuser: return self.render_json({ 'status': 'error', 'error': 'User not found' }) if action == 'post': if publicuser.user_id in user.following: return self.render_json({ 'status': 'error', 'error': 'Already following user' }) user.following.append(publicuser.user_id) existing = UserAction.all()\ .filter('owner =', user)\ .filter('type =', UserAction.TYPE_USER_FOLLOWED)\ .filter('object_id =', publicuser.key().id())\ .count() if not existing: user_action = UserAction() user_action.owner = user user_action.type = user_action.TYPE_USER_FOLLOWED user_action.object_id = publicuser.key().id() user_action.put() else: if publicuser.user_id not in user.following: return self.render_json({ 'status': 'error', 'error': 'User not being followed' }) user.following.remove(publicuser.user_id) existing = UserAction.all()\ .filter('owner =', user)\ .filter('type =', UserAction.TYPE_USER_FOLLOWED)\ .filter('object_id =', publicuser.key().id())\ .get() if existing: existing.delete() # Save updated following list user.put() self.render_json({ 'status': 'ok' })
def process(self, action, username=None, recipe_slug=None): """ Process a request to add or remove a user from the liked list. """ user = UserPrefs.get() publicuser = UserPrefs.all()\ .filter('name = ', username)\ .get() if not publicuser: self.render_json({'status': 'error', 'error': 'User not found'}) return recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug =', recipe_slug)\ .get() if not recipe: self.render_json({'status': 'error', 'error': 'Recipe not found'}) return if action == 'post': if user.user_id not in recipe.likes: recipe.likes.append(user.user_id) recipe.put() existing = UserAction.all()\ .filter('owner =', user)\ .filter('type =', UserAction.TYPE_RECIPE_LIKED)\ .filter('object_id =', recipe.key().id())\ .count() if not existing: user_action = UserAction() user_action.owner = user user_action.type = user_action.TYPE_RECIPE_LIKED user_action.object_id = recipe.key().id() user_action.put() elif action == 'delete': if user.user_id in recipe.likes: recipe.likes.remove(user.user_id) recipe.put() existing = UserAction.all()\ .filter('owner =', user)\ .filter('type =', UserAction.TYPE_RECIPE_LIKED)\ .filter('object_id =', recipe.key().id())\ .get() if existing: existing.delete() return self.render_json({'status': 'ok', 'likes': len(recipe.likes)})
def get(self, username): """ Render a user page. """ publicuser = UserPrefs.all().filter('name =', username).get() if not publicuser: self.abort(404) recipes = Recipe.all()\ .filter('owner =', publicuser)\ .order('name')\ .run(limit=25) actions = UserAction.all()\ .filter('owner =', publicuser)\ .order('-created')\ .fetch(15) object_ids = UserAction.gather_object_ids(actions) user_map = {publicuser.key().id(): publicuser} for user in UserPrefs.get_by_id(object_ids['users']): user_map[user.key().id()] = user recipes = [r for r in recipes] recipe_ids = [recipe.key().id() for recipe in recipes] object_ids['recipes'] = [ id for id in object_ids['recipes'] if id not in recipe_ids ] recipe_map = {} for recipe in recipes: recipe.owner = publicuser recipe_map[recipe.key().id()] = recipe for recipe in Recipe.get_by_id(object_ids['recipes']): recipe_map[recipe.key().id()] = recipe brew_map = {} for brew in Brew.get_by_id(object_ids['brews']): brew_map[brew.key().id()] = brew self.render( 'user.html', { 'publicuser': publicuser, 'recipes': recipes, 'actions': actions, 'user_map': user_map, 'recipe_map': recipe_map, 'brew_map': brew_map })
def post(self, username=None, recipe_slug=None): publicuser = UserPrefs.all()\ .filter('name = ', username)\ .get() if not publicuser: self.render_json({ 'status': 'error', 'error': 'User not found' }) return recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug =', recipe_slug)\ .get() if not recipe: self.render_json({ 'status': 'error', 'error': 'Recipe not found' }) return new_recipe = Recipe(**{ 'owner': UserPrefs.get(), 'cloned_from': recipe, 'color': recipe.color, 'ibu': recipe.ibu, 'alcohol': recipe.alcohol, 'name': recipe.name, 'description': recipe.description, 'type': recipe.type, 'style': recipe.style, 'batch_size': recipe.batch_size, 'boil_size': recipe.boil_size, 'bottling_temp': recipe.bottling_temp, 'bottling_pressure': recipe.bottling_pressure, '_ingredients': recipe._ingredients }) new_recipe.slug = generate_usable_slug(new_recipe) new_recipe.put() action = UserAction() action.owner = UserPrefs.get() action.type = action.TYPE_RECIPE_CLONED action.object_id = new_recipe.key().id() action.put() return self.render_json({ 'status': 'ok', 'redirect': new_recipe.url })
def get(self, username): """ Render a user page. """ publicuser = UserPrefs.all().filter('name =', username).get() if not publicuser: self.abort(404) recipes = Recipe.all()\ .filter('owner =', publicuser)\ .order('name')\ .run(limit=25) actions = UserAction.all()\ .filter('owner =', publicuser)\ .order('-created')\ .fetch(15) object_ids = UserAction.gather_object_ids(actions) user_map = { publicuser.key().id(): publicuser } for user in UserPrefs.get_by_id(object_ids['users']): user_map[user.key().id()] = user recipes = [r for r in recipes] recipe_ids = [recipe.key().id() for recipe in recipes] object_ids['recipes'] = [id for id in object_ids['recipes'] if id not in recipe_ids] recipe_map = {} for recipe in recipes: recipe.owner = publicuser recipe_map[recipe.key().id()] = recipe for recipe in Recipe.get_by_id(object_ids['recipes']): recipe_map[recipe.key().id()] = recipe brew_map = {} for brew in Brew.get_by_id(object_ids['brews']): brew_map[brew.key().id()] = brew self.render('user.html', { 'publicuser': publicuser, 'recipes': recipes, 'actions': actions, 'user_map': user_map, 'recipe_map': recipe_map, 'brew_map': brew_map })
def get(self, provider=None): """ Handle login initiation via a known OAuth 2 provider. """ if settings.DEBUG and provider == "dummy": # Handle test dev login auth_id = 'dummy:test' UserPrefs.create_or_update( auth_id, { 'id': '1234', 'name': 'Test User', 'email': '*****@*****.**', 'avatar': 'http://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s={0}&d=identicon' }, {}) # Update session self.session['auth_id'] = auth_id return self.redirect(self.session.pop('next', '/')) auth_url = AUTH_URLS[provider][0] key = getattr(settings, provider.upper() + '_OAUTH_KEY') scope = getattr(settings, provider.upper() + '_OAUTH_SCOPE') # Generate a random state parameter to prevent CSRF attacks # This is both stored in the session and sent to the authorizing # server, relayed back and then checked to make sure it matches # up. If not, then the request likely did not originate from # this site and it can be ignored. csrf_state = hashlib.md5(uuid.uuid4().hex).hexdigest() self.session['login_csrf'] = csrf_state params = { 'response_type': 'code', 'client_id': key, 'redirect_uri': self.callback_url(provider), 'state': csrf_state } if scope: params.update(scope=scope) target_url = auth_url.format(urlencode(params)) logging.info('Redirecting user to %s', target_url) self.redirect(target_url)
def get_recipes(self, request): """ Get a list of recipes, optionally filtered by user name. """ offset, limit = get_limits(request) if request.user_name: publicuser = UserPrefs.all().filter('name =', request.user_name).get() if not publicuser: raise endpoints.NotFoundException(USER_NOT_FOUND) query = Recipe.all()\ .filter('owner =', publicuser) else: query = Recipe.all() if request.order == apimessages.RecipeOrder.NAME: query = query.order('name') elif request.order == apimessages.RecipeOrder.CREATED: query = query.order('-created') elif request.order == apimessages.RecipeOrder.EDITED: query = query.order('-edited') elif request.order == apimessages.RecipeOrder.LIKES: query = query.order('-likes_count') recipes = query.fetch(limit, offset=offset) items = [] for recipe in recipes: items.append(recipe_to_response(recipe)) return apimessages.RecipeListResponse(**{'items': items})
def process(self, username=None, recipe_slug=None, brew_slug=None): publicuser = UserPrefs.all()\ .filter('name =', username)\ .get() if not publicuser: return [None, None, None] recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug = ', recipe_slug)\ .get() if not recipe: return [publicuser, None, None] if brew_slug is not None: brew = Brew.all()\ .filter('owner =', publicuser)\ .filter('recipe =', recipe)\ .filter('slug =', brew_slug)\ .get() if not brew: return [publicuser, recipe, None] else: brew = Brew() brew.owner = self.user brew.recipe = recipe return [publicuser, recipe, brew]
def get(self): """ Render the public users list. """ self.render('users.html', { 'users': UserPrefs.all() })
def get(self, username=None): """ Render the public recipe list for a user or all users. """ show_owners = False if username: publicuser = UserPrefs.all().filter('name =', username).get() recipes = Recipe.all()\ .filter('owner =', publicuser)\ .order('-grade') def setowner(recipe): recipe.owner = publicuser return recipe recipes = map(setowner, recipes) else: publicuser = None recipes = Recipe.all().order('-grade') recipes = [r for r in recipes] show_owners = True self.render('recipes.html', { 'publicuser': publicuser, 'recipes': recipes, 'show_owners': show_owners })
def get(self, username=None): """ Render the public recipe list for a user or all users. """ show_owners = False if username: publicuser = UserPrefs.all().filter('name =', username).get() recipes = Recipe.all()\ .filter('owner =', publicuser)\ .order('-grade') def setowner(recipe): recipe.owner = publicuser return recipe recipes = map(setowner, recipes) else: publicuser = None recipes = Recipe.all().order('-grade') recipes = [r for r in recipes] show_owners = True self.render( 'recipes.html', { 'publicuser': publicuser, 'recipes': recipes, 'show_owners': show_owners })
def handle_error(request, response, exception, code, msg): """ Render an error template with a code and message. """ logging.exception(exception) # Try to get the current user session_store = sessions.get_store(request=request) session = session_store.get_session() auth_id = session.get('auth_id') user = None if auth_id: user = UserPrefs.get(auth_id) if user: logging.error('Currently logged in user is ' + user.name + ' (' + user.email + ')') # Render and error template template = get_template('error.html') response.status = code response.out.write( template.render({ 'user': user, 'code': code, 'message': msg }))
def get(self, username, recipe_slug): publicuser = UserPrefs.all().filter('name = ', username).get() if publicuser: recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug =', recipe_slug)\ .get() else: recipe = None width = 260 try: width = int(self.request.get('width')) except: pass if publicuser and recipe: self.render('recipe-embed.html', { 'publicuser': publicuser, 'recipe': recipe, 'width': width, }) else: self.render('recipe-embed-404.html', { 'publicuser': publicuser, 'width': width, })
def get_recipes(self, request): """ Get a list of recipes, optionally filtered by user name. """ offset, limit = get_limits(request) if request.user_name: publicuser = UserPrefs.all().filter('name =', request.user_name).get() if not publicuser: raise endpoints.NotFoundException(USER_NOT_FOUND) query = Recipe.all()\ .filter('owner =', publicuser) else: query = Recipe.all() if request.order == apimessages.RecipeOrder.NAME: query = query.order('name') elif request.order == apimessages.RecipeOrder.CREATED: query = query.order('-created') elif request.order == apimessages.RecipeOrder.EDITED: query = query.order('-edited') elif request.order == apimessages.RecipeOrder.LIKES: query = query.order('-likes_count') recipes = query.fetch(limit, offset=offset) items = [] for recipe in recipes: items.append(recipe_to_response(recipe)) return apimessages.RecipeListResponse(**{ 'items': items })
def get(self, provider=None): """ Handle login initiation via a known OAuth 2 provider. """ if settings.DEBUG and provider == "dummy": # Handle test dev login auth_id = 'dummy:test' UserPrefs.create_or_update(auth_id, { 'id': '1234', 'name': 'Test User', 'email': '*****@*****.**', 'avatar': 'http://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s={0}&d=identicon' }, {}) # Update session self.session['auth_id'] = auth_id return self.redirect(self.session.pop('next', '/')) auth_url = AUTH_URLS[provider][0] key = getattr(settings, provider.upper() + '_OAUTH_KEY') scope = getattr(settings, provider.upper() + '_OAUTH_SCOPE') # Generate a random state parameter to prevent CSRF attacks # This is both stored in the session and sent to the authorizing # server, relayed back and then checked to make sure it matches # up. If not, then the request likely did not originate from # this site and it can be ignored. csrf_state = hashlib.md5(uuid.uuid4().hex).hexdigest() self.session['login_csrf'] = csrf_state params = { 'response_type': 'code', 'client_id': key, 'redirect_uri': self.callback_url(provider), 'state': csrf_state } if scope: params.update(scope=scope) target_url = auth_url.format(urlencode(params)) logging.info('Redirecting user to %s', target_url) self.redirect(target_url)
def post(self, username=None, recipe_slug=None): publicuser = UserPrefs.all()\ .filter('name = ', username)\ .get() if not publicuser: self.render_json({'status': 'error', 'error': 'User not found'}) return recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug =', recipe_slug)\ .get() if not recipe: self.render_json({'status': 'error', 'error': 'Recipe not found'}) return new_recipe = Recipe( **{ 'owner': UserPrefs.get(), 'cloned_from': recipe, 'color': recipe.color, 'ibu': recipe.ibu, 'alcohol': recipe.alcohol, 'name': recipe.name, 'description': recipe.description, 'type': recipe.type, 'style': recipe.style, 'batch_size': recipe.batch_size, 'boil_size': recipe.boil_size, 'bottling_temp': recipe.bottling_temp, 'bottling_pressure': recipe.bottling_pressure, '_ingredients': recipe._ingredients }) new_recipe.slug = generate_usable_slug(new_recipe) new_recipe.put() action = UserAction() action.owner = UserPrefs.get() action.type = action.TYPE_RECIPE_CLONED action.object_id = new_recipe.key().id() action.put() return self.render_json({'status': 'ok', 'redirect': new_recipe.url})
def object(self): if self.type in [self.TYPE_USER_FOLLOWED]: return UserPrefs.get_by_id(self.object_id) elif self.type in [ self.TYPE_RECIPE_CREATED, self.TYPE_RECIPE_EDITED, self.TYPE_RECIPE_CLONED, self.TYPE_RECIPE_LIKED ]: from models.recipe import Recipe return Recipe.get_by_id(self.object_id)
def object(self): if self.type in [self.TYPE_USER_FOLLOWED]: return UserPrefs.get_by_id(self.object_id) elif self.type in [self.TYPE_RECIPE_CREATED, self.TYPE_RECIPE_EDITED, self.TYPE_RECIPE_CLONED, self.TYPE_RECIPE_LIKED]: from models.recipe import Recipe return Recipe.get_by_id(self.object_id)
def get_user(self, request): """ Get a user by name. """ publicuser = UserPrefs.all().filter('name =', request.user_name).get() if not publicuser: raise endpoints.NotFoundException(USER_NOT_FOUND) return user_to_response(publicuser)
def get(self, username=None, recipe_slug=None, version=None): """ Render the recipe view. If no slug is given then create a new recipe and render it in edit mode. """ # Create a new recipe if we have no slug, otherwise query if not recipe_slug: publicuser = self.user recipe = Recipe() recipe.owner = publicuser recipe.new = True else: publicuser = UserPrefs.all().filter('name =', username).get() if not publicuser: self.abort(404) recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug =', recipe_slug)\ .get() if not recipe: self.abort(404) if version: try: version = int(version) except: self.abort(404) history = RecipeHistory.get_by_id(version, recipe) if not history: self.abort(404) recipe.old = True recipe.oldname = history.name recipe.description = history.description recipe.type = history.type recipe.category = history.category recipe.style = history.style recipe.batch_size = history.batch_size recipe.boil_size = history.boil_size recipe.bottling_temp = history.bottling_temp recipe.bottling_pressure = history.bottling_pressure recipe._ingredients = history._ingredients cloned_from = None try: cloned_from = recipe.cloned_from except Exception, e: pass
def get(self): """ Render the public users list. """ # Try to get users list from memcache instead of datastore users_html = memcache.get('users-content') if not users_html or settings.DEBUG: users_html = self.render('users-content.html', {'users': UserPrefs.all()}, write_to_stream=False) memcache.set('users-content', users_html, self.CACHE_TIME) self.render('users.html', {'users_content': users_html})
def user(self): """Returns currently logged in user""" user = None auth_id = self.session.get('auth_id') # Do we have user session info set by auth handler? if auth_id: user = UserPrefs.get(auth_id) if not user: del self.session['auth_id'] return user
def get(self, username, recipe_slug): publicuser = UserPrefs.all().filter('name = ', username).get() if not publicuser: self.abort(404) recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug =', recipe_slug)\ .get() if not recipe: self.abort(404) self.render_xml(recipe.beerxml)
def get(self): """ Render the public users list. """ # Try to get users list from memcache instead of datastore users_html = memcache.get('users-content') if not users_html or settings.DEBUG: users_html = self.render('users-content.html', { 'users': UserPrefs.all() }, write_to_stream=False) memcache.set('users-content', users_html, self.CACHE_TIME) self.render('users.html', { 'users_content': users_html })
def get(self): """ Render the donation page. This includes a form to process a credit card and show the top donating users. """ # Get a list of the top 10 users who donated top_users = UserPrefs.all()\ .filter('donated >', 0)\ .order('-donated')\ .fetch(10) self.render('donate.html', { 'STRIPE_PUBLIC_KEY': settings.STRIPE_PUBLIC_KEY, 'top_users': top_users, 'success': self.request.get('success') })
def handle_error(request, response, exception, code, msg): """ Render an error template with a code and message. """ logging.exception(exception) # Try to get the current user session_store = sessions.get_store(request=request) session = session_store.get_session() auth_id = session.get("auth_id") user = None if auth_id: user = UserPrefs.get(auth_id) # Render and error template template = get_template("error.html") response.out.write(template.render({"user": user, "code": code, "message": msg}))
def get_recipe(self, request): """ Get a recipe by user name and recipe slug. """ publicuser = UserPrefs.all().filter('name =', request.user_name).get() if not publicuser: raise endpoints.NotFoundException(USER_NOT_FOUND) recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug =', request.slug)\ .get() if not recipe: raise endpoints.NotFoundException(RECIPE_NOT_FOUND) return recipe_to_response(recipe)
def get_users(self, request): """ Get a list of users. """ offset, limit = get_limits(request) query = UserPrefs.all() if request.order == apimessages.UserOrder.NAME: query = query.order('name') elif request.order == apimessages.UserOrder.JOINED: query = query.order('-joined') users = query.fetch(limit, offset=offset) items = [] for user in users: items.append(user_to_response(user)) return apimessages.UserListResponse(**{'items': items})
def post(self): """ Import a new recipe or list of recipes from BeerXML to the currently logged in user's account. """ user = UserPrefs.get() recipesxml = self.request.POST['file'].value for recipe in Recipe.new_from_beerxml(recipesxml): recipe.owner = user recipe.slug = generate_usable_slug(recipe) recipe.update_cache(); key = recipe.put() action = UserAction() action.owner = user action.object_id = key.id() action.type = action.TYPE_RECIPE_CREATED action.put() self.redirect('/users/' + user.name + '/recipes')
def post(self): """ Import a new recipe or list of recipes from BeerXML to the currently logged in user's account. """ user = UserPrefs.get() recipesxml = self.request.POST['file'].value for recipe in Recipe.new_from_beerxml(recipesxml): recipe.owner = user recipe.slug = generate_usable_slug(recipe) recipe.update_cache() key = recipe.put() action = UserAction() action.owner = user action.object_id = key.id() action.type = action.TYPE_RECIPE_CREATED action.put() self.redirect('/users/' + user.name + '/recipes')
def get_users(self, request): """ Get a list of users. """ offset, limit = get_limits(request) query = UserPrefs.all() if request.order == apimessages.UserOrder.NAME: query = query.order('name') elif request.order == apimessages.UserOrder.JOINED: query = query.order('-joined') users = query.fetch(limit, offset=offset) items = [] for user in users: items.append(user_to_response(user)) return apimessages.UserListResponse(**{ 'items': items })
def post(self): """ Send a new message to a user. """ user = self.user if not user: return render_json(self, { 'status': 'error', 'error': 'User not logged in' }) recipient_name = cgi.escape(self.request.get('recipient')) body = cgi.escape(self.request.get('body')) recipient = UserPrefs.all()\ .filter('name =', recipient_name)\ .get() if not recipient: return render_json( self, { 'status': 'error', 'error': 'Recipient %(recipient_name)s not found' % locals() }) msg = Message() msg.user_from = user msg.user_to = recipient msg.body = body msg.put() # TODO: put this into a transaction recipient.unread_messages += 1 recipient.put() self.render_json({'status': 'ok'})
def post(self): """ Send a new message to a user. """ user = self.user if not user: return render_json(self, { 'status': 'error', 'error': 'User not logged in' }) recipient_name = cgi.escape(self.request.get('recipient')) body = cgi.escape(self.request.get('body')) recipient = UserPrefs.all()\ .filter('name =', recipient_name)\ .get() if not recipient: return render_json(self, { 'status': 'error', 'error': 'Recipient %(recipient_name)s not found' % locals() }) msg = Message() msg.user_from = user msg.user_to = recipient msg.body = body msg.put() # TODO: put this into a transaction recipient.unread_messages += 1 recipient.put() self.render_json({ 'status': 'ok' })
def get(self, username=None, recipe_slug=None): """ Render the basic recipe history list for the given recipe. """ if not username or not recipe_slug: self.abort(404) publicuser = UserPrefs.all().filter('name =', username).get() if not publicuser: self.abort(404) recipe = Recipe.all()\ .filter('slug = ', recipe_slug)\ .filter('owner =', publicuser)\ .get() if not recipe: self.abort(404) history = RecipeHistory.all()\ .ancestor(recipe)\ .order('-created')\ .fetch(20) # The list of entries we'll use to populate the template along with # the current recipe as the first entry entries = [{ 'recipe': recipe, 'edited': recipe.edited, 'slug': recipe.slug, 'customtag': 'Most Recent', 'show_snippet': True }] # Check if there is any history to diff with if len(history) > 0: entries[0]['differences'] = self.delete_ignored_keys( recipe.diff(history[0])) else: entries[0]['first'] = True # Get a list of differences in the history. Use reduce with a function # that returns the right operand to simply find pairwise differences. differences = [] def diff(left, right): differences.append(left.diff(right)) return right # Make sure reduce isn't called with no history (throws exception) reduce(diff, history) if len(history) > 0 else None # Start going through the history looking at differences to decide how # we plan on displaying the info to the user (using a snippet or not) for i in range(len(differences)): # Make sure this entry has differences before further processing if self.is_empty(differences[i]): continue # Set some required properties for the snippet to work history[i].owner = publicuser history[i].slug = recipe.slug + '/history/' + str( history[i].key().id()) # Create the entry entry = { 'recipe': history[i], 'differences': self.delete_ignored_keys(differences[i]), 'edited': history[i].created, 'slug': history[i].slug, 'show_snippet': False } # Check if the name or description changed and we should show # a snippet for snippetItem in RecipeHistoryHandler.SNIPPET_ITEMS: if snippetItem in differences[i][2]: entry['show_snippet'] = True # Make sure the color, ibu, and alcohol were created if not hasattr(history[i], 'color'): history[i].update_cache() break # Save the entry entries.append(entry) # Add the final entry only if it's the original recipe, otherwise it # will be a version that should have diffs but we didn't generate any. if len(history) > 0: last = history[-1] delta = timedelta(seconds=1) if recipe.created - delta < last.created < recipe.created + delta: last.owner = publicuser last.slug = recipe.slug + '/history/' + str(last.key().id()) if not hasattr(last, 'color'): last.update_cache() entries.append({ 'recipe': last, 'edited': last.created, 'slug': last.slug, 'customtag': 'Original', 'first': True }) # Perform a second pass in reverse to check for large changes that # should show up as a snippet but were missed in the pairwise # checking above. entries[-1]['show_snippet'] = True for i in range(len(entries) - 1, 0, -1): # Check if the snippet is already showing if entries[i]['show_snippet']: last_snippet = entries[i]['recipe'] continue # Check if the name, description, color, ibu, or alcohol changed # and we should show a snippet for snippetItem in RecipeHistoryHandler.IGNORED_KEYS: if not hasattr(entries[i]['recipe'], snippetItem): continue attr = getattr(entries[i]['recipe'], snippetItem) if type(attr) != int and type(attr) != float: continue # See if the change was more than 10%, then show the # snippet, else show the orb try: change = float(attr) / getattr(last_snippet, snippetItem) except: change = 2 logging.info(snippetItem) logging.info(change) if change < 0.9 or change > 1.1: last_snippet = entries[i]['recipe'] entries[i]['show_snippet'] = True break # Stop the template from performing another query for the username # when it tries to render the recipe recipe.owner = publicuser self.render('recipe-history.html', { 'publicuser': publicuser, 'recipe': recipe, 'entries': entries })
def get(self): """ Render the index page. Currently this renders a 'Coming soon' landing page that will eventually be replaced with a proper home page. """ user = self.user if user: # Try to get rendered output from memcache rendered = memcache.get('dashboard-' + user.user_id) if rendered and not settings.DEBUG: return self.response.out.write(rendered) # Fetch following users following = user.following_users\ .order('name')\ .fetch(100) user_keys = [user.key()] + [u.key() for u in following] # Start async fetch of top recipes top_recipes = Recipe.all()\ .filter('owner IN', user_keys)\ .order('-likes_count')\ .run(limit=15) # Get and process interesting events interesting_events = UserAction.all()\ .filter('owner IN', user_keys)\ .order('-created')\ .fetch(15) object_ids = UserAction.gather_object_ids(interesting_events) object_ids['users'] = [ id for id in object_ids['users'] if id not in [user.key().id()] + user.following ] # Start async fetch of relevant recipes recipes = db.get_async( [Key.from_path('Recipe', id) for id in object_ids['recipes']]) # Convert iterators to lists of items in memory and setup a map # of user id -> user for easy lookups following = list(following) top_recipes = list(top_recipes) user_map = {user.key().id(): user} for u in following: user_map[u.key().id()] = u if object_ids['users']: for u in UserPrefs.get_by_id(object_ids['users']): user_map[u.key().id()] = u # Setup a map of recipe id -> recipe for easy lookups recipe_map = {} for r in recipes.get_result(): recipe_map[r.key().id()] = r # Render and cache for 1 minute memcache.set( 'dashboard-' + user.user_id, self.render( 'dashboard.html', { 'following': following, 'user_map': user_map, 'recipe_map': recipe_map, 'top_recipes': top_recipes, 'interesting_events': interesting_events }), 60) else: # Try to get rendered output from memcache rendered = memcache.get('index') if rendered and not settings.DEBUG: return self.response.out.write(rendered) recipes = Recipe.all()\ .order('-likes_count')\ .run(limit=15) # Render and cache for 15 minutes memcache.set('index', self.render('index.html', {'recipes': recipes}), 900)
def get(self, provider): # Did we get an error? error = self.request.get('error') if error: raise AuthProviderResponseError(error, provider) # At this point the user is successfully logged in, but we need to # get an access token in order to call the API which gets user # information like id, name, avatar, email, etc. Then we need # to actually call that API, and use the response to create a # user object within our data store. # Get the access code so we can exchange it for a token code = self.request.get('code') # Get the CSRF state state = self.request.get('state') if self.session['login_csrf'] != state: logging.warning("Login aborted due to possible CSRF!") return self.abort() # Get the access token using the access code key = getattr(settings, provider.upper() + '_OAUTH_KEY') secret = getattr(settings, provider.upper() + '_OAUTH_SECRET') payload = { 'code': code, 'client_id': key, 'client_secret': secret, 'redirect_uri': self.callback_url(provider), 'grant_type': 'authorization_code' } resp = urlfetch.fetch( url=AUTH_URLS[provider][1], payload=urlencode(payload), method=urlfetch.POST, headers={'Content-Type': 'application/x-www-form-urlencoded'}) if provider in ['google', 'windows_live']: auth_info = json.loads(resp.content) elif provider in ['facebook']: auth_info = dict(urlparse.parse_qsl(resp.content)) else: raise NotImplementedError( 'Not sure how to parse access token response for ' + provider) logging.info(str(auth_info)) # Use access token to make an API call to get user info user_info = getattr(self, '_' + provider + '_user_info')(auth_info) # Create or update a user object in the data store auth_id = provider + ":" + str(user_info['id']) UserPrefs.create_or_update(auth_id, user_info, auth_info) # Update session self.session['auth_id'] = auth_id self.redirect(self.session.pop('next', '/dashboard'))
def get(self, provider): # Did we get an error? error = self.request.get('error') if error: raise AuthProviderResponseError(error, provider) # At this point the user is successfully logged in, but we need to # get an access token in order to call the API which gets user # information like id, name, avatar, email, etc. Then we need # to actually call that API, and use the response to create a # user object within our data store. # Get the access code so we can exchange it for a token code = self.request.get('code') # Get the CSRF state state = self.request.get('state') if self.session['login_csrf'] != state: logging.warning("Login aborted due to possible CSRF!") return self.abort() # Get the access token using the access code key = getattr(settings, provider.upper() + '_OAUTH_KEY') secret = getattr(settings, provider.upper() + '_OAUTH_SECRET') payload = { 'code': code, 'client_id': key, 'client_secret': secret, 'redirect_uri': self.callback_url(provider), 'grant_type': 'authorization_code' } resp = urlfetch.fetch( url=AUTH_URLS[provider][1], payload=urlencode(payload), method=urlfetch.POST, headers={'Content-Type': 'application/x-www-form-urlencoded'} ) if provider in ['google', 'windows_live']: auth_info = json.loads(resp.content) elif provider in ['facebook']: auth_info = dict(urlparse.parse_qsl(resp.content)) else: raise NotImplementedError('Not sure how to parse access token response for ' + provider) logging.info(str(auth_info)) # Use access token to make an API call to get user info user_info = getattr(self, '_' + provider + '_user_info')(auth_info) # Create or update a user object in the data store auth_id = provider + ":" + str(user_info['id']) UserPrefs.create_or_update(auth_id, user_info, auth_info) # Update session self.session['auth_id'] = auth_id self.redirect(self.session.pop('next', '/dashboard'))
def get(self): """ Render the index page. Currently this renders a 'Coming soon' landing page that will eventually be replaced with a proper home page. """ user = self.user if user: # Try to get rendered output from memcache rendered = memcache.get('dashboard-' + user.user_id) if rendered and not settings.DEBUG: return self.response.out.write(rendered) # Fetch following users following = user.following_users\ .order('name')\ .fetch(100) user_keys = [user.key()] + [u.key() for u in following] # Start async fetch of top recipes top_recipes = Recipe.all()\ .filter('owner IN', user_keys)\ .order('-likes_count')\ .run(limit=15) # Get and process interesting events interesting_events = UserAction.all()\ .filter('owner IN', user_keys)\ .order('-created')\ .fetch(15) object_ids = UserAction.gather_object_ids(interesting_events) object_ids['users'] = [id for id in object_ids['users'] if id not in [user.key().id()] + user.following] # Start async fetch of relevant recipes recipes = db.get_async([Key.from_path('Recipe', id) for id in object_ids['recipes']]) # Convert iterators to lists of items in memory and setup a map # of user id -> user for easy lookups following = list(following) top_recipes = list(top_recipes) user_map = { user.key().id(): user } for u in following: user_map[u.key().id()] = u if object_ids['users']: for u in UserPrefs.get_by_id(object_ids['users']): user_map[u.key().id()] = u # Setup a map of recipe id -> recipe for easy lookups recipe_map = {} for r in recipes.get_result(): recipe_map[r.key().id()] = r # Render and cache for 1 minute memcache.set('dashboard-' + user.user_id, self.render('dashboard.html', { 'following': following, 'user_map': user_map, 'recipe_map': recipe_map, 'top_recipes': top_recipes, 'interesting_events': interesting_events }), 60) else: # Try to get rendered output from memcache rendered = memcache.get('index') if rendered and not settings.DEBUG: return self.response.out.write(rendered) recipes = Recipe.all()\ .order('-likes_count')\ .run(limit=15) # Render and cache for 15 minutes memcache.set('index', self.render('index.html', { 'recipes': recipes }), 900)
def get(self): user = self.user # Try to get rendered output from memcache rendered = memcache.get('dashboard-' + user.user_id) if rendered and not settings.DEBUG: return self.response.out.write(rendered) # Fetch following users following = user.following_users\ .order('name')\ .fetch(100) user_keys = [user.key()] + [u.key() for u in following] # Start async fetch of top recipes top_recipes = Recipe.all()\ .filter('owner IN', user_keys)\ .order('-grade')\ .run(limit=15) # Get and process interesting events interesting_events = UserAction.all()\ .filter('owner IN', user_keys)\ .order('-created')\ .fetch(15) object_ids = UserAction.gather_object_ids(interesting_events) object_ids['users'] = [id for id in object_ids['users'] if id not in [user.key().id()] + user.following] # Start async fetch of relevant recipes recipes = db.get_async([Key.from_path('Recipe', id) for id in object_ids['recipes']]) # Start async fetch of relevant brews brews = db.get_async([Key.from_path('Brew', id) for id in object_ids['brews']]) # Convert iterators to lists of items in memory and setup a map # of user id -> user for easy lookups following = list(following) top_recipes = list(top_recipes) user_map = { user.key().id(): user } for u in following: user_map[u.key().id()] = u if object_ids['users']: for u in UserPrefs.get_by_id(object_ids['users']): user_map[u.key().id()] = u # Setup a map of brew id -> brew for easy lookups brew_map = {} brew_recipe_ids = set() for b in brews.get_result(): brew_recipe_ids.add(b.recipe_key.id()) brew_map[b.key().id()] = b # Async fetch of any recipes brews reference that weren't # included in the recipe fetch above... brew_recipes = db.get_async([Key.from_path('Recipe', id) for id in brew_recipe_ids if id not in object_ids['recipes']]) # Setup a map of recipe id -> recipe for easy lookups recipe_map = {} for r in recipes.get_result(): recipe_map[r.key().id()] = r for r in brew_recipes.get_result(): recipe_map[r.key().id()] = r # Render and cache for 1 minute memcache.set('dashboard-' + user.user_id, self.render('dashboard.html', { 'following': following, 'user_map': user_map, 'recipe_map': recipe_map, 'brew_map': brew_map, 'top_recipes': top_recipes, 'interesting_events': interesting_events }), self.CACHE_TIME)
def process(self, action, username=None, recipe_slug=None): """ Process a request to add or remove a user from the liked list. """ user = UserPrefs.get() publicuser = UserPrefs.all()\ .filter('name = ', username)\ .get() if not publicuser: self.render_json({ 'status': 'error', 'error': 'User not found' }) return recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug =', recipe_slug)\ .get() if not recipe: self.render_json({ 'status': 'error', 'error': 'Recipe not found' }) return if action == 'post': if user.user_id not in recipe.likes: recipe.likes.append(user.user_id) recipe.put() existing = UserAction.all()\ .filter('owner =', user)\ .filter('type =', UserAction.TYPE_RECIPE_LIKED)\ .filter('object_id =', recipe.key().id())\ .count() if not existing: user_action = UserAction() user_action.owner = user user_action.type = user_action.TYPE_RECIPE_LIKED user_action.object_id = recipe.key().id() user_action.put() elif action == 'delete': if user.user_id in recipe.likes: recipe.likes.remove(user.user_id) recipe.put() existing = UserAction.all()\ .filter('owner =', user)\ .filter('type =', UserAction.TYPE_RECIPE_LIKED)\ .filter('object_id =', recipe.key().id())\ .get() if existing: existing.delete() return self.render_json({ 'status': 'ok', 'likes': len(recipe.likes) })
def get(self): user = self.user # Try to get rendered output from memcache rendered = memcache.get('dashboard-' + user.user_id) if rendered and not settings.DEBUG: return self.response.out.write(rendered) # Fetch following users following = user.following_users\ .order('name')\ .fetch(100) user_keys = [user.key()] + [u.key() for u in following] # Start async fetch of top recipes top_recipes = Recipe.all()\ .filter('owner IN', user_keys)\ .order('-grade')\ .run(limit=15) # Get and process interesting events interesting_events = UserAction.all()\ .filter('owner IN', user_keys)\ .order('-created')\ .fetch(15) object_ids = UserAction.gather_object_ids(interesting_events) object_ids['users'] = [ id for id in object_ids['users'] if id not in [user.key().id()] + user.following ] # Start async fetch of relevant recipes recipes = db.get_async( [Key.from_path('Recipe', id) for id in object_ids['recipes']]) # Start async fetch of relevant brews brews = db.get_async( [Key.from_path('Brew', id) for id in object_ids['brews']]) # Convert iterators to lists of items in memory and setup a map # of user id -> user for easy lookups following = list(following) top_recipes = list(top_recipes) user_map = {user.key().id(): user} for u in following: user_map[u.key().id()] = u if object_ids['users']: for u in UserPrefs.get_by_id(object_ids['users']): user_map[u.key().id()] = u # Setup a map of brew id -> brew for easy lookups brew_map = {} brew_recipe_ids = set() for b in brews.get_result(): brew_recipe_ids.add(b.recipe_key.id()) brew_map[b.key().id()] = b # Async fetch of any recipes brews reference that weren't # included in the recipe fetch above... brew_recipes = db.get_async([ Key.from_path('Recipe', id) for id in brew_recipe_ids if id not in object_ids['recipes'] ]) # Setup a map of recipe id -> recipe for easy lookups recipe_map = {} for r in recipes.get_result(): recipe_map[r.key().id()] = r for r in brew_recipes.get_result(): recipe_map[r.key().id()] = r # Render and cache for 1 minute memcache.set( 'dashboard-' + user.user_id, self.render( 'dashboard.html', { 'following': following, 'user_map': user_map, 'recipe_map': recipe_map, 'brew_map': brew_map, 'top_recipes': top_recipes, 'interesting_events': interesting_events }), self.CACHE_TIME)
def post(self, username=None, recipe_slug=None): publicuser = UserPrefs.all()\ .filter('name = ', username)\ .get() if not publicuser: self.render_json({'status': 'error', 'error': 'User not found'}) return recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug =', recipe_slug)\ .get() if not recipe: self.render_json({'status': 'error', 'error': 'Recipe not found'}) return new_recipe = Recipe( **{ 'owner': self.user, 'cloned_from': recipe, 'color': recipe.color, 'ibu': recipe.ibu, 'alcohol': recipe.alcohol, 'name': recipe.name, 'description': recipe.description, 'type': recipe.type, 'category': recipe.category, 'style': recipe.style, 'batch_size': recipe.batch_size, 'boil_size': recipe.boil_size, 'bottling_temp': recipe.bottling_temp, 'bottling_pressure': recipe.bottling_pressure, 'steep_efficiency': recipe.steep_efficiency, 'mash_efficiency': recipe.mash_efficiency, 'primary_days': recipe.primary_days, 'primary_temp': recipe.primary_temp, 'secondary_days': recipe.secondary_days, 'secondary_temp': recipe.secondary_temp, 'tertiary_days': recipe.tertiary_days, 'tertiary_temp': recipe.tertiary_temp, 'aging_days': recipe.aging_days, '_ingredients': recipe._ingredients }) new_recipe.slug = generate_usable_slug(new_recipe) new_recipe.put() new_recipe.update_grade() new_recipe.put() # Update recipe ranking for sorting recipe.update_grade() recipe.put() action = UserAction() action.owner = self.user action.type = action.TYPE_RECIPE_CLONED action.object_id = new_recipe.key().id() action.put() return self.render_json({'status': 'ok', 'redirect': new_recipe.url})
def get(self, username=None, recipe_slug=None): """ Render the basic recipe history list for the given recipe. """ if not username or not recipe_slug: self.abort(404) publicuser = UserPrefs.all().filter('name =', username).get() if not publicuser: self.abort(404) recipe = Recipe.all()\ .filter('slug = ', recipe_slug)\ .filter('owner =', publicuser)\ .get() if not recipe: self.abort(404) history = RecipeHistory.all()\ .ancestor(recipe)\ .order('-created')\ .fetch(20) # The list of entries we'll use to populate the template along with # the current recipe as the first entry entries = [{ 'recipe': recipe, 'edited': recipe.edited, 'slug': recipe.slug, 'customtag': 'Most Recent', 'show_snippet': True }] # Check if there is any history to diff with if len(history) > 0: entries[0]['differences'] = self.delete_ignored_keys(recipe.diff(history[0])) else: entries[0]['first'] = True # Get a list of differences in the history. Use reduce with a function # that returns the right operand to simply find pairwise differences. differences = [] def diff(left, right): differences.append(left.diff(right)) return right # Make sure reduce isn't called with no history (throws exception) reduce(diff, history) if len(history) > 0 else None # Start going through the history looking at differences to decide how # we plan on displaying the info to the user (using a snippet or not) for i in range(len(differences)): # Make sure this entry has differences before further processing if self.is_empty(differences[i]): continue # Set some required properties for the snippet to work history[i].owner = publicuser history[i].slug = recipe.slug + '/history/' + str(history[i].key().id()) # Create the entry entry = { 'recipe': history[i], 'differences': self.delete_ignored_keys(differences[i]), 'edited': history[i].created, 'slug': history[i].slug, 'show_snippet': False } # Check if the name or description changed and we should show # a snippet for snippetItem in RecipeHistoryHandler.SNIPPET_ITEMS: if snippetItem in differences[i][2]: entry['show_snippet'] = True # Make sure the color, ibu, and alcohol were created if not hasattr(history[i], 'color'): history[i].update_cache() break # Save the entry entries.append(entry) # Add the final entry only if it's the original recipe, otherwise it # will be a version that should have diffs but we didn't generate any. if len(history) > 0: last = history[-1] delta = timedelta(seconds=1) if recipe.created - delta < last.created < recipe.created + delta: last.owner = publicuser last.slug = recipe.slug + '/history/' + str(last.key().id()) if not hasattr(last, 'color'): last.update_cache() entries.append({ 'recipe': last, 'edited': last.created, 'slug': last.slug, 'customtag': 'Original', 'first': True }) # Perform a second pass in reverse to check for large changes that # should show up as a snippet but were missed in the pairwise # checking above. entries[-1]['show_snippet'] = True for i in range(len(entries) - 1, 0, -1): # Check if the snippet is already showing if entries[i]['show_snippet']: last_snippet = entries[i]['recipe'] continue # Check if the name, description, color, ibu, or alcohol changed # and we should show a snippet for snippetItem in RecipeHistoryHandler.IGNORED_KEYS: if not hasattr(entries[i]['recipe'], snippetItem): continue attr = getattr(entries[i]['recipe'], snippetItem) if type(attr) != int and type(attr) != float: continue # See if the change was more than 10%, then show the # snippet, else show the orb try: change = float(attr) / getattr(last_snippet, snippetItem) except: change = 2 logging.info(snippetItem) logging.info(change) if change < 0.9 or change > 1.1: last_snippet = entries[i]['recipe'] entries[i]['show_snippet'] = True break # Stop the template from performing another query for the username # when it tries to render the recipe recipe.owner = publicuser self.render('recipe-history.html', { 'publicuser': publicuser, 'recipe': recipe, 'entries': entries })
def post(self, username=None, recipe_slug=None): publicuser = UserPrefs.all()\ .filter('name = ', username)\ .get() if not publicuser: self.render_json({ 'status': 'error', 'error': 'User not found' }) return recipe = Recipe.all()\ .filter('owner =', publicuser)\ .filter('slug =', recipe_slug)\ .get() if not recipe: self.render_json({ 'status': 'error', 'error': 'Recipe not found' }) return new_recipe = Recipe(**{ 'owner': self.user, 'cloned_from': recipe, 'color': recipe.color, 'ibu': recipe.ibu, 'alcohol': recipe.alcohol, 'name': recipe.name, 'description': recipe.description, 'type': recipe.type, 'category': recipe.category, 'style': recipe.style, 'batch_size': recipe.batch_size, 'boil_size': recipe.boil_size, 'bottling_temp': recipe.bottling_temp, 'bottling_pressure': recipe.bottling_pressure, 'steep_efficiency': recipe.steep_efficiency, 'mash_efficiency': recipe.mash_efficiency, 'primary_days': recipe.primary_days, 'primary_temp': recipe.primary_temp, 'secondary_days': recipe.secondary_days, 'secondary_temp': recipe.secondary_temp, 'tertiary_days': recipe.tertiary_days, 'tertiary_temp': recipe.tertiary_temp, 'aging_days': recipe.aging_days, '_ingredients': recipe._ingredients }) new_recipe.slug = generate_usable_slug(new_recipe) new_recipe.put() new_recipe.update_grade() new_recipe.put() # Update recipe ranking for sorting recipe.update_grade() recipe.put() action = UserAction() action.owner = self.user action.type = action.TYPE_RECIPE_CLONED action.object_id = new_recipe.key().id() action.put() return self.render_json({ 'status': 'ok', 'redirect': new_recipe.url })