def get_app_by_bundle_id(bundle_id, country): try: app = AppStoreApp.objects.get(bundle_id=bundle_id) except AppStoreApp.DoesNotExist: app = None if app and app.app_info_countries and country in app.app_info_countries: appstore_app_info.decorate_app(app, country) return app app_info = appstore_fetch.app_info_with_bundle_id(bundle_id, country) if not app_info: return None if not app: app = AppStoreApp(itunes_id=app_info.itunes_id, bundle_id=app_info.bundle_id) if not app.app_info_countries: app.app_info_countries = [country] elif country not in app.app_info_countries: app.app_info_countries.append(country) app.save() appstore_app_info.mark_app_needs_info_ingestion(app) appstore_app_info.update_app_info_with_fetched_data(app, app_info) appstore_app_info.decorate_app(app, country) return app
def ingest_app(app, country): appstore_app_info.decorate_app(app, country) # Check current_version_reviews_count here too because apparently total # reviews count can be missing sometimes, but current version is present. if app.reviews_count > 0 or app.current_version_reviews_count > 0: try: inserted, _ = update_with_new_reviews(app, country) if inserted > 0: appstore_review_notify.notify_subscriptions_for_app(app, country) except appstore_review_fetch.RateLimitedError: # This should stop other apps from being ingested in this run as well. raise except Exception: logging.exception('Problem ingesting reviews for app id: %s (%s - %s)', app.id, app.itunes_id, app.bundle_id) return else: # No reviews in the store, no need to check. inserted = 0 tracker_qs = AppStoreAppReviewTracker.objects.filter(app_id=app.id, country=country) if inserted < 0: # A value of -1 indicates that something went wrong and ingestion failed. tracker_qs.update( failed_ingestion_attempts=F('failed_ingestion_attempts') + 1, last_ingestion_time=datetime.now()) else: tracker_qs.update( successful_ingestion_attempts=F('successful_ingestion_attempts') + 1, last_ingestion_time=datetime.now())
def decorated_apps_by_id(app_ids, load_config_children=False): filters = Q(id__in=app_ids) if load_config_children: filters |= Q(config_parent_id__in=app_ids) apps = list(SDKApp.objects.filter(filters)) counts_by_app = session_user_labels.label_counts_by_app_ids([a.id for a in apps]) for app in apps: app.decorated_label_counts = counts_by_app[app.id] if app.appstore_app_id: appstore_app_info.decorate_app(app.appstore_app, app.appstore_app_country) if load_config_children: parent_apps = [] apps_by_id = {} for app in apps: apps_by_id[app.id] = app if app.decorated_config_children is None: app.decorated_config_children = [] for app in apps: parent = apps_by_id.get(app.config_parent_id) if parent: parent.decorated_config_children.append(app) else: parent_apps.append(app) else: parent_apps = apps return sorted(parent_apps, key=lambda a: a.decorated_label_counts.get(ActiveStatus.MonthlyActive, 0), reverse=True)
def add_all_apps_to_ingestion_queue(): apps_countries = list(AppStoreAppReviewTracker.objects.all().values_list('app_id', 'country')) redis = redis_wrap.client() for app_id, country in apps_countries: app = AppStoreApp.objects.get(pk=app_id) appstore_app_info.decorate_app(app, country) target_time = next_ingestion_time(app, country) redis.zadd(APP_REVIEWS_INGESTION_ZSET_KEY, target_time, '%s:%s' % (app_id, country))
def my_apps(user, limit=50): my_interests = AppStoreAppInterest.objects.filter( user=user, enabled=True).order_by('id').select_related('app')[:limit] apps = [] for interest in my_interests: appstore_app_info.decorate_app(interest.app, interest.country) apps.append(interest.app) return sorted(apps, key=lambda a: a.name.lower())
def mark_should_track_reviews_for_my_apps(user): interests = AppStoreAppInterest.objects.filter( user=user, enabled=True).select_related('app') my_apps = [] for interest in interests: appstore_app_info.decorate_app(interest.app, interest.country) mark_app_should_track_reviews(interest.app, interest.country) my_apps.append(interest.app) return my_apps
def subscribed_reviews_for_user(user, app=None, start_review=None, rating=None, limit=None, country=None): if not limit: limit = 25 interested_apps = AppStoreAppInterest.objects.filter(user=user, enabled=True).values_list('app_id', 'country') if app: interested_apps = interested_apps.filter(app=app) if country: interested_apps = interested_apps.filter(country=country) interested_apps = list(interested_apps) if not interested_apps: return [] matching_apps_by_country = {} for app_id, country in interested_apps: if country not in matching_apps_by_country: matching_apps_by_country[country] = [] matching_apps_by_country[country].append(app_id) where_ors = [] params = [] for country, app_ids in matching_apps_by_country.items(): where_ors.append('country = %%s AND app_id IN (%s)' % ','.join(['%s'] * len(app_ids))) params.append(country) params += app_ids where = '((%s))' % ') OR ('.join(where_ors) if rating: where += ' AND rating = %s' params.append(rating) if start_review: where += ' AND appstore_review_id < %s' params.append(start_review.appstore_review_id) params.append(limit) reviews = AppStoreReview.objects.raw(""" WITH reviews AS ( SELECT * FROM lk_appstorereview WHERE invalidated_time IS NULL AND (%s) ) SELECT * FROM reviews ORDER BY appstore_review_id DESC LIMIT %%s """ % where, params) reviews = list(reviews) app_ids = set(r.app_id for r in reviews) apps = AppStoreApp.objects.filter(id__in=list(app_ids)) apps_by_id = dict((a.id, a) for a in apps) for r in reviews: r.app = apps_by_id[r.app_id] appstore_app_info.decorate_app(r.app, r.country) return reviews
def app_itunes_info_view(request, app): if app.appstore_app: appstore_app = app.appstore_app appstore_app_info.decorate_app(appstore_app, app.appstore_app_country) else: appstore_app = appstore.get_app_by_bundle_id(app.bundle_id, request.GET.get('country') or 'us') return api_response({ 'info': appstore_app and appstore_app.decorated_info.to_dict(), })
def add_all_apps_to_ingestion_queue(): apps_countries = list(AppStoreAppReviewTracker.objects.all().values_list( 'app_id', 'country')) redis = redis_wrap.client() for app_id, country in apps_countries: app = AppStoreApp.objects.get(pk=app_id) appstore_app_info.decorate_app(app, country) target_time = next_ingestion_time(app, country) redis.zadd(APP_REVIEWS_INGESTION_ZSET_KEY, target_time, '%s:%s' % (app_id, country))
def subscriptions_for_user(user): my_subs = ( AppStoreReviewSubscription.objects .filter(user=user, enabled=True) .select_related('filter_app', 'twitter_connection') ) for sub in my_subs: if sub.filter_app_id: # FIXME: Add "country" to filter_app subs. interests = AppStoreAppInterest.objects.filter(user_id=user.id, app_id=sub.filter_app_id)[:1] appstore_app_info.decorate_app(sub.filter_app, interests[0].country) return my_subs
def review_view(request, review_id=None): review = AppStoreReview.find_by_encrypted_id(review_id) if not review: return not_found() app = review.app appstore_app_info.decorate_app(app, review.country) return api_response({ 'review': review.to_dict(), 'apps': {app.encrypted_id: app.to_dict()} })
def send_reviews_ready_email(user_id, app_ids_countries): user = User.objects.get(pk=user_id) app_ids = [app_id for app_id, _ in app_ids_countries] apps_by_id = dict((a.id, a) for a in AppStoreApp.objects.filter(id__in=app_ids)) apps = [] for app_id, country in app_ids_countries: app = apps_by_id[app_id] appstore_app_info.decorate_app(app, country) apps.append(app) messsage = emails.create_reviews_ready_email(user, apps) emails.send_all([messsage])
def app_itunes_info_view(request, app): if app.appstore_app: appstore_app = app.appstore_app appstore_app_info.decorate_app(appstore_app, app.appstore_app_country) else: appstore_app = appstore.get_app_by_bundle_id( app.bundle_id, request.GET.get('country') or 'us') return api_response({ 'info': appstore_app and appstore_app.decorated_info.to_dict(), })
def send_reviews_ready_email(user_id, app_ids_countries): user = User.objects.get(pk=user_id) app_ids = [app_id for app_id, _ in app_ids_countries] apps_by_id = dict( (a.id, a) for a in AppStoreApp.objects.filter(id__in=app_ids)) apps = [] for app_id, country in app_ids_countries: app = apps_by_id[app_id] appstore_app_info.decorate_app(app, country) apps.append(app) messsage = emails.create_reviews_ready_email(user, apps) emails.send_all([messsage])
def next_ingestion_time(app, country): appstore_app_info.decorate_app(app, country) reviews_count = app.reviews_count or 0 if reviews_count < 100: # Few times a day, ish. base_wait = HOUR * 6 elif reviews_count < 1000: # Every 3 hours, ish. base_wait = HOUR * 2 else: # Every 1.5 hours, ish. base_wait = HOUR wait = base_wait + (base_wait * (random.randint(0, 100) / 100.0)) return time.time() + wait
def _apps_view_POST(request): form = CreateSDKAppForm(request.POST) if not form.is_valid(): return bad_request('Invalid create app parameters.', errors=form.errors) bundle_id = form.cleaned_data.get('bundle_id') if bundle_id: app = sdk_apps.create_or_fetch_sdk_app_with_bundle_id( request.user, bundle_id) if app.appstore_app: # Edge case: Creating app store app again later, from bundle ID appstore_app_info.decorate_app(app.appstore_app, app.appstore_app_country) else: itunes_id, country = form.cleaned_data.get( 'itunes_id'), form.cleaned_data.get('itunes_country') appstore_app = appstore.get_app_by_itunes_id(itunes_id, country) if not appstore_app: return bad_request( 'Invalid `itunes_id` or `itunes_country` provided.') app = sdk_apps.create_or_decorate_sdk_app_with_appstore_app( request.user, appstore_app) display_name = form.cleaned_data.get('display_name') if display_name: app.display_name = display_name app.save(update_fields=['display_name']) config_parent_app = form.cleaned_data.get('config_parent_id') if config_parent_app: app.config_parent = config_parent_app app.save(update_fields=['config_parent']) for sdk_product in SDKProduct.kinds(): if sdk_product in request.POST: setattr(app, sdk_product, form.cleaned_data[sdk_product]) app.save(update_fields=['products']) return api_response({ 'app': app.to_dict(), })
def ingest_app(app, country): appstore_app_info.decorate_app(app, country) # Check current_version_reviews_count here too because apparently total # reviews count can be missing sometimes, but current version is present. if app.reviews_count > 0 or app.current_version_reviews_count > 0: try: inserted, _ = update_with_new_reviews(app, country) if inserted > 0: appstore_review_notify.notify_subscriptions_for_app( app, country) except appstore_review_fetch.RateLimitedError: # This should stop other apps from being ingested in this run as well. raise except Exception: logging.exception( 'Problem ingesting reviews for app id: %s (%s - %s)', app.id, app.itunes_id, app.bundle_id) return else: # No reviews in the store, no need to check. inserted = 0 tracker_qs = AppStoreAppReviewTracker.objects.filter(app_id=app.id, country=country) if inserted < 0: # A value of -1 indicates that something went wrong and ingestion failed. tracker_qs.update( failed_ingestion_attempts=F('failed_ingestion_attempts') + 1, last_ingestion_time=datetime.now()) else: tracker_qs.update( successful_ingestion_attempts=F('successful_ingestion_attempts') + 1, last_ingestion_time=datetime.now())
def get_app_and_related_by_itunes_id(itunes_id, country): app_info = appstore_fetch.app_info_with_id(itunes_id, country) if not app_info: return None app_infos = appstore_fetch.related_app_infos_with_developer_id( app_info.developer_id, country) app_infos_by_itunes_id = dict((a.itunes_id, a) for a in app_infos) existing_app_ids = AppStoreApp.objects.filter( itunes_id__in=app_infos_by_itunes_id.keys()).values_list('itunes_id', flat=True) existing_app_ids = set(long(i) for i in existing_app_ids) apps_to_insert = [ AppStoreApp(itunes_id=a.itunes_id, bundle_id=a.bundle_id) for a in app_infos if long(a.itunes_id) not in existing_app_ids ] if apps_to_insert: AppStoreApp.objects.bulk_create(apps_to_insert) fetched_apps = list( AppStoreApp.objects.filter( itunes_id__in=[str(i) for i in app_infos_by_itunes_id])) for app in fetched_apps: app_info = app_infos_by_itunes_id[long(app.itunes_id)] if not app.app_info_countries: app.app_info_countries = [country] else: app.app_info_countries.append(country) app.save() appstore_app_info.mark_app_needs_info_ingestion(app) appstore_app_info.update_app_info_with_fetched_data(app, app_info) appstore_app_info.decorate_app(app, country) return fetched_apps
def _apps_view_POST(request): form = CreateSDKAppForm(request.POST) if not form.is_valid(): return bad_request('Invalid create app parameters.', errors=form.errors) bundle_id = form.cleaned_data.get('bundle_id') if bundle_id: app = sdk_apps.create_or_fetch_sdk_app_with_bundle_id(request.user, bundle_id) if app.appstore_app: # Edge case: Creating app store app again later, from bundle ID appstore_app_info.decorate_app(app.appstore_app, app.appstore_app_country) else: itunes_id, country = form.cleaned_data.get('itunes_id'), form.cleaned_data.get('itunes_country') appstore_app = appstore.get_app_by_itunes_id(itunes_id, country) if not appstore_app: return bad_request('Invalid `itunes_id` or `itunes_country` provided.') app = sdk_apps.create_or_decorate_sdk_app_with_appstore_app(request.user, appstore_app) display_name = form.cleaned_data.get('display_name') if display_name: app.display_name = display_name app.save(update_fields=['display_name']) config_parent_app = form.cleaned_data.get('config_parent_id') if config_parent_app: app.config_parent = config_parent_app app.save(update_fields=['config_parent']) for sdk_product in SDKProduct.kinds(): if sdk_product in request.POST: setattr(app, sdk_product, form.cleaned_data[sdk_product]) app.save(update_fields=['products']) return api_response({ 'app': app.to_dict(), })
def create_twitter_subscription_from_connection(twitter_connection): user = twitter_connection.user app = twitter_connection.app # FIXME: Add "country" to filter_app subs. interest = AppStoreAppInterest.objects.filter(user=user, app=app)[0] appstore_app_info.decorate_app(app, interest.country) twitter_handle = twitter_connection.handle if twitter_handle not in user.twitter_handles: return None subs = AppStoreReviewSubscription.objects.filter(user=user, filter_app=app, twitter_connection=twitter_connection) if subs: sub = subs[0] sub.filter_app = app else: sub = AppStoreReviewSubscription(user=user) sub.filter_app = app sub.twitter_connection = twitter_connection mark_subscription_filtered_very_good(sub, True) if sub.enabled: # Already have this sub. return None sub.enabled = True if sub.last_notification_time: # Move pointer to now so they don't get a deluge of reviews. sub.last_notification_time = datetime.now() sub.save() appstore.mark_should_track_reviews_for_my_apps(user) return sub
def get_sales_metrics(vendor, requested_date, base_currency='usd'): if report_status_for_vendor_date(vendor, requested_date) != REPORT_STATUS_AVAILABLE: return None, None previous_date = requested_date - timedelta(1) requested_week_dates = [requested_date - timedelta(x) for x in range(7)] previous_week_dates = [requested_date - timedelta(x) for x in range(7, 14)] load_dates = requested_week_dates + previous_week_dates # There might NOT be a report for a given day (sales=0), so use the # non-failed report dates here that we know that we have fetched in order # to determine whether our data is good. dates_accounted_for = set( AppStoreSalesReportFetchedStatus.objects .filter(vendor=vendor, report_date__in=load_dates) .exclude(failed=True) .values_list('report_date', flat=True) .distinct() ) reports = list( AppStoreSalesReport.objects .filter(vendor=vendor, end_date__in=load_dates) # Don't load the app name / promo code / category / version / etc. here .only('app_id', 'developer_proceeds', 'proceeds_currency', 'units', 'end_date', 'product_type_identifier') ) total_sales_metrics = copy.deepcopy(METRICS_DICT) app_sales_metrics = {} currency_conversions = conversion_dict_for_currency(base_currency) or {} for report in reports: app_id = report.app_id if app_id not in app_sales_metrics: app_sales_metrics[app_id] = copy.deepcopy(METRICS_DICT) revenue = float(report.developer_proceeds or 0) if report.proceeds_currency != base_currency: if report.proceeds_currency in currency_conversions: revenue = revenue / currency_conversions[report.proceeds_currency] else: logging.error('Itunes Connect Error - currency conversion not found: %s -> %s', report.proceeds_currency, base_currency) revenue = 0 # developer_proceeds is the unit price. units is a decimal. revenue *= float(report.units) if revenue > 0: if report.end_date in requested_week_dates: app_sales_metrics[app_id]['revenue']['week']['requested'] += revenue total_sales_metrics['revenue']['week']['requested'] += revenue if report.end_date == requested_date: app_sales_metrics[app_id]['revenue']['day']['requested'] += revenue total_sales_metrics['revenue']['day']['requested'] += revenue if report.end_date == previous_date: app_sales_metrics[app_id]['revenue']['day']['previous'] += revenue total_sales_metrics['revenue']['day']['previous'] += revenue else: app_sales_metrics[app_id]['revenue']['week']['previous'] += revenue total_sales_metrics['revenue']['week']['previous'] += revenue if report.is_download: downloads = int(report.units) if report.end_date in requested_week_dates: app_sales_metrics[app_id]['downloads']['week']['requested'] += downloads total_sales_metrics['downloads']['week']['requested'] += downloads if report.end_date == requested_date: app_sales_metrics[app_id]['downloads']['day']['requested'] += downloads total_sales_metrics['downloads']['day']['requested'] += downloads if report.end_date == previous_date: app_sales_metrics[app_id]['downloads']['day']['previous'] += downloads total_sales_metrics['downloads']['day']['previous'] += downloads else: app_sales_metrics[app_id]['downloads']['week']['previous'] += downloads total_sales_metrics['downloads']['week']['previous'] += downloads if requested_date not in dates_accounted_for: logging.warn('Requested date not in dates accounted for, yet status was available') return None, None # We might not have the full week -- if we don't have the full week, # we can't show the data because it will be incorrect. has_requested_week_data = all(map(lambda d: d in dates_accounted_for, requested_week_dates)) if not has_requested_week_data: del total_sales_metrics['downloads']['week'] del total_sales_metrics['revenue']['week'] for app_id in app_sales_metrics: del app_sales_metrics[app_id]['downloads']['week'] del app_sales_metrics[app_id]['revenue']['week'] has_previous_day_data = previous_date in dates_accounted_for has_previous_week_data = all(map(lambda d: d in dates_accounted_for, previous_week_dates)) if has_previous_day_data: total_daily_dls = total_sales_metrics['downloads']['day'] total_daily_dls['delta'] = formatted_delta(total_daily_dls['requested'], total_daily_dls['previous']) total_daily_rev = total_sales_metrics['revenue']['day'] total_daily_rev['delta'] = formatted_delta(total_daily_rev['requested'], total_daily_rev['previous']) if has_requested_week_data and has_previous_week_data: total_weekly_dls = total_sales_metrics['downloads']['week'] total_weekly_dls['delta'] = formatted_delta(total_weekly_dls['requested'], total_weekly_dls['previous']) total_weekly_rev = total_sales_metrics['revenue']['week'] total_weekly_rev['delta'] = formatted_delta(total_weekly_rev['requested'], total_weekly_rev['previous']) for app_id in app_sales_metrics: if has_previous_day_data: daily_dls = app_sales_metrics[app_id]['downloads']['day'] daily_dls['delta'] = formatted_delta(daily_dls['requested'], daily_dls['previous']) daily_rev = app_sales_metrics[app_id]['revenue']['day'] daily_rev['delta'] = formatted_delta(daily_rev['requested'], daily_rev['previous']) if has_requested_week_data and has_previous_week_data: weekly_dls = app_sales_metrics[app_id]['downloads']['week'] weekly_dls['delta'] = formatted_delta(weekly_dls['requested'], weekly_dls['previous']) weekly_rev = app_sales_metrics[app_id]['revenue']['week'] weekly_rev['delta'] = formatted_delta(weekly_rev['requested'], weekly_rev['previous']) apps_by_id = {a.id: a for a in AppStoreApp.objects.filter(id__in=app_sales_metrics.keys())} app_sales_metrics_list = [] for app_id in app_sales_metrics: metrics = app_sales_metrics[app_id] app = apps_by_id[app_id] appstore_app_info.decorate_app(app, app.app_info_countries[0]) app_sales_metrics_list.append({'app': app, 'metrics': metrics}) app_sales_metrics_list.sort(key=lambda a: a['metrics']['downloads']['day']['requested'], reverse=True) if len(app_sales_metrics_list) == 1: total_sales_metrics = None return app_sales_metrics_list, total_sales_metrics
def get_sales_metrics(vendor, requested_date, base_currency='usd'): if report_status_for_vendor_date( vendor, requested_date) != REPORT_STATUS_AVAILABLE: return None, None previous_date = requested_date - timedelta(1) requested_week_dates = [requested_date - timedelta(x) for x in range(7)] previous_week_dates = [requested_date - timedelta(x) for x in range(7, 14)] load_dates = requested_week_dates + previous_week_dates # There might NOT be a report for a given day (sales=0), so use the # non-failed report dates here that we know that we have fetched in order # to determine whether our data is good. dates_accounted_for = set( AppStoreSalesReportFetchedStatus.objects.filter( vendor=vendor, report_date__in=load_dates).exclude(failed=True).values_list( 'report_date', flat=True).distinct()) reports = list( AppStoreSalesReport.objects.filter(vendor=vendor, end_date__in=load_dates) # Don't load the app name / promo code / category / version / etc. here .only('app_id', 'developer_proceeds', 'proceeds_currency', 'units', 'end_date', 'product_type_identifier')) total_sales_metrics = copy.deepcopy(METRICS_DICT) app_sales_metrics = {} currency_conversions = conversion_dict_for_currency(base_currency) or {} for report in reports: app_id = report.app_id if app_id not in app_sales_metrics: app_sales_metrics[app_id] = copy.deepcopy(METRICS_DICT) revenue = float(report.developer_proceeds or 0) if report.proceeds_currency != base_currency: if report.proceeds_currency in currency_conversions: revenue = revenue / currency_conversions[ report.proceeds_currency] else: logging.error( 'Itunes Connect Error - currency conversion not found: %s -> %s', report.proceeds_currency, base_currency) revenue = 0 # developer_proceeds is the unit price. units is a decimal. revenue *= float(report.units) if revenue > 0: if report.end_date in requested_week_dates: app_sales_metrics[app_id]['revenue']['week'][ 'requested'] += revenue total_sales_metrics['revenue']['week']['requested'] += revenue if report.end_date == requested_date: app_sales_metrics[app_id]['revenue']['day'][ 'requested'] += revenue total_sales_metrics['revenue']['day'][ 'requested'] += revenue if report.end_date == previous_date: app_sales_metrics[app_id]['revenue']['day'][ 'previous'] += revenue total_sales_metrics['revenue']['day'][ 'previous'] += revenue else: app_sales_metrics[app_id]['revenue']['week'][ 'previous'] += revenue total_sales_metrics['revenue']['week']['previous'] += revenue if report.is_download: downloads = int(report.units) if report.end_date in requested_week_dates: app_sales_metrics[app_id]['downloads']['week'][ 'requested'] += downloads total_sales_metrics['downloads']['week'][ 'requested'] += downloads if report.end_date == requested_date: app_sales_metrics[app_id]['downloads']['day'][ 'requested'] += downloads total_sales_metrics['downloads']['day'][ 'requested'] += downloads if report.end_date == previous_date: app_sales_metrics[app_id]['downloads']['day'][ 'previous'] += downloads total_sales_metrics['downloads']['day'][ 'previous'] += downloads else: app_sales_metrics[app_id]['downloads']['week'][ 'previous'] += downloads total_sales_metrics['downloads']['week'][ 'previous'] += downloads if requested_date not in dates_accounted_for: logging.warn( 'Requested date not in dates accounted for, yet status was available' ) return None, None # We might not have the full week -- if we don't have the full week, # we can't show the data because it will be incorrect. has_requested_week_data = all( map(lambda d: d in dates_accounted_for, requested_week_dates)) if not has_requested_week_data: del total_sales_metrics['downloads']['week'] del total_sales_metrics['revenue']['week'] for app_id in app_sales_metrics: del app_sales_metrics[app_id]['downloads']['week'] del app_sales_metrics[app_id]['revenue']['week'] has_previous_day_data = previous_date in dates_accounted_for has_previous_week_data = all( map(lambda d: d in dates_accounted_for, previous_week_dates)) if has_previous_day_data: total_daily_dls = total_sales_metrics['downloads']['day'] total_daily_dls['delta'] = formatted_delta( total_daily_dls['requested'], total_daily_dls['previous']) total_daily_rev = total_sales_metrics['revenue']['day'] total_daily_rev['delta'] = formatted_delta( total_daily_rev['requested'], total_daily_rev['previous']) if has_requested_week_data and has_previous_week_data: total_weekly_dls = total_sales_metrics['downloads']['week'] total_weekly_dls['delta'] = formatted_delta( total_weekly_dls['requested'], total_weekly_dls['previous']) total_weekly_rev = total_sales_metrics['revenue']['week'] total_weekly_rev['delta'] = formatted_delta( total_weekly_rev['requested'], total_weekly_rev['previous']) for app_id in app_sales_metrics: if has_previous_day_data: daily_dls = app_sales_metrics[app_id]['downloads']['day'] daily_dls['delta'] = formatted_delta(daily_dls['requested'], daily_dls['previous']) daily_rev = app_sales_metrics[app_id]['revenue']['day'] daily_rev['delta'] = formatted_delta(daily_rev['requested'], daily_rev['previous']) if has_requested_week_data and has_previous_week_data: weekly_dls = app_sales_metrics[app_id]['downloads']['week'] weekly_dls['delta'] = formatted_delta(weekly_dls['requested'], weekly_dls['previous']) weekly_rev = app_sales_metrics[app_id]['revenue']['week'] weekly_rev['delta'] = formatted_delta(weekly_rev['requested'], weekly_rev['previous']) apps_by_id = { a.id: a for a in AppStoreApp.objects.filter(id__in=app_sales_metrics.keys()) } app_sales_metrics_list = [] for app_id in app_sales_metrics: metrics = app_sales_metrics[app_id] app = apps_by_id[app_id] appstore_app_info.decorate_app(app, app.app_info_countries[0]) app_sales_metrics_list.append({'app': app, 'metrics': metrics}) app_sales_metrics_list.sort( key=lambda a: a['metrics']['downloads']['day']['requested'], reverse=True) if len(app_sales_metrics_list) == 1: total_sales_metrics = None return app_sales_metrics_list, total_sales_metrics
def _maybe_notify_subscriptions_for_app_id(app_id, country): app = AppStoreApp.objects.get(pk=app_id) appstore_app_info.decorate_app(app, country) interested_parties = AppStoreAppInterest.objects.filter(app_id=app.id, country=country, enabled=True).values_list('user_id', flat=True) subs = AppStoreReviewSubscription.objects.filter(user__in=interested_parties, enabled=True).select_related('user') # Restrict subs to unfiltered or filtered just for this app. subs = subs.filter(Q(filter_app_id__isnull=True) | Q(filter_app_id=app_id)) if subs.count() > 100: logging.warn('Optimize subscription sending for app: %s (%s - %s)', app.id, app.itunes_id, app.bundle_id) email_messages = [] notifications = [] for sub in subs: reviews = AppStoreReview.objects.filter(app=app, initial_ingestion=False, create_time__gt=(sub.last_notification_time or sub.create_time), country=country) if sub.filter_good: reviews = reviews.filter(rating__gte=4) if sub.filter_very_good: reviews = reviews.filter(rating=5) reviews_stats = reviews.aggregate(max_create_time=Max('create_time'), count=Count('id')) reviews_count = reviews_stats['count'] reviews_max_create_time = reviews_stats['max_create_time'] if not reviews_count: continue reviews = list(reviews.order_by('-rating', '-create_time')[:10]) n = AppStoreReviewNotification(app=app, user=sub.user) n.reviews_count = reviews_count if sub.email or sub.my_email: created_user = None if sub.my_email: email = sub.user.email else: created_user = sub.user email = sub.email unsub_url = unsubscribe_url_for_subscription(sub) email_messages.append( emails.create_review_email(email, app, reviews_count, reviews, unsub_url, created_user=created_user)) n.email = email if sub.my_email: n.my_email = True elif sub.slack_channel_name or sub.slack_url: slack_json = slack_review_json(app, reviews_count, reviews) slack.post_message_to_slack_subscription(sub, slack_json) if sub.slack_url: n.slack_webhook = True else: n.slack_channel_name = sub.slack_channel_name elif sub.twitter_connection: # For now, just pick first one for auto-tweeting. tweet_review_for_user.delay(sub.user_id, sub.twitter_connection.handle, reviews[0].id) n.twitter_handle = sub.twitter_connection.handle else: continue logging.error('WTF? unknown sub: %s', sub.id) # NOTE: This is not now() because now() might be different from max(create_time) of this batch, # and we use create_time as the filter for the next notification time. sub.last_notification_time = reviews_max_create_time sub.save(update_fields=['last_notification_time']) notifications.append(n) if email_messages: emails.send_all(email_messages) if notifications: AppStoreReviewNotification.objects.bulk_create(notifications)