Exemplo n.º 1
0
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())
Exemplo n.º 3
0
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))
Exemplo n.º 5
0
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())
Exemplo n.º 6
0
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
Exemplo n.º 8
0
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(),
  })
Exemplo n.º 9
0
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
Exemplo n.º 11
0
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])
Exemplo n.º 13
0
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(),
    })
Exemplo n.º 14
0
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])
Exemplo n.º 15
0
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 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
Exemplo n.º 17
0
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(),
    })
Exemplo n.º 18
0
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())
Exemplo n.º 19
0
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
Exemplo n.º 20
0
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
Exemplo n.º 22
0
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
Exemplo n.º 23
0
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
Exemplo n.º 24
0
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)