Пример #1
0
    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)
Пример #2
0
    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)
Пример #3
0
    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)
Пример #4
0
    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)
Пример #5
0
    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)
Пример #6
0
    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)
Пример #7
0
    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)
Пример #8
0
    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)