def place_delete(place_id, place_key_str): try: pkey = Place.make_key(place_id, place_key_str) cluster_ratings = ClusterRating.get_list({'place': pkey.urlsafe()}) for cr in cluster_ratings: ClusterRating.delete(cr.key) user_ratings = Rating.get_list({'place': pkey.urlsafe()}) for r in user_ratings: Rating.delete(r.key) res = Place.delete(pkey) except TypeError, e: return None, str(e), 400
def get(self): user_id = logic.get_current_userid(self.request.cookies.get('user')) if user_id is None: self.redirect('/') return rest_key = self.request.GET.get('rest_id') user, status, errcode = logic.user_get(user_id, None) if status != "OK": self.render("error.html", {'error_code': errcode, 'error_string': status, 'lang': LANG}) return discount = Discount() discount.place = Place.make_key(None, rest_key) try: discount = Discount.to_json(discount, None, None) self.render('discount_edit.html', {'is_new': 'True', 'discount': discount, 'user': user, 'lang' : LANG }); except TypeError, e: self.render("error.html", {'error_code': 500, 'error_string': str(e), 'lang': LANG}) return
def rating_count(user_id=None, user_str=None, place_id=None, place_str=None): """ It counts the number of ratings for a user, a place or both. Parameters: - user_id and user_str: key (str = urlsafe) for the PFuser, only one needed - place_id and place_str: key (str = urlsafe) for the Place, only one needed It returns a tuple: - the number of ratings, - the status (a string indicating whether an error occurred) - the http code indicating the type of error, if any """ try: user_key = PFuser.make_key(user_id, user_str) place_key = Place.make_key(place_id, place_str) count = Rating.count(user_key, place_key) except TypeError as e: return None, str(e), 400 return count, "OK", 200
def place_update(in_place, place_id, place_key_str): """ It updates the place. Parameters: - in_place: the Place containing the information to update - place_id: the string id of the Place - place_key_str: the urlsafe string representing the key. Only one between place_id and place_key_str should be set, since they represent the same information, but encoded in two different ways. They are both accepted for generality. If both are set, the id is used. It returns a tuple: - the place with updated information (or None in case of errors in the input), - the status (a string indicating whether an error occurred), - the http code indicating the type of error, if any """ try: res = Place.store( in_place, Place.make_key(place_id, place_key_str)) except (TypeError, ValueError, InvalidKeyException) as e: return None, str(e), 400 return res, "OK", 200
def place_get(place_id, place_key_str): """ It retrieves the place. Parameters: - place_id: the string id of the Place - place_key_str: the urlsafe string representing the key. Only one between place_id and place_key_str should be set, since they represent the same information, but encoded in two different ways. They are both accepted for generality. If both are set, the id is used. It returns a tuple: - the requested place (or None in case of errors in the input), - the status (a string indicating whether an error occurred), - the http code indicating the type of error, if any """ try: key = Place.make_key(place_id, place_key_str) logging.info("KEY: " + str(key)) place = Place.get_by_key(key) except TypeError as e: logging.error("TypeError on Place.get_by_key: " + str(e)) return None, str(e), 400 return place, "OK", 200
def recommend(user_id, filters, purpose='dinner with tourists', n=5): """ It computes the recommendations for the user, according to specified filters and parameters. When possible, the recommendation list is personalized, using the cluster-based algorithm. If the personalized algorithm fails to find the required number of recommended place, an average-based non-personalized recommendation algorithm is used. If still other places are needed, the recommendation list is filled with places ordered by distance from user. Input: - user_id: is of the requester - filters: filters for places of interest for the user - purpose: the purpose the user is interested in - n: number of recommended places requested by the user Available filters: //- 'city': 'city!province!state!country' The 'city' filter contains the full description of the city, with values separated with a '!'. This string is split and used to retrieve only the places that are in the specified city. 'null' is used if part of the full city description is not available [example: 'Trento!TN!null!Italy' or if a bigger reagion is considered [example: 'null!TN!null!Italy' retrieves all places in the province of Trento] - 'lat', 'lon' and 'max_dist': lat and lon indicates the user position, while max_dist is a measure expressed in meters and represnt the radius of the circular region the user is interested in. Returns a list of n places in json format """ logging.info("recommender.recommend START - user_id=" + str(user_id) + ', filters=' + str(filters) + ', purpose=' + str(purpose) + ', n=' + str(n)) # places is already a json list start = datetime.now() user_max_dist = None if filters is not None and 'max_dist' in filters and filters['max_dist'] is not None and filters['max_dist'] > 0: user_max_dist = filters['max_dist'] #get places for a larger area filters['max_dist'] = 2 * user_max_dist places, status, errcode = logic.place_list_get(filters, user_id) logging.info("RECOMMEND places loaded ") if status != "OK" or places is None or len(places) < 1: # the system do not know any place within these filters logging.info("recommender.recommend END - no places") logging.error(str(errcode) + ": " + status) return None logging.warning("Loaded places for double distance: " + str(datetime.now() - start)) start = datetime.now() closest = [] out_distance = [] for p in places: if 'lat' in filters and 'lon' in filters and filters['lat'] is not None and filters['lon'] is not None: # add distance to user for each place p['distance'] = distance( p['address']['lat'], p['address']['lon'], filters['lat'], filters['lon']) if p['distance'] is not None and user_max_dist is not None and p['distance'] <= user_max_dist: closest.append(p) else: out_distance.append(p) if len(closest) >= n: places = closest elif len(closest) == 0: places = out_distance else: #TODO: fill missing spaces with outliers? places = closest logging.warning("removing places that are too far: " + str(datetime.now() - start)) place_ids = [] if places is not None: place_ids = [Place.make_key(None, place['key']).id() for place in places] scores = None purpose_list = ["dinner with tourists", "romantic dinner", "dinner with friends", "best price/quality ratio"] start = datetime.now() # logging.warning("RECOMMEND START get cluster-based predictions for all purposes: " + str(start)) for p in purpose_list: if p == purpose: start2 = datetime.now() scores = cluster_based(user_id, place_ids, p, n, loc_filters=filters) logging.warning("RECOMMEND END get cluster-based predictions: " + str(datetime.now()-start2)) else: q = taskqueue.Queue('recommendations') task = taskqueue.Task(params={'user_id': user_id, 'place_ids': place_ids, 'purpose': p, 'n': n, 'loc_filters': str(filters)}, url='/recommender/compute_cluster_based', method='POST', countdown=10) q.add(task) logging.warning("Getting recommendations from cluster and starting computation for other purposes: " + str(datetime.now() - start)) log_text = "RECOMMEND scores from cluster-based : " if scores is None: log_text += "None" else: log_text += str(len(scores)) logging.info(log_text) start = datetime.now() if scores is None or (len(scores) < n and len(scores) < len(places)): # cluster-based recommendation failed # non-personalized recommendation rating_filters = {} if places is not None: rating_filters['places'] = place_ids rating_filters['purpose'] = purpose ratings = load_data(rating_filters) if ratings is None: logging.info("ratings for places: None") else: logging.info("ratings for places: " + str(len(ratings))) items = {} if ratings is not None: for other in ratings: if other != user_id: for item in ratings[other]: if purpose in ratings[other][item]: if item not in items.keys(): items[item] = [] items[item].append(ratings[other][item][purpose]) avg_scores = [(sum(items[item]) / len(items[item]), item) for item in items] logging.info("avg_scores: " + str(len(avg_scores))) filters = {'purpose': purpose, 'user': user_id} if places is not None: filters['places'] = place_ids user_ratings = Rating.get_list(filters) logging.info("Loaded user ratings: " + str(len(user_ratings))) if scores is None: scores = [] for value, key in avg_scores: toadd = True for ur in user_ratings: if value < 3.0: #skip this place, too low rating toadd = False continue if key == ur.place.urlsafe() and ur.value < 3.0: #skip this place, user doesn't like it toadd = False continue for svalue, skey in scores: if key == skey: #already in list because of cluster toadd = False break if toadd: scores.append((value, key)) logging.info("Appending place with value " + str(value)) if len(scores) >= n: # we have enough recommended places break scores = sorted(scores, key=lambda x: x[0], reverse = True) if len(scores) > n: scores = scores[0:n] # if debug: # log_text = "RECOMMEND scores from average-based : " # if scores is None: # log_text += "None" # else: # log_text += str(len(scores)) # logging.info(log_text) # # if scores is None or (len(scores) < n and len(scores) < len(places)): # # cluster-based and average recommendations both failed to fill the recommendation list # # just add some other places # for p in places: # in_list = False # for score, key in scores: # if key == p['key']: # in_list = True # break # if not in_list: # scores.append((0, p['key'])) # if len(scores) >= n: # # we have enough recommended places # break # # if debug: # log_text = "RECOMMEND final scores : " # if scores is None: # log_text += "None" # else: # log_text += str(len(scores)) # logging.info(log_text) logging.warning("Filling empty space with full average predictions: " + str(datetime.now() - start)) start = datetime.now() places_scores = [] for p in places: # found = False for (score, item) in scores: if item == p['key']: places_scores.append((score, p)) # found = True # if not found: # places_scores.append((0, p)) logging.info('places_scores: ' + str(len(places_scores))) places_scores = sorted(places_scores, key=lambda x: x[0], reverse = True) logging.warning("Moving mapping from place ids to full place data: " + str(datetime.now() - start)) if len(places_scores) > n: places_scores = places_scores[0:n] # logging.info('recommender.recommend - places_scores: ' + str(places_scores)) items = [] start = datetime.now() for (score, place) in places_scores: #TODO: make discount loading asynchronous in javascript page, after visualization of places!!! disc_filters = {'place': place['key'], 'published': 'True', 'passed': 'False'} discounts, status, errcode = logic.discount_list_get(disc_filters, user_id) logging.info("discounts loaded: " + str(errcode) + " - " + status) if discounts is not None and status == "OK": try: json_discounts = [Discount.to_json(d, None, None) for d in discounts] place['discounts'] = json_discounts except (TypeError, ValueError) as e: #do nothing logging.error('Discounts not loaded: ' + str(e)) pass place['predicted'] = score items.append(place) logging.warning("Time for loading discounts: " + str(datetime.now() - start)) # logging.info("Recommended items: " + str(items)) logging.info("recommender.recommend END ")#- items: " + str(items)) return items