review_utils.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import re
  2. import json
  3. from requests import get, put, post
  4. from gauth.auth_utils import get_gmb_id, get_auth_header
  5. from .models import Review, Reply
  6. from gauth.models import Location
  7. from django.utils import timezone
  8. _, account_id = get_gmb_id()
  9. STAR_REVIEW_NUM = {'STAR_RATING_UNSPECIFIED': 0, 'ONE': 1, 'TWO': 2, 'THREE': 3, 'FOUR': 4, 'FIVE': 5}
  10. BASE_URL = f'https://mybusiness.googleapis.com/v4/'
  11. def clean_comment(text):
  12. rules = [
  13. {r'[^\x00-\x7F]+': ''},
  14. {r'^\(Google-\s*\)(.|\n|]\s)*\(\)': ''},
  15. {r'^\n*': ''}
  16. ]
  17. for rule in rules:
  18. for (k, v) in rule.items():
  19. regex = re.compile(k)
  20. text = regex.sub(v, text)
  21. text = text.rstrip()
  22. return text
  23. def get_review_list_url(location_id, next_page_token=''):
  24. # An helper function that make a url that need to consume GMB review api
  25. return f'{BASE_URL}accounts/{account_id}/locations/{location_id}/reviews?pageToken='+next_page_token
  26. def get_reply_url(location_id, review_id):
  27. return f'{BASE_URL}accounts/{account_id}/locations/{location_id}/reviews/{review_id}/reply'
  28. def reply_review(review, replied_text):
  29. '''
  30. reply a review with a put request.
  31. :param review: review object -> a review which you want to reply.
  32. :param replied_text: string -> The actual reply that you want to post.
  33. :return:
  34. '''
  35. url = get_reply_url(review.location_id, review.review_id)
  36. headers = get_auth_header()
  37. payload = json.dumps({'comment': replied_text})
  38. response = put(url, headers=headers, data=payload)
  39. return response
  40. def insert_review_into_database(reviews, loc_id):
  41. '''
  42. Insert reviews to database.
  43. :param reviews: all reviews for location.
  44. :param loc_id: location id unrecorded_reviews belongs to.
  45. :return: It insert all reviews if it is not exits in database and return nothing.
  46. '''
  47. for rev in reviews:
  48. review_id = rev.get('reviewId')
  49. try:
  50. review = Review.objects.get(pk=review_id)
  51. except Review.DoesNotExist:
  52. review = Review(review_id=review_id)
  53. review.comment = rev.get('comment')
  54. review.create_time = rev.get('createTime')
  55. review.update_time = rev.get('updateTime')
  56. review.star_rating = STAR_REVIEW_NUM[rev.get('starRating')]
  57. reviewer = rev.get('reviewer')
  58. review.reviewer_name = reviewer.get('displayName')
  59. review.reviewer_photo = reviewer.get('profilePhotoUrl')
  60. review.location_id = loc_id
  61. review_reply = rev.get('reviewReply')
  62. # Check if it is already replied.
  63. if review_reply:
  64. replied_text = review_reply.get('comment')
  65. create_time = review_reply.get('updateTime')
  66. reply, created = Reply.objects.update_or_create(
  67. replied_text=replied_text,
  68. create_time=create_time
  69. )
  70. review.reply = reply
  71. else:
  72. review.reply = None
  73. review.save()
  74. def sync_all_review(loc_id):
  75. '''
  76. Sync a location if any bad thing occur i.e. any network break.
  77. :param: loc_id -> Location id of a particular location
  78. :return: None -> It just update all reviews of this location and return nothing.
  79. '''
  80. next_page_token = ''
  81. headers = get_auth_header()
  82. while True:
  83. url = get_review_list_url(loc_id, next_page_token)
  84. res = get(url, headers=headers)
  85. if res.status_code == 401:
  86. headers = get_auth_header()
  87. continue
  88. data = res.json()
  89. reviews = data['reviews']
  90. if len(reviews) != 0:
  91. insert_review_into_database(reviews, loc_id)
  92. next_page_token = data.get('nextPageToken')
  93. if next_page_token is None:
  94. break
  95. average_rating = data.get('averageRating')
  96. total_reviews = data.get('totalReviewCount')
  97. total_reviews_db = Review.objects.filter(location_id=loc_id).count()
  98. update_location_data(loc_id, average_rating, total_reviews, total_reviews_db)
  99. def update_location_data(loc_id, average_rating, total_reviews, total_reviews_db):
  100. loc = Location.objects.get(pk=loc_id)
  101. loc.average_rating = average_rating
  102. loc.total_review = total_reviews
  103. loc.total_review_DB = total_reviews_db
  104. loc.save()
  105. def fetch_last_20_reviews(loc_id, page_size=20):
  106. headers = get_auth_header()
  107. url = get_review_list_url(loc_id)+'&pageSize='+str(page_size)
  108. res = get(url, headers=headers)
  109. data = res.json()
  110. reviews = data.get('reviews')
  111. average_rating = data.get('averageRating')
  112. total_reviews = data.get('totalReviewCount')
  113. if len(reviews) > 0:
  114. insert_review_into_database(reviews, loc_id)
  115. total_reviews_db = Review.objects.filter(location_id=loc_id).count()
  116. update_location_data(loc_id, average_rating, total_reviews, total_reviews_db)
  117. def store_batch_of_reviews(reviews):
  118. for rev in reviews:
  119. location_id = rev.get('name').split('/')[-1]
  120. rev = rev.get('review')
  121. review_id = rev.get('reviewId')
  122. try:
  123. review = Review.objects.get(pk=review_id)
  124. except Review.DoesNotExist:
  125. review = Review(review_id=review_id)
  126. comment = rev.get('comment')
  127. if comment:
  128. review.comment = clean_comment(comment)
  129. review.create_time = rev.get('createTime')
  130. review.update_time = rev.get('updateTime')
  131. review.star_rating = STAR_REVIEW_NUM[rev.get('starRating')]
  132. reviewer = rev.get('reviewer')
  133. review.reviewer_name = reviewer.get('displayName')
  134. review.reviewer_photo = reviewer.get('profilePhotoUrl')
  135. review.location_id = location_id
  136. review_reply = rev.get('reviewReply')
  137. # Check if it is already replied.
  138. if review_reply:
  139. replied_text = review_reply.get('comment')
  140. create_time = review_reply.get('updateTime')
  141. reply, created = Reply.objects.update_or_create(
  142. replied_text=replied_text,
  143. create_time=create_time
  144. )
  145. review.reply = reply
  146. else:
  147. review.reply = None
  148. review.save()
  149. def fetch_batch_of_reviews():
  150. headers = get_auth_header()
  151. url = f'{BASE_URL}accounts/{account_id}/locations:batchGetReviews'
  152. # location names should be in this format:
  153. # "accounts/103266181421855655295/locations/8918455867446117794",
  154. locations = Location.objects.all()
  155. location_names = [f'accounts/{account_id}/locations/{loc.location_id}' for loc in locations]
  156. '''
  157. post data format:
  158. {
  159. "locationNames": [
  160. string
  161. ],
  162. "pageSize": integer, -> Total number of reviews
  163. "pageToken": string, -> If has any to go next page.
  164. "orderBy": string, -> By-default updateTime desc
  165. "ignoreRatingOnlyReviews": boolean -> Whether to ignore rating-only reviews
  166. }
  167. '''
  168. payload = json.dumps({
  169. "locationNames": location_names
  170. })
  171. response = post(url, headers=headers, data=payload)
  172. if response.status_code == 200:
  173. data = response.json()
  174. location_reviews = data.get('locationReviews')
  175. store_batch_of_reviews(location_reviews)
  176. else:
  177. return None
  178. def populate_reviews():
  179. start = timezone.now()
  180. locations = Location.objects.all().values('location_id')
  181. for loc in locations:
  182. loc_id = loc.get('location_id')
  183. fetch_last_20_reviews(loc_id)
  184. end = timezone.now()
  185. elapsed = end - start
  186. print(f'Elapsed time: {elapsed.seconds//60} minutes and {elapsed.seconds % 60} secs.')
  187. def get_bad_reviews(location_id, **kwargs):
  188. '''
  189. a utility function that return all reviews has less or equal three.
  190. :param location_id: str -> id of the location where reviews are belongs to
  191. :param kwargs: i.e (days=__, hours=__, minutes=__)
  192. :return: QuerySet -> all low rating reviews in last * days/hours/minutes
  193. Example --------------
  194. >>> get_bad_reviews(location_id='123456', days=5, hours=2, minute=1)
  195. >>> get_bad_reviews(location_id='123456', days=5)
  196. >>> get_bad_reviews(location_id='123456', hours=5)
  197. '''
  198. now = timezone.now()
  199. date = now - timezone.timedelta(**kwargs)
  200. reviews = Review.objects.filter(location_id=location_id, update_time__gte=date, star_rating__lte=3)
  201. return reviews