import json from requests import get, put from gauth.auth_utils import get_gmb_id, get_auth_header from gauth.location_utils import get_all_location_ids from django.db.models import Max from .models import Review, Reply from gauth.models import Location from django.utils import timezone STAR_REVIEW_NUM = {'STAR_RATING_UNSPECIFIED': 0, 'ONE': 1, 'TWO': 2, 'THREE': 3, 'FOUR': 4, 'FIVE': 5} USER, GMB_ID = get_gmb_id() def get_review_list_url(loc_id, next_page_token=''): # An helper function that make a url that need to consume GMB review api return 'https://mybusiness.googleapis.com/v4/accounts/' + GMB_ID + '/locations/' + loc_id + '/reviews?pageToken='+next_page_token def get_reply_url(location_id, review_id): _, account_id = get_gmb_id() return f'https://mybusiness.googleapis.com/v4/accounts/{account_id}/locations/{location_id}/reviews/{review_id}/reply' def reply_review(review, replied_text): ''' reply a review with a put request. :param review: review object -> a review which you want to reply. :param replied_text: string -> The actual reply that you want to post. :return: ''' url = get_reply_url(review.location.location_id, review.review_id) headers = get_auth_header() payload = json.dumps({'comment': replied_text}) response = put(url, headers=headers, data=payload) return response def get_max_date(loc_id): ''' find the maximum date of any particular location. the latest date :param loc_id: Integer -> Globally unique ID for google my business locations. :return: DateTime Obj -> The latest reviews date of that location. ''' # Function that takes a location id and return the latest updated review. largest = Review.objects.filter(location_id=loc_id).aggregate(Max('create_time'))['create_time__max'] max_date = largest if largest else '1970-01-11 17:41:17.532740' return max_date def filter_unrecorded_review_by_date(reviews, max_date): # A function that return only those reviews whose has larger value than # the max create_time value in review database. filtered_reviews = [] for rev in reviews: if rev['createTime'] >= str(max_date): filtered_reviews.append(rev) return filtered_reviews def insert_review_into_database(unrecorded_reviews, loc): ''' Insert reviews to database. :param unrecorded_reviews: all reviews for location. :param loc: location that unrecorded_reviews belongs to. :return: It insert all reviews if it is not exits in database and return nothing. ''' for review in unrecorded_reviews: review_id = review.get('reviewId') # Check the review already in database then We don't need to store again. rev = Review.objects.filter(pk=review_id).first() if rev: continue comment = review.get('comment') create_time = review.get('createTime') update_time = review.get('updateTime') star_rating = STAR_REVIEW_NUM[review.get('starRating')] reviewer = review.get('reviewer') reviewer_name = reviewer.get('displayName') reviewer_photo = reviewer.get('profilePhotoUrl') review_reply = review.get('reviewReply') # Check if it is already replied. if review_reply: replied_text = review_reply.get('comment') create_time = review_reply.get('updateTime') reply = Reply.objects.create(replied_text=replied_text, create_time=create_time) else: reply = None review = Review( review_id=review_id, comment=comment, create_time=create_time, update_time=update_time, star_rating=star_rating, reviewer_name=reviewer_name, reviewer_photo=reviewer_photo, location=loc, reply=reply ) review.save() def sync_all_review(loc_id): ''' Sync a location if any bad thing occur i.e. any network break. :param: loc_id -> Location id of a particular location :return: None -> It just update all reviews of this location and return nothing. ''' loc = Location.objects.get(pk=loc_id) next_page_token = '' headers = get_auth_header() while True: url = get_review_list_url(loc_id, next_page_token) res = get(url, headers=headers) if res.status_code == 401: headers = get_auth_header() continue data = res.json() reviews = data['reviews'] if len(reviews) != 0: insert_review_into_database(reviews, loc) next_page_token = data.get('nextPageToken') if next_page_token is None: break average_rating = data.get('averageRating') total_reviews = data.get('totalReviewCount') total_reviews_db = Review.objects.filter(location_id=loc_id).count() update_location_data(loc, average_rating, total_reviews, total_reviews_db) def update_location_data(loc, average_rating, total_reviews, total_reviews_db): loc.average_rating = average_rating loc.total_review = total_reviews loc.total_review_DB = total_reviews_db loc.save() def fetch_all_review(loc_id): loc = Location.objects.get(pk=loc_id) max_date = get_max_date(loc_id) headers = get_auth_header() url = get_review_list_url(loc_id) res = get(url, headers=headers) if res.status_code == 401: return data = res.json() reviews = data['reviews'] average_rating = data.get('averageRating') total_reviews = data.get('totalReviewCount') unrecorded_reviews = filter_unrecorded_review_by_date(reviews, max_date) if len(unrecorded_reviews) != 0: insert_review_into_database(unrecorded_reviews, loc) total_reviews_db = Review.objects.filter(location_id=loc_id).count() update_location_data(loc, average_rating, total_reviews, total_reviews_db) def fetch_last_50_reviews(loc_id): loc = Location.objects.get(pk=loc_id) headers = get_auth_header() url = get_review_list_url(loc_id)+'&pageSize=50' res = get(url, headers=headers) if res.status_code == 401: return data = res.json() reviews = data['reviews'] average_rating = data.get('averageRating') total_reviews = data.get('totalReviewCount') if reviews.count() > 0: insert_review_into_database(reviews, loc) total_reviews_db = Review.objects.filter(location_id=loc_id).count() update_location_data(loc, average_rating, total_reviews, total_reviews_db) def populate_reviews(): start = timezone.now() locations = get_all_location_ids() for loc_id in locations: fetch_last_50_reviews(loc_id) end = timezone.now() elapsed = end - start print(f'Elapsed time: {elapsed.seconds//60} minutes and {elapsed.seconds % 60} secs.') def get_bad_reviews(location_id, **kwargs): ''' a utility function that return all reviews has less or equal three. :param location_id: str -> id of the location where reviews are belongs to :param kwargs: i.e (days=__, hours=__, minutes=__) :return: QuerySet -> all low rating reviews in last * days/hours/minutes Example -------------- >>> get_bad_reviews(location_id='123456', days=5, hours=2, minute=1) >>> get_bad_reviews(location_id='123456', days=5) >>> get_bad_reviews(location_id='123456', hours=5) ''' now = timezone.now() date = now - timezone.timedelta(**kwargs) reviews = Review.objects.filter(location_id=location_id, update_time__gte=date, star_rating__lte=3) return reviews