def get(self, linkKeyStr): if conf.isDev: logging.debug('SlicesFromCreator.get() linkKeyStr=' + str(linkKeyStr)) # Collect inputs httpRequestId = os.environ.get(conf.REQUEST_LOG_ID) responseData = {'success': False, 'httpRequestId': httpRequestId} cookieData = httpServer.validate(self.request, self.request.GET, responseData, self.response, crumbRequired=False, signatureRequired=False) if not cookieData.valid(): return userId = cookieData.id() # Retrieve and check linkKey linkKeyRecord = linkKey.LinkKey.get_by_id(linkKeyStr) if conf.isDev: logging.debug('SlicesFromCreator.get() linkKeyRecord=' + str(linkKeyRecord)) if (linkKeyRecord is None) or (linkKeyRecord.destinationType != conf.BUDGET_CLASS_NAME): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.BAD_LINK) budgetId = linkKeyRecord.destinationId # Check that user is budget-creator budgetRecord = budget.Budget.get_by_id(int(budgetId)) if conf.isDev: logging.debug('SlicesFromCreator.get() budgetRecord=' + str(budgetRecord)) if budgetRecord is None: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='budget is null') if (budgetRecord.creator != userId): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.NOT_OWNER) # Retrieve all slices for this budget and creator sliceRecords = slice.Slice.query( slice.Slice.budgetId == budgetId, slice.Slice.creator == userId, slice.Slice.fromEditPage == True).fetch() slicesByTitle = sorted(sliceRecords, key=lambda a: a.title) sliceDisplays = [ httpServerBudget.sliceToDisplay(a, userId) for a in slicesByTitle ] # Display slices data responseData = {'success': True, 'slices': sliceDisplays} httpServer.outputJson(cookieData, responseData, self.response)
def get(self, linkKeyStr): if conf.isDev: logging.debug('SlicesForUser.get() linkKeyStr=' + linkKeyStr) # Collect inputs httpRequestId = os.environ.get(conf.REQUEST_LOG_ID) responseData = {'success': False, 'httpRequestId': httpRequestId} cookieData = httpServer.validate(self.request, self.request.GET, responseData, self.response, crumbRequired=False, signatureRequired=False) if not cookieData.valid(): return userId = cookieData.id() # Retrieve and check linkKey linkKeyRecord = linkKey.LinkKey.get_by_id(linkKeyStr) if (linkKeyRecord is None) or (linkKeyRecord.destinationType != conf.BUDGET_CLASS_NAME): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.BAD_LINK) budgetId = linkKeyRecord.destinationId # Retrieve all slices for this budget and voter sliceVoteRecord = slice.SliceVotes.get(budgetId, userId) sliceRecordKeys = [ ndb.Key(slice.Slice, sliceId) for sliceId, size in sliceVoteRecord.slices.iteritems() ] if sliceVoteRecord else [] sliceRecords = ndb.get_multi(sliceRecordKeys) if conf.isDev: logging.debug('SlicesForUser.get() sliceRecords=' + str(sliceRecords)) # TODO: If slice-record does not exist for slice in slice-votes... remove that vote sliceIdToDisplay = { s.key.id(): httpServerBudget.sliceToDisplay(s, userId) for s in sliceRecords if s } votesDisplay = httpServerBudget.sliceVotesToDisplay( sliceVoteRecord, userId) # Display slices data. responseData.update({ 'success': True, 'slices': sliceIdToDisplay, 'votes': votesDisplay }) httpServer.outputJson(cookieData, responseData, self.response)
def post(self): logging.debug(('EditSlice.post()', 'request.body=', self.request.body)) # Collect inputs requestLogId = os.environ.get(conf.REQUEST_LOG_ID) inputData = json.loads(self.request.body) logging.debug(('EditSlice.post()', 'inputData=', inputData)) responseData = {'success': False, 'requestLogId': requestLogId} cookieData = httpServer.validate(self.request, inputData, responseData, self.response) if not cookieData.valid(): return userId = cookieData.id() title = slice.standardizeContent( text.formTextToStored(inputData['title'])) reason = slice.standardizeContent( text.formTextToStored(inputData['reason'])) linkKeyString = inputData['linkKey'] sliceId = inputData.get('sliceId', None) # Null if slice is new logging.debug( ('EditSlice.post()', 'sliceId=', sliceId, 'title=', title, 'reason=', reason, 'linkKeyString=', linkKeyString)) budgetId, loginRequired = retrieveBudgetIdFromLinkKey( cookieData, linkKeyString, responseData, self.response) if budgetId is None: return # Retrieve budget record to check budget creator budgetRec = budget.Budget.get_by_id(int(budgetId)) if budgetRec is None: return httpServer.outputJson( cookieData, responseData, self.response, errorMessage='budget record not found') if budgetRec.creator != userId: return httpServer.outputJson( cookieData, responseData, self.response, errorMessage='budgetRec.creator != userId') # Check slice length if (not title) or (len(title) < conf.minLengthSliceTitle): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.TOO_SHORT) if budgetRec.hideReasons: if reason: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='reasons hidden') else: if (not reason) or (len(reason) < conf.minLengthSliceTitle): return httpServer.outputJson( cookieData, responseData, self.response, errorMessage=conf.REASON_TOO_SHORT) # Delete old slice if it has no votes if sliceId: oldSliceRec = slice.Slice.get_by_id(sliceId) if oldSliceRec is None: return httpServer.outputJson( cookieData, responseData, self.response, errorMessage='slice record not found') if oldSliceRec.budgetId != budgetId: return httpServer.outputJson( cookieData, responseData, self.response, errorMessage='oldSliceRec.budgetId != budgetId') if oldSliceRec.creator != userId: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=NOT_OWNER) if (oldSliceRec.voteCount <= 0): oldSliceRec.key.delete() # Create new slice only if there is existing record/vote sliceRec = slice.Slice.get(budgetId, title, reason) if sliceRec is None: sliceRec = slice.Slice.create(budgetId, title, reason, creator=userId) # Store slice record sliceRec.fromEditPage = True sliceRec.put() # Display updated slices sliceDisplay = httpServerBudget.sliceToDisplay(sliceRec, userId) responseData.update({'success': True, 'slice': sliceDisplay}) httpServer.outputJson(cookieData, responseData, self.response)
def post(self): if conf.isDev: logging.debug('SliceSetSize.post() request.body=' + self.request.body) # Collect inputs requestLogId = os.environ.get(conf.REQUEST_LOG_ID) inputData = json.loads(self.request.body) if conf.isDev: logging.debug('SliceSetSize.post() inputData=' + str(inputData)) sliceId = inputData.get('sliceId', None) size = int(inputData.get('size', -1)) linkKeyString = inputData['linkKey'] if conf.isDev: logging.debug('SliceSetSize.post() sliceId=' + str(sliceId) + ' size=' + str(size) + ' linkKeyString=' + str(linkKeyString)) responseData = {'success': False, 'requestLogId': requestLogId} cookieData = httpServer.validate(self.request, inputData, responseData, self.response) if not cookieData.valid(): return userId = cookieData.id() if not sliceId: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='sliceId is null') # Enforce size limits if (size < conf.SLICE_SIZE_MIN) or (conf.SLICE_SIZE_MAX < size): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='size out of bounds') # Retrieve link-key record budgetId, loginRequired = retrieveBudgetIdFromLinkKey( cookieData, linkKeyString, responseData, self.response) if budgetId is None: return # Retrieve slice-record to get title and reason sliceRecord = slice.Slice.get_by_id(sliceId) if conf.isDev: logging.debug('SliceSetSize.post() sliceRecord=' + str(sliceRecord)) if sliceRecord is None: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='slice record not found') if (sliceRecord.budgetId != budgetId): return httpServer.outputJson( cookieData, responseData, self.response, errorMessage='sliceId does not match budgetId') title = sliceRecord.title reason = sliceRecord.reason # Retrieve budget record to check whether budget is frozen budgetRec = budget.Budget.get_by_id(int(budgetId)) if budgetRec is None: return httpServer.outputJson( cookieData, responseData, self.response, errorMessage='budget record not found') if budgetRec.freezeUserInput: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.FROZEN) # Update slice-vote, and vote-aggregates sliceRecord, voteRecord, success = slice.vote(budgetId, title, reason, size, userId) if conf.isDev: logging.debug('SliceSetSize.post() success=' + str(success) + ' sliceRecord=' + str(sliceRecord) + ' voteRecord=' + str(voteRecord)) if not success: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.OVER_BUDGET) if sliceRecord is None: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='sliceRecord is null') # Display updated slice sliceDisplay = httpServerBudget.sliceToDisplay( sliceRecord, userId) if sliceRecord else None voteRecord = httpServerBudget.sliceVotesToDisplay(voteRecord, userId) responseData.update({ 'success': True, 'slice': sliceDisplay, 'vote': voteRecord }) httpServer.outputJson(cookieData, responseData, self.response)
def post(self): logging.debug('SliceVote.post() request.body=' + self.request.body) # Collect inputs requestLogId = os.environ.get(conf.REQUEST_LOG_ID) inputData = json.loads(self.request.body) logging.debug(('SliceVote.post()', 'inputData=', inputData)) title = slice.standardizeContent(inputData.get('title', None)) reason = slice.standardizeContent(inputData.get('reason', None)) size = int(inputData.get('size', -1)) linkKeyString = inputData['linkKey'] sliceId = inputData.get('sliceId', None) logging.debug(('SliceVote.post()', 'title=', title, 'reason=', reason, 'linkKeyString=', linkKeyString)) responseData = {'success': False, 'requestLogId': requestLogId} cookieData = httpServer.validate(self.request, inputData, responseData, self.response) if not cookieData.valid(): return userId = cookieData.id() # Enforce size limits if (size < conf.SLICE_SIZE_MIN) or (conf.SLICE_SIZE_MAX < size): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='Size out of bounds') # Retrieve link-key record budgetId, loginRequired = retrieveBudgetIdFromLinkKey( cookieData, linkKeyString, responseData, self.response) if budgetId is None: return # Retrieve budget record to check whether budget is frozen budgetRec = budget.Budget.get_by_id(int(budgetId)) if budgetRec is None: return httpServer.outputJson( cookieData, responseData, self.response, errorMessage='budget record not found') if budgetRec.freezeUserInput: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.FROZEN) # Enforce minimum title/reason lengths if (title is None) or (len(title) < conf.minLengthSliceTitle): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.TOO_SHORT) if budgetRec.hideReasons: if reason: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='reasons hidden') else: if (reason is None) or (len(reason) < conf.minLengthSliceReason): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.TOO_SHORT) # Do not need to prevent duplicate titles here, because title & detail are already deduplicated by storage key # Storage-side deduplication would drop some user input, so have client-side warn if title is duplicate # Un-vote for old title and reason, if different newSliceId = slice.Slice.toKeyId(budgetId, title, reason) logging.debug(('SliceVote.post()', 'sliceId=', sliceId)) logging.debug(('SliceVote.post()', 'newSliceId=', newSliceId)) if sliceId and (sliceId != newSliceId): oldSliceRecord = slice.Slice.get_by_id( sliceId) if sliceId else None logging.debug( ('SliceVote.post()', 'oldSliceRecord=', oldSliceRecord)) if oldSliceRecord: if (oldSliceRecord.budgetId != budgetId): return httpServer.outputJson( cookieData, responseData, self.response, errorMessage='sliceId does not match budgetId') titleOld = oldSliceRecord.title reasonOld = oldSliceRecord.reason sliceRecord, voteRecord, success = slice.vote( budgetId, titleOld, reasonOld, 0, userId) if not success: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='un-vote failed') # Update slice and vote sliceRecord, voteRecord, success = slice.vote(budgetId, title, reason, size, userId) if not success: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.OVER_BUDGET) # Display updated slice sliceDisplay = httpServerBudget.sliceToDisplay(sliceRecord, userId) responseData.update({'success': success, 'slice': sliceDisplay}) httpServer.outputJson(cookieData, responseData, self.response)
def post(self, linkKeyStr): logging.debug(('SliceSizeReasons', 'linkKeyStr=', linkKeyStr)) # Collect inputs inputData = json.loads(self.request.body) logging.debug(('SliceSizeReasons', 'inputData=', inputData)) title = budget.standardizeContent(inputData.get('title', '')) size = int(inputData.get('size', 0)) logging.debug(('SliceSizeReasons', 'title=', title, 'size=', size, 'linkKeyStr=', linkKeyStr)) httpRequestId = os.environ.get(conf.REQUEST_LOG_ID) responseData = {'success': False, 'httpRequestId': httpRequestId} # No user-id required, works for any user with the link-key cookieData = httpServer.validate(self.request, self.request.GET, responseData, self.response, idRequired=False) userId = cookieData.id() if not title: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='title is null') if not size: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='size is null') if (size < conf.SLICE_SIZE_MIN) or (conf.SLICE_SIZE_MAX < size): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='Size out of bounds') # Retrieve and check linkKey linkKeyRecord = linkKey.LinkKey.get_by_id(linkKeyStr) if (linkKeyRecord is None) or (linkKeyRecord.destinationType != conf.BUDGET_CLASS_NAME): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.BAD_LINK) budgetId = linkKeyRecord.destinationId # No need to enforce login-required in GET calls, only on write operations that create/use link-key # But enforcing here because the search is expensive, and part of a write flow if linkKeyRecord.loginRequired and not cookieData.loginId: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.NO_LOGIN) # Check that budget is not frozen, to reduce the cost of unnecessary search budgetRecord = budget.Budget.get_by_id(int(budgetId)) if budgetRecord is None: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='budget is null') if budgetRecord.freezeUserInput: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.FROZEN) if budgetRecord.hideReasons: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='reasons hidden') # Retrieve top-scoring slice-reasons for title slicesByScore = slice.retrieveTopSliceReasonsByScore(budgetId, title, maxSlices=10) logging.debug(('SliceSizeReasons', 'slicesByScore=', slicesByScore)) # TODO: Filter out suggestions that are from the same budget/user # Retrieve SliceVotes # votedSliceIds = [ s.key.id() for sliceId, count in voteRecord.sliceVotes.iteritems() ] if (voteRecord and voteRecord.sliceVotes) else [] # votedSliceIds = set( votedSliceIds ) # slicesFiltered = [ s for s in slicesByScore if s not in votedSliceIds ] # For each slice... sum scores across sizes slicesAndSums = [ SliceAndSums(s, s.sumScoreBelowSize(size), s.sumScoreAboveSize(size)) for s in slicesByScore ] logging.debug(('SliceSizeReasons', 'slicesAndSums=', slicesAndSums)) # Each slice can only have a score-sum below or above, not both slicesAndSumsExclusive = [] for s in slicesAndSums: if (s.sumAbove < s.sumBelow): slicesAndSumsExclusive.append( SliceAndSums(s.record, s.sumBelow, 0)) elif (s.sumBelow < s.sumAbove): slicesAndSumsExclusive.append( SliceAndSums(s.record, 0, s.sumAbove)) logging.debug(('SliceSizeReasons', 'slicesAndSumsExclusive=', slicesAndSumsExclusive)) # Find slice with most votes below/above size sliceBelow = max( slicesAndSumsExclusive, key=lambda s: s.sumBelow) if slicesAndSumsExclusive else None sliceAbove = max( slicesAndSumsExclusive, key=lambda s: s.sumAbove) if slicesAndSumsExclusive else None if sliceBelow and (sliceBelow.sumBelow <= 0): sliceBelow = None if sliceAbove and (sliceAbove.sumAbove <= 0): sliceAbove = None logging.debug(('SliceSizeReasons', 'sliceBelow=', sliceBelow)) logging.debug(('SliceSizeReasons', 'sliceAbove=', sliceAbove)) # Display slices below/above sliceBelowDisplay = httpServerBudget.sliceToDisplay( sliceBelow.record, userId) if sliceBelow else None sliceAboveDisplay = httpServerBudget.sliceToDisplay( sliceAbove.record, userId) if sliceAbove else None responseData.update({ 'success': True, 'sliceSmaller': sliceBelowDisplay, 'sliceBigger': sliceAboveDisplay }) httpServer.outputJson(cookieData, responseData, self.response)
def get(self, linkKeyStr, sliceTitleId): if conf.isDev: logging.debug('SliceReasonResults.get() sliceTitleId=' + str(sliceTitleId) + ' linkKeyStr=' + str(linkKeyStr)) # Collect inputs page = int(self.request.get('page', 0)) httpRequestId = os.environ.get(conf.REQUEST_LOG_ID) responseData = {'success': False, 'httpRequestId': httpRequestId} # No user-id required, works for any user with the link-key cookieData = httpServer.validate(self.request, self.request.GET, responseData, self.response, idRequired=False) userId = cookieData.id() # Retrieve and check linkKey linkKeyRecord = linkKey.LinkKey.get_by_id(linkKeyStr) if (linkKeyRecord is None) or (linkKeyRecord.destinationType != conf.BUDGET_CLASS_NAME): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.BAD_LINK) budgetId = linkKeyRecord.destinationId # No need to enforce login-required in GET calls, only on write operations that create/use link-key # But enforcing here because the search is expensive, and part of a write flow if linkKeyRecord.loginRequired and not cookieData.loginId: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.NO_LOGIN) # Check that budget is valid budgetRecord = budget.Budget.get_by_id(int(budgetId)) if budgetRecord is None: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='budget is null') # Retrieve slice-title record sliceTitleRecord = slice.SliceTitle.get_by_id(sliceTitleId) if sliceTitleRecord is None: return httpServer.outputJson( cookieData, responseData, self.response, errorMessage='sliceTitleRecord is null') if (sliceTitleRecord.budgetId != budgetId): return httpServer.outputJson( cookieData, responseData, self.response, errorMessage='sliceTitleRecord.budgetId mismatch') # Retrieve top-voted slice-reasons for given slice-title maxSlices = 1 if (page <= 0) else 100 slicesOrdered = slice.retrieveTopSliceReasonsByVotes( budgetId, sliceTitleRecord.title, maxSlices=(maxSlices + 1)) if conf.isDev: logging.debug('SliceReasonResults.get() slicesOrdered=' + str(slicesOrdered)) sliceDisplays = [ httpServerBudget.sliceToDisplay(s, userId) for s in slicesOrdered[0:maxSlices] ] hasMore = (maxSlices < len(slicesOrdered)) # Display slices data responseData.update({ 'success': True, 'slices': sliceDisplays, 'hasMoreReasons': hasMore, 'totalBudget': budgetRecord.total, 'medianSize': sliceTitleRecord.medianSize() }) httpServer.outputJson(cookieData, responseData, self.response)
def post(self, linkKeyStr): logging.debug(('SlicesForPrefix', 'linkKeyStr=', linkKeyStr)) # Collect inputs inputData = json.loads(self.request.body) logging.debug(('SliceSizeReasons', 'inputData=', inputData)) title = budget.standardizeContent(inputData.get('title', '')) reason = budget.standardizeContent(inputData.get('reason', '')) logging.debug(('SlicesForPrefix', 'title=', title, 'reason=', reason, 'linkKeyStr=', linkKeyStr)) httpRequestId = os.environ.get(conf.REQUEST_LOG_ID) responseData = {'success': False, 'httpRequestId': httpRequestId} # No user-id required, works for any user with the link-key cookieData = httpServer.validate(self.request, self.request.GET, responseData, self.response, idRequired=False) userId = cookieData.id() # Retrieve and check linkKey linkKeyRecord = linkKey.LinkKey.get_by_id(linkKeyStr) if (linkKeyRecord is None) or (linkKeyRecord.destinationType != conf.BUDGET_CLASS_NAME): return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.BAD_LINK) budgetId = linkKeyRecord.destinationId # No need to enforce login-required in GET calls, only on write operations that create/use link-key # But enforcing here because the search is expensive, and part of a write flow if linkKeyRecord.loginRequired and not cookieData.loginId: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.NO_LOGIN) # Check that budget is not frozen, to reduce the cost of unnecessary search budgetRecord = budget.Budget.get_by_id(int(budgetId)) if budgetRecord is None: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage='budget is null') if budgetRecord.freezeUserInput: return httpServer.outputJson(cookieData, responseData, self.response, errorMessage=conf.FROZEN) # Retrieve best suggested slices for this slice-start sliceStart = ' '.join( [title if title else '', reason if reason else '']) slicesOrdered = slice.retrieveTopSlicesByScoreForStart( budgetId, sliceStart, hideReasons=budgetRecord.hideReasons) logging.debug(('SlicesForPrefix', 'slicesOrdered=', slicesOrdered)) sliceDisplays = [ httpServerBudget.sliceToDisplay(a, userId) for a in slicesOrdered ] # Display slices data responseData.update({'success': True, 'slices': sliceDisplays}) httpServer.outputJson(cookieData, responseData, self.response)