コード例 #1
0
    def test_insert_user_stats_mult_ranges_artist(self):
        """ Test if multiple time range data is inserted correctly """
        with open(self.path_to_data_file('user_top_artists_db.json')) as f:
            artists_data = json.load(f)

        db_stats.insert_user_artists(user_id=self.user['id'], artists=UserArtistStatJson(**{'all_time': artists_data}))
        db_stats.insert_user_artists(user_id=self.user['id'], artists=UserArtistStatJson(**{'year': artists_data}))

        result = db_stats.get_user_artists(1, 'all_time')
        self.assertDictEqual(result.all_time.dict(), artists_data)

        result = db_stats.get_user_artists(1, 'year')
        self.assertDictEqual(result.year.dict(), artists_data)
コード例 #2
0
ファイル: user.py プロジェクト: regagain/listenbrainz-server
def artists(user_name):
    """ Show the top artists for the user. These users must have been already
        calculated using Google BigQuery. If the stats are not present, we
        redirect to the user page with a message.
    """

    try:
        user = _get_user(user_name)
        data = db_stats.get_user_artists(user.id)
    except DatabaseException as e:
        current_app.logger.error(
            'Error while getting top artist page for user %s: %s',
            user.musicbrainz_id, str(e))
        raise

    # if no data, flash a message and return to profile page
    if data is None:
        msg = (
            'No data calculated for user %s yet. ListenBrainz only calculates statistics for'
            ' recently active users. If %s has logged in recently, they\'ve already been added to'
            ' the stats calculation queue. Please wait until the next statistics calculation batch is finished'
            ' or request stats calculation from your info page.') % (user_name,
                                                                     user_name)

        flash.error(msg)
        return redirect(url_for('user.profile', user_name=user_name))

    top_artists = data['artist']['all_time']
    return render_template("user/artists.html",
                           user=user,
                           data=ujson.dumps(top_artists),
                           section='artists')
コード例 #3
0
def artists(user_name):
    """ Show the top artists for the user. These users must have been already
        calculated using Google BigQuery. If the stats are not present, we
        redirect to the user page with a message.
    """

    try:
        user = _get_user(user_name)
        data = db_stats.get_user_artists(user.id)
    except DatabaseException as e:
        current_app.logger.error('Error while getting top artist page for user %s: %s', user.musicbrainz_id, str(e))
        raise

    # if no data, flash a message and return to profile page
    if data is None:
        msg = ('No data calculated for user %s yet. ListenBrainz only calculates statistics for'
        ' recently active users. If %s has logged in recently, they\'ve already been added to'
        ' the stats calculation queue. Please wait until the next statistics calculation batch is finished'
        ' or request stats calculation from your info page.') % (user_name, user_name)

        flash.error(msg)
        return redirect(url_for('user.profile', user_name=user_name))

    top_artists = data['artist']['all_time']
    return render_template(
        "user/artists.html",
        user=user,
        data=ujson.dumps(top_artists),
        section='artists'
    )
コード例 #4
0
    def test_insert_user_artists(self):
        """ Test if artist stats are inserted correctly """
        with open(self.path_to_data_file('user_top_artists_db.json')) as f:
            artists_data = json.load(f)

        db_stats.insert_user_artists(user_id=self.user['id'], artists=UserArtistStatJson(**{'all_time': artists_data}))

        result = db_stats.get_user_artists(user_id=self.user['id'], stats_range='all_time')
        self.assertDictEqual(result.all_time.dict(), artists_data)
コード例 #5
0
    def test_delete(self):
        user_id = db_user.create(10, 'frank')

        user = db_user.get(user_id)
        self.assertIsNotNone(user)

        with open(self.path_to_data_file('user_top_artists_db.json')) as f:
            artists_data = ujson.load(f)
        db_stats.insert_user_artists(
            user_id=user_id,
            artists=UserArtistStatJson(**{'all_time': artists_data}),
        )
        user_stats = db_stats.get_user_artists(user_id, 'all_time')
        self.assertIsNotNone(user_stats)

        db_user.delete(user_id)
        user = db_user.get(user_id)
        self.assertIsNone(user)
        user_stats = db_stats.get_user_artists(user_id, 'all_time')
        self.assertIsNone(user_stats)
コード例 #6
0
def profile(user_name):
    # Which database to use to showing user listens.
    db_conn = webserver.timescale_connection._ts
    # Which database to use to show playing_now stream.
    playing_now_conn = webserver.redis_connection._redis

    user = _get_user(user_name)
    # User name used to get user may not have the same case as original user name.
    user_name = user.musicbrainz_id

    # Getting data for current page
    max_ts = request.args.get("max_ts")
    if max_ts is not None:
        try:
            max_ts = int(max_ts)
        except ValueError:
            raise BadRequest("Incorrect timestamp argument max_ts: %s" % request.args.get("max_ts"))

    min_ts = request.args.get("min_ts")
    if min_ts is not None:
        try:
            min_ts = int(min_ts)
        except ValueError:
            raise BadRequest("Incorrect timestamp argument min_ts: %s" % request.args.get("min_ts"))

    # Send min and max listen times to allow React component to hide prev/next buttons accordingly
    (min_ts_per_user, max_ts_per_user) = db_conn.get_timestamps_for_user(user_name)

    if max_ts is None and min_ts is None:
        if max_ts_per_user:
            max_ts = max_ts_per_user + 1
        else:
            max_ts = int(time.time())

    listens = []
    if min_ts_per_user != max_ts_per_user:
        args = {}
        if max_ts:
            args['to_ts'] = max_ts
        else:
            args['from_ts'] = min_ts
        for listen in db_conn.fetch_listens(user_name, limit=LISTENS_PER_PAGE, **args):
            listens.append({
                "track_metadata": listen.data,
                "listened_at": listen.ts_since_epoch,
                "listened_at_iso": listen.timestamp.isoformat() + "Z",
            })

    # If there are no previous listens then display now_playing
    if not listens or listens[0]['listened_at'] >= max_ts_per_user:
        playing_now = playing_now_conn.get_playing_now(user.id)
        if playing_now:
            listen = {
                "track_metadata": playing_now.data,
                "playing_now": "true",
            }
            listens.insert(0, listen)

    user_stats = db_stats.get_user_artists(user.id, 'all_time')
    try:
        artist_count = user_stats.all_time.count
    except (AttributeError, ValidationError):
        artist_count = None

    spotify_data = {}
    current_user_data = {}
    logged_in_user_follows_user = None
    if current_user.is_authenticated:
        spotify_data = spotify.get_user_dict(current_user.id)
        current_user_data = {
            "id": current_user.id,
            "name": current_user.musicbrainz_id,
            "auth_token": current_user.auth_token,
        }
        logged_in_user_follows_user = db_user_relationship.is_following_user(current_user.id, user.id)

    props = {
        "user": {
            "id": user.id,
            "name": user.musicbrainz_id,
        },
        "current_user": current_user_data,
        "listens": listens,
        "latest_listen_ts": max_ts_per_user,
        "oldest_listen_ts": min_ts_per_user,
        "latest_spotify_uri": _get_spotify_uri_for_listens(listens),
        "artist_count": format(artist_count, ",d") if artist_count else None,
        "profile_url": url_for('user.profile', user_name=user_name),
        "mode": "listens",
        "spotify": spotify_data,
        "web_sockets_server_url": current_app.config['WEBSOCKETS_SERVER_URL'],
        "api_url": current_app.config['API_URL'],
        "logged_in_user_follows_user": logged_in_user_follows_user,
    }

    return render_template("user/profile.html",
                           props=ujson.dumps(props),
                           mode='listens',
                           user=user,
                           active_section='listens')
コード例 #7
0
def get_artist_map(user_name: str):
    """
    Get the artist map for user ``user_name``. The artist map shows the number of artists the user has listened to
    from different countries of the world.

    A sample response from the endpoint may look like::

        {
            "payload": {
                "from_ts": 1587945600,
                "last_updated": 1592807084,
                "artist_map": [
                    {
                        "country": "USA",
                        "artist_count": 34
                    },
                    {
                        "country": "GBR",
                        "artist_count": 69
                    },
                    {
                        "country": "IND",
                        "artist_count": 32
                    }
                ],
                "stats_range": "all_time"
                "to_ts": 1589155200,
                "user_id": "ishaanshah"
            }
        }
    .. note::
        - This endpoint is currently in beta
        - We cache the results for this query for a week to improve page load times, if you want to request fresh data you
          can use the ``force_recalculate`` flag.

    :param range: Optional, time interval for which statistics should be returned, possible values are ``week``,
        ``month``, ``year``, ``all_time``, defaults to ``all_time``
    :type range: ``str``
    :param force_recalculate: Optional, recalculate the data instead of returning the cached result.
    :type range: ``bool``
    :statuscode 200: Successful query, you have data!
    :statuscode 204: Statistics for the user haven't been calculated, empty response will be returned
    :statuscode 400: Bad request, check ``response['error']`` for more details
    :statuscode 404: User not found
    :resheader Content-Type: *application/json*

    """
    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: {}".format(user_name))

    stats_range = request.args.get('range', default='all_time')
    if not _is_valid_range(stats_range):
        raise APIBadRequest("Invalid range: {}".format(stats_range))

    recalculate_param = request.args.get('force_recalculate', default='false')
    if recalculate_param.lower() not in ['true', 'false']:
        raise APIBadRequest(
            "Invalid value of force_recalculate: {}".format(recalculate_param))
    force_recalculate = recalculate_param.lower() == 'true'

    # Check if stats are present in DB, if not calculate them
    calculated = not force_recalculate
    stats = db_stats.get_user_artist_map(user['id'], stats_range)
    if stats is None or getattr(stats, stats_range) is None:
        calculated = False

    # Check if the stats present in DB have been calculated in the past week, if not recalculate them
    stale = False
    if calculated:
        last_updated = getattr(stats, stats_range).last_updated
        if (datetime.now() - datetime.fromtimestamp(last_updated)
            ).days >= STATS_CALCULATION_INTERVAL:
            stale = True

    if stale or not calculated:
        artist_stats = db_stats.get_user_artists(user['id'], stats_range)

        # If top artists are missing, return the stale stats if present, otherwise return 204
        if artist_stats is None or getattr(artist_stats, stats_range) is None:
            if stale:
                result = stats
            else:
                raise APINoContent('')
        else:
            # Calculate the data
            artist_msids = defaultdict(lambda: 0)
            artist_mbids = defaultdict(lambda: 0)
            top_artists = getattr(artist_stats, stats_range).artists
            for artist in top_artists:
                if artist.artist_msid is not None:
                    artist_msids[artist.artist_msid] += artist.listen_count
                else:
                    for artist_mbid in artist.artist_mbids:
                        artist_mbids[artist_mbid] += artist.listen_count

            country_code_data = _get_country_codes(artist_msids, artist_mbids)
            result = UserArtistMapStatJson(
                **{
                    stats_range: {
                        "artist_map": country_code_data,
                        "from_ts": int(
                            getattr(artist_stats, stats_range).from_ts),
                        "to_ts": int(getattr(artist_stats, stats_range).to_ts),
                        "last_updated": int(datetime.now().timestamp())
                    }
                })

            # Store in DB for future use
            try:
                db_stats.insert_user_artist_map(user['id'], result)
            except Exception as err:
                current_app.logger.error(
                    "Error while inserting artist map stats for {user}. Error: {err}. Data: {data}"
                    .format(user=user_name, err=err, data=result),
                    exc_info=True)
    else:
        result = stats

    return jsonify({
        "payload": {
            "user_id": user_name,
            "range": stats_range,
            **(getattr(result, stats_range).dict())
        }
    })
コード例 #8
0
def get_user_artist(user_name):
    """
    Get top artists for user ``user_name``.


    A sample response from the endpoint may look like::

        {
            "payload": {
                "artists": [
                    {
                       "artist_mbids": ["93e6118e-7fa8-49f6-9e02-699a1ebce105"],
                       "artist_msid": "d340853d-7408-4a0d-89c2-6ff13e568815",
                       "artist_name": "The Local train",
                       "listen_count": 385
                    },
                    {
                       "artist_mbids": ["ae9ed5e2-4caf-4b3d-9cb3-2ad626b91714"],
                       "artist_msid": "ba64b195-01dd-4613-9534-bb87dc44cffb",
                       "artist_name": "Lenka",
                       "listen_count": 333
                    },
                    {
                       "artist_mbids": ["cc197bad-dc9c-440d-a5b5-d52ba2e14234"],
                       "artist_msid": "6599e41e-390c-4855-a2ac-68ee798538b4",
                       "artist_name": "Coldplay",
                       "listen_count": 321
                    }
                ],
                "count": 3,
                "total_artist_count": 175,
                "range": "all_time",
                "last_updated": 1588494361,
                "user_id": "John Doe",
                "from_ts": 1009823400,
                "to_ts": 1590029157
            }
        }

    .. note::
        - This endpoint is currently in beta
        - ``artist_mbids`` and ``artist_msid`` are optional fields and may not be present in all the responses

    :param count: Optional, number of artists to return, Default: :data:`~webserver.views.api.DEFAULT_ITEMS_PER_GET`
        Max: :data:`~webserver.views.api.MAX_ITEMS_PER_GET`
    :type count: ``int``
    :param offset: Optional, number of artists to skip from the beginning, for pagination.
        Ex. An offset of 5 means the top 5 artists will be skipped, defaults to 0
    :type offset: ``int``
    :param range: Optional, time interval for which statistics should be collected, possible values are ``week``,
        ``month``, ``year``, ``all_time``, defaults to ``all_time``
    :type range: ``str``
    :statuscode 200: Successful query, you have data!
    :statuscode 204: Statistics for the user haven't been calculated, empty response will be returned
    :statuscode 400: Bad request, check ``response['error']`` for more details
    :statuscode 404: User not found
    :resheader Content-Type: *application/json*
    """
    user = db_user.get_by_mb_id(user_name)
    if user is None:
        raise APINotFound("Cannot find user: %s" % user_name)

    stats_range = request.args.get('range', default='all_time')
    if not _is_valid_range(stats_range):
        raise APIBadRequest("Invalid range: {}".format(stats_range))

    offset = get_non_negative_param('offset', default=0)
    count = get_non_negative_param('count', default=DEFAULT_ITEMS_PER_GET)

    stats = db_stats.get_user_artists(user['id'], stats_range)
    if stats is None or getattr(stats, stats_range) is None:
        raise APINoContent('')

    entity_list, total_entity_count = _process_user_entity(stats,
                                                           stats_range,
                                                           offset,
                                                           count,
                                                           entity='artist')
    from_ts = int(getattr(stats, stats_range).from_ts)
    to_ts = int(getattr(stats, stats_range).to_ts)
    last_updated = int(stats.last_updated.timestamp())

    return jsonify({
        'payload': {
            "user_id": user_name,
            "artists": entity_list,
            "count": len(entity_list),
            "total_artist_count": total_entity_count,
            "offset": offset,
            "range": stats_range,
            "from_ts": from_ts,
            "to_ts": to_ts,
            "last_updated": last_updated,
        }
    })
コード例 #9
0
 def test_get_user_artists(self):
     data_inserted = self.insert_test_data()
     data = db_stats.get_user_artists(self.user['id'])
     self.assertEqual(data['artist']['count'], 2)
コード例 #10
0
 def test_get_user_artists(self):
     data_inserted = self.insert_test_data()
     result = db_stats.get_user_artists(self.user['id'], 'all_time')
     self.assertDictEqual(result.all_time.dict(), data_inserted['user_artists'])
コード例 #11
0
ファイル: user.py プロジェクト: regagain/listenbrainz-server
def profile(user_name):
    # Which database to use to showing user listens.
    db_conn = webserver.influx_connection._influx
    # Which database to use to show playing_now stream.
    playing_now_conn = webserver.redis_connection._redis

    user = _get_user(user_name)
    # User name used to get user may not have the same case as original user name.
    user_name = user.musicbrainz_id

    try:
        have_listen_count = True
        listen_count = db_conn.get_listen_count_for_user(user_name)
    except (InfluxDBServerError, InfluxDBClientError):
        have_listen_count = False
        listen_count = 0

    # Getting data for current page
    max_ts = request.args.get("max_ts")
    if max_ts is not None:
        try:
            max_ts = int(max_ts)
        except ValueError:
            raise BadRequest("Incorrect timestamp argument max_ts: %s" %
                             request.args.get("max_ts"))

    min_ts = request.args.get("min_ts")
    if min_ts is not None:
        try:
            min_ts = int(min_ts)
        except ValueError:
            raise BadRequest("Incorrect timestamp argument min_ts: %s" %
                             request.args.get("min_ts"))

    if max_ts is None and min_ts is None:
        max_ts = int(time.time())

    args = {}
    if max_ts:
        args['to_ts'] = max_ts
    else:
        args['from_ts'] = min_ts

    listens = []
    for listen in db_conn.fetch_listens(user_name,
                                        limit=LISTENS_PER_PAGE,
                                        **args):
        # Let's fetch one more listen, so we know to show a next page link or not
        listens.append({
            "track_metadata": listen.data,
            "listened_at": listen.ts_since_epoch,
            "listened_at_iso": listen.timestamp.isoformat() + "Z",
        })

    # Calculate if we need to show next/prev buttons
    previous_listen_ts = None
    next_listen_ts = None
    if listens:
        (min_ts_per_user,
         max_ts_per_user) = db_conn.get_timestamps_for_user(user_name)
        if min_ts_per_user >= 0:
            if listens[-1]['listened_at'] > min_ts_per_user:
                next_listen_ts = listens[-1]['listened_at']
            else:
                next_listen_ts = None

            if listens[0]['listened_at'] < max_ts_per_user:
                previous_listen_ts = listens[0]['listened_at']
            else:
                previous_listen_ts = None

    # If there are no previous listens then display now_playing
    if not previous_listen_ts:
        playing_now = playing_now_conn.get_playing_now(user.id)
        if playing_now:
            listen = {
                "track_metadata": playing_now.data,
                "playing_now": "true",
            }
            listens.insert(0, listen)

    user_stats = db_stats.get_user_artists(user.id)
    try:
        artist_count = int(user_stats['artist']['count'])
    except (KeyError, TypeError):
        artist_count = None

    return render_template(
        "user/profile.html",
        user=user,
        listens=listens,
        previous_listen_ts=previous_listen_ts,
        next_listen_ts=next_listen_ts,
        spotify_uri=_get_spotify_uri_for_listens(listens),
        have_listen_count=have_listen_count,
        listen_count=format(int(listen_count), ",d"),
        artist_count=format(artist_count, ",d") if artist_count else None,
        section='listens')
コード例 #12
0
 def test_get_user_artists(self):
     data_inserted = self.insert_test_data()
     data = db_stats.get_user_artists(self.user['id'])
     self.assertEqual(data['artist']['count'], 2)
コード例 #13
0
def profile(user_name):
    # Which database to use to showing user listens.
    db_conn = webserver.influx_connection._influx
    # Which database to use to show playing_now stream.
    playing_now_conn = webserver.redis_connection._redis

    user = _get_user(user_name)
    # User name used to get user may not have the same case as original user name.
    user_name = user.musicbrainz_id

    try:
        have_listen_count = True
        listen_count = db_conn.get_listen_count_for_user(user_name)
    except (InfluxDBServerError, InfluxDBClientError):
        have_listen_count = False
        listen_count = 0

    # Getting data for current page
    max_ts = request.args.get("max_ts")
    if max_ts is not None:
        try:
            max_ts = int(max_ts)
        except ValueError:
            raise BadRequest("Incorrect timestamp argument max_ts: %s" % request.args.get("max_ts"))

    min_ts = request.args.get("min_ts")
    if min_ts is not None:
        try:
            min_ts = int(min_ts)
        except ValueError:
            raise BadRequest("Incorrect timestamp argument min_ts: %s" % request.args.get("min_ts"))

    if max_ts is None and min_ts is None:
        max_ts = int(time.time())

    args = {}
    if max_ts:
        args['to_ts'] = max_ts
    else:
        args['from_ts'] = min_ts

    listens = []
    for listen in db_conn.fetch_listens(user_name, limit=LISTENS_PER_PAGE, **args):
        # Let's fetch one more listen, so we know to show a next page link or not
        listens.append({
            "track_metadata": listen.data,
            "listened_at": listen.ts_since_epoch,
            "listened_at_iso": listen.timestamp.isoformat() + "Z",
        })

    # Calculate if we need to show next/prev buttons
    previous_listen_ts = None
    next_listen_ts = None
    if listens:
        (min_ts_per_user, max_ts_per_user) = db_conn.get_timestamps_for_user(user_name)
        if min_ts_per_user >= 0:
            if listens[-1]['listened_at'] > min_ts_per_user:
                next_listen_ts = listens[-1]['listened_at']
            else:
                next_listen_ts = None

            if listens[0]['listened_at'] < max_ts_per_user:
                previous_listen_ts = listens[0]['listened_at']
            else:
                previous_listen_ts = None

    # If there are no previous listens then display now_playing
    if not previous_listen_ts:
        playing_now = playing_now_conn.get_playing_now(user.id)
        if playing_now:
            listen = {
                "track_metadata": playing_now.data,
                "playing_now": "true",
            }
            listens.insert(0, listen)

    user_stats = db_stats.get_user_artists(user.id)
    try:
        artist_count = int(user_stats['artist']['count'])
    except (KeyError, TypeError):
        artist_count = None

    return render_template(
        "user/profile.html",
        user=user,
        listens=listens,
        previous_listen_ts=previous_listen_ts,
        next_listen_ts=next_listen_ts,
        spotify_uri=_get_spotify_uri_for_listens(listens),
        have_listen_count=have_listen_count,
        listen_count=format(int(listen_count), ",d"),
        artist_count=format(artist_count, ",d") if artist_count else None,
        section='listens'
    )
コード例 #14
0
def profile(user_name):
    # Which database to use to showing user listens.
    db_conn = webserver.influx_connection._influx
    # Which database to use to show playing_now stream.
    playing_now_conn = webserver.redis_connection._redis

    user = _get_user(user_name)
    # User name used to get user may not have the same case as original user name.
    user_name = user.musicbrainz_id

    try:
        have_listen_count = True
        listen_count = db_conn.get_listen_count_for_user(user_name)
    except (InfluxDBServerError, InfluxDBClientError):
        have_listen_count = False
        listen_count = 0

    # Getting data for current page
    max_ts = request.args.get("max_ts")
    if max_ts is not None:
        try:
            max_ts = int(max_ts)
        except ValueError:
            raise BadRequest("Incorrect timestamp argument max_ts: %s" %
                             request.args.get("max_ts"))

    min_ts = request.args.get("min_ts")
    if min_ts is not None:
        try:
            min_ts = int(min_ts)
        except ValueError:
            raise BadRequest("Incorrect timestamp argument min_ts: %s" %
                             request.args.get("min_ts"))

    if max_ts is None and min_ts is None:
        max_ts = int(time.time())

    args = {}
    if max_ts:
        args['to_ts'] = max_ts
    else:
        args['from_ts'] = min_ts

    listens = []
    for listen in db_conn.fetch_listens(user_name,
                                        limit=LISTENS_PER_PAGE,
                                        **args):
        # Let's fetch one more listen, so we know to show a next page link or not
        listens.append({
            "track_metadata": listen.data,
            "listened_at": listen.ts_since_epoch,
            "listened_at_iso": listen.timestamp.isoformat() + "Z",
        })

    latest_listen = db_conn.fetch_listens(user_name=user_name,
                                          limit=1,
                                          to_ts=int(time.time()))
    latest_listen_ts = latest_listen[0].ts_since_epoch if len(
        latest_listen) > 0 else 0

    # Calculate if we need to show next/prev buttons
    previous_listen_ts = None
    next_listen_ts = None
    if listens:
        (min_ts_per_user,
         max_ts_per_user) = db_conn.get_timestamps_for_user(user_name)
        if min_ts_per_user >= 0:
            if listens[-1]['listened_at'] > min_ts_per_user:
                next_listen_ts = listens[-1]['listened_at']
            else:
                next_listen_ts = None

            if listens[0]['listened_at'] < max_ts_per_user:
                previous_listen_ts = listens[0]['listened_at']
            else:
                previous_listen_ts = None

    # If there are no previous listens then display now_playing
    if not previous_listen_ts:
        playing_now = playing_now_conn.get_playing_now(user.id)
        if playing_now:
            listen = {
                "track_metadata": playing_now.data,
                "playing_now": "true",
            }
            listens.insert(0, listen)

    user_stats = db_stats.get_user_artists(user.id)
    try:
        artist_count = int(user_stats['artist']['count'])
    except (KeyError, TypeError):
        artist_count = None

    spotify_data = {}
    if current_user.is_authenticated:
        spotify_data = spotify.get_user_dict(current_user.id)

    props = {
        "user": {
            "id": user.id,
            "name": user.musicbrainz_id,
        },
        "listens": listens,
        "previous_listen_ts": previous_listen_ts,
        "next_listen_ts": next_listen_ts,
        "latest_listen_ts": latest_listen_ts,
        "latest_spotify_uri": _get_spotify_uri_for_listens(listens),
        "have_listen_count": have_listen_count,
        "listen_count": format(int(listen_count), ",d"),
        "artist_count": format(artist_count, ",d") if artist_count else None,
        "profile_url": url_for('user.profile', user_name=user_name),
        "mode": "listens",
        "spotify": spotify_data,
        "web_sockets_server_url": current_app.config['WEBSOCKETS_SERVER_URL'],
        "api_url": current_app.config['API_URL'],
    }

    return render_template("user/profile.html",
                           props=ujson.dumps(props),
                           mode='listens',
                           user=user,
                           active_section='listens')