示例#1
0
def write_ride_photo_primary(strava_activity, ride):
    """
    Store primary photo for activity from the main detail-level activity.

    :param strava_activity: The Strava :class:`stravalib.orm.Activity` object.
    :type strava_activity: :class:`stravalib.orm.Activity`

    :param ride: The db model object for ride.
    :type ride: bafs.orm.Ride
    """
    # If we have > 1 instagram photo, then we don't do anything.
    if strava_activity.photo_count > 1:
        log.debug("Ignoring basic sync for {} since there are > 1 instagram photos.")
        return

    # Start by removing any priamry photos for this ride.
    meta.engine.execute(RidePhoto.__table__.delete().where(and_(RidePhoto.ride_id == strava_activity.id,
                                                              RidePhoto.primary == True)))

    primary_photo = strava_activity.photos.primary

    if primary_photo:
        if primary_photo.source == 1:
            _write_strava_photo_primary(primary_photo, ride)
        else:
            _write_instagram_photo_primary(primary_photo, ride)
示例#2
0
def index():
    page = int(request.args.get('page', 1))
    if page < 1:
        page = 1

    page_size = 60
    offset = page_size * (page - 1)
    limit = page_size

    log.debug("Page = {0}, offset={1}, limit={2}".format(page, offset, limit))

    total_q = meta.session_factory().query(RidePhoto).join(Ride).order_by(Ride.start_date.desc())
    num_photos = total_q.count()

    page_q = total_q.limit(limit).offset(offset)

    if num_photos < offset:
        page = 1

    total_pages = int(math.ceil( (1.0 * num_photos) / page_size))

    if page > total_pages:
        page = total_pages

    return render_template('photos.html',
                           photos=page_q,
                           page=page,
                           total_pages=total_pages)
示例#3
0
def write_ride_photo_primary(strava_activity, ride):
    """
    Store primary photo for activity from the main detail-level activity.

    :param strava_activity: The Strava :class:`stravalib.orm.Activity` object.
    :type strava_activity: :class:`stravalib.orm.Activity`

    :param ride: The db model object for ride.
    :type ride: bafs.orm.Ride
    """
    # If we have > 1 instagram photo, then we don't do anything.
    if strava_activity.photo_count > 1:
        log.debug(
            "Ignoring basic sync for {} since there are > 1 instagram photos.")
        return

    # Start by removing any priamry photos for this ride.
    meta.engine.execute(RidePhoto.__table__.delete().where(
        and_(RidePhoto.ride_id == strava_activity.id,
             RidePhoto.primary == True)))

    primary_photo = strava_activity.photos.primary

    if primary_photo:
        if primary_photo.source == 1:
            _write_strava_photo_primary(primary_photo, ride)
        else:
            _write_instagram_photo_primary(primary_photo, ride)
示例#4
0
def update_ride_from_activity(strava_activity, ride):
    """
    Refactoring to just set ride properties from the Strava Activity object.

    :param strava_activity: The Strava Activyt
    :type strava_activity: stravalib.orm.Activity
    :param ride: The ride model object.
    :type ride: Ride
    """
    # Should apply to both new and preexisting rides ...
    # If there are multiple instagram photos, then request syncing of non-primary photos too.

    if strava_activity.photo_count > 1 and ride.photos_fetched is None:

        log.debug("Scheduling non-primary photos sync for {!r}".format(ride))
        ride.photos_fetched = False

    ride.private = bool(strava_activity.private)
    ride.name = strava_activity.name
    ride.start_date = strava_activity.start_date_local

    # We need to round so that "1.0" miles in strava is "1.0" miles when we convert back from meters.
    ride.distance = round(float(unithelper.miles(strava_activity.distance)), 3)

    ride.average_speed = float(unithelper.mph(strava_activity.average_speed))
    ride.maximum_speed = float(unithelper.mph(strava_activity.max_speed))
    ride.elapsed_time = timedelta_to_seconds(strava_activity.elapsed_time)
    ride.moving_time = timedelta_to_seconds(strava_activity.moving_time)

    location_parts = []
    if strava_activity.location_city:
        location_parts.append(strava_activity.location_city)
    if strava_activity.location_state:
        location_parts.append(strava_activity.location_state)
    location_str = ', '.join(location_parts)

    ride.location = location_str

    ride.commute = strava_activity.commute
    ride.trainer = strava_activity.trainer
    ride.manual = strava_activity.manual
    ride.elevation_gain = float(
        unithelper.feet(strava_activity.total_elevation_gain))
    ride.timezone = str(strava_activity.timezone)

    # # Short-circuit things that might result in more obscure db errors later.
    if ride.elapsed_time is None:
        raise DataEntryError("Activities cannot have null elapsed time.")

    if ride.moving_time is None:
        raise DataEntryError("Activities cannot have null moving time.")

    if ride.distance is None:
        raise DataEntryError("Activities cannot have null distance.")

    log.debug("Writing ride for {athlete!r}: \"{ride!r}\" on {date}".format(
        athlete=ride.athlete.name,
        ride=ride.name,
        date=ride.start_date.strftime('%m/%d/%y')))
示例#5
0
def update_ride_from_activity(strava_activity, ride):
    """
    Refactoring to just set ride properties from the Strava Activity object.

    :param strava_activity: The Strava Activyt
    :type strava_activity: stravalib.orm.Activity
    :param ride: The ride model object.
    :type ride: Ride
    """
     # Should apply to both new and preexisting rides ...
    # If there are multiple instagram photos, then request syncing of non-primary photos too.

    if strava_activity.photo_count > 1 and ride.photos_fetched is None:


        log.debug("Scheduling non-primary photos sync for {!r}".format(ride))
        ride.photos_fetched = False

    ride.private = bool(strava_activity.private)
    ride.name = strava_activity.name
    ride.start_date = strava_activity.start_date_local

    # We need to round so that "1.0" miles in strava is "1.0" miles when we convert back from meters.
    ride.distance = round(float(unithelper.miles(strava_activity.distance)), 3)

    ride.average_speed = float(unithelper.mph(strava_activity.average_speed))
    ride.maximum_speed = float(unithelper.mph(strava_activity.max_speed))
    ride.elapsed_time = timedelta_to_seconds(strava_activity.elapsed_time)
    ride.moving_time = timedelta_to_seconds(strava_activity.moving_time)

    location_parts = []
    if strava_activity.location_city:
        location_parts.append(strava_activity.location_city)
    if strava_activity.location_state:
        location_parts.append(strava_activity.location_state)
    location_str = ', '.join(location_parts)

    ride.location = location_str

    ride.commute = strava_activity.commute
    ride.trainer = strava_activity.trainer
    ride.manual = strava_activity.manual
    ride.elevation_gain = float(unithelper.feet(strava_activity.total_elevation_gain))
    ride.timezone = str(strava_activity.timezone)

    # # Short-circuit things that might result in more obscure db errors later.
    if ride.elapsed_time is None:
        raise DataEntryError("Activities cannot have null elapsed time.")

    if ride.moving_time is None:
        raise DataEntryError("Activities cannot have null moving time.")

    if ride.distance is None:
        raise DataEntryError("Activities cannot have null distance.")

    log.debug("Writing ride for {athlete!r}: \"{ride!r}\" on {date}".format(athlete=ride.athlete.name,
                                                                        ride=ride.name,
                                                                        date=ride.start_date.strftime('%m/%d/%y')))
示例#6
0
def disambiguate_athlete_display_names():
    q = meta.scoped_session().query(orm.Athlete)
    q = q.filter(orm.Athlete.access_token != None)
    athletes = q.all()

    # Ok, here is the plan; bin these things together based on firstname and last initial.
    # Then iterate over each bin and if there are multiple entries, find the least number
    # of letters to make them all different. (we should be able to use set intersection
    # to check for differences within the bins?)

    def firstlast(name):
        name_parts = a.name.split(' ')
        fname = name_parts[0]
        if len(name_parts) < 2:
            lname = None
        else:
            lname = name_parts[-1]
        return (fname, lname)

    athletes_bin = {}
    for a in athletes:
        (fname, lname) = firstlast(a.name)
        if lname is None:
            # We only care about people with first and last names for this exercise
            # key = fname
            continue
        else:
            key = '{0} {1}'.format(fname, lname[0])
        athletes_bin.setdefault(key, []).append(a)

    for (name_key, athletes) in athletes_bin.items():
        shortest_lname = min([firstlast(a.name)[1] for a in athletes], key=len)
        required_length = None
        for i in range(len(shortest_lname)):
            # Calculate fname + lname-of-x-chars for each athlete.
            # If unique, then use this number and update the model objects
            candidate_short_lasts = [firstlast(a.name)[1][:i + 1] for a in athletes]
            if len(set(candidate_short_lasts)) == len(candidate_short_lasts):
                required_length = i + 1
                break

        if required_length is not None:
            for a in athletes:
                fname, lname = firstlast(a.name)
                log.debug("Converting '{fname} {lname}' -> '{fname} {minlname}".format(fname=fname,
                                                                                       lname=lname,
                                                                                       minlname=lname[
                                                                                                :required_length]))
                a.display_name = '{0} {1}'.format(fname, lname[:required_length])
        else:
            log.debug("Unable to find a minimum lastname; using full lastname.")
            # Just use full names
            for a in athletes:
                fname, lname = firstlast(a.name)
                a.display_name = '{0} {1}'.format(fname, lname[:required_length])

    # Update the database with new values
    meta.scoped_session().commit()
示例#7
0
def _geo_tracks(start_date=None, end_date=None, team_id=None):

    # These dates  must be made naive, since we don't have TZ info stored in our ride columns.
    if start_date:
        start_date = arrow.get(start_date).datetime.replace(tzinfo=None)

    if end_date:
        end_date = arrow.get(end_date).datetime.replace(tzinfo=None)

    log.debug("Filtering on start_date: {}".format(start_date))
    log.debug("Filtering on end_date: {}".format(end_date))

    sess = meta.scoped_session()

    q = sess.query(RideTrack).join(Ride).join(Athlete)
    q = q.filter(Ride.private==False)

    if team_id:
        q = q.filter(Athlete.team_id==team_id)

    if start_date:
        q = q.filter(Ride.start_date >= start_date)
    if end_date:
        q = q.filter(Ride.start_date < end_date)

    linestrings = []
    for ride_track in q:
        assert isinstance(ride_track, RideTrack)
        ride_tz = pytz.timezone(ride_track.ride.timezone)
        wkt = sess.scalar(ride_track.gps_track.wkt)

        coordinates = []
        for (i, (lon, lat)) in enumerate(parse_linestring(wkt)):
            elapsed_time = ride_track.ride.start_date + timedelta(seconds=ride_track.time_stream[i])

            point = (
                float(Decimal(lon)),
                float(Decimal(lat)),
                float(Decimal(ride_track.elevation_stream[i])),
                ride_tz.localize(elapsed_time).isoformat()
            )

            coordinates.append(point)

        linestrings.append(coordinates)

    geojson_structure = {"type": "MultiLineString", "coordinates": linestrings}

    #return geojson.dumps(geojson.MultiLineString(linestrings))
    return json.dumps(geojson_structure)
示例#8
0
def _geo_tracks(start_date=None, end_date=None, team_id=None):

    # These dates  must be made naive, since we don't have TZ info stored in our ride columns.
    if start_date:
        start_date = arrow.get(start_date).datetime.replace(tzinfo=None)

    if end_date:
        end_date = arrow.get(end_date).datetime.replace(tzinfo=None)

    log.debug("Filtering on start_date: {}".format(start_date))
    log.debug("Filtering on end_date: {}".format(end_date))

    sess = meta.scoped_session()

    q = sess.query(RideTrack).join(Ride).join(Athlete)
    q = q.filter(Ride.private == False)

    if team_id:
        q = q.filter(Athlete.team_id == team_id)

    if start_date:
        q = q.filter(Ride.start_date >= start_date)
    if end_date:
        q = q.filter(Ride.start_date < end_date)

    linestrings = []
    for ride_track in q:
        assert isinstance(ride_track, RideTrack)
        ride_tz = pytz.timezone(ride_track.ride.timezone)
        wkt = sess.scalar(ride_track.gps_track.wkt)

        coordinates = []
        for (i, (lon, lat)) in enumerate(parse_linestring(wkt)):
            elapsed_time = ride_track.ride.start_date + timedelta(
                seconds=ride_track.time_stream[i])

            point = (float(Decimal(lon)), float(Decimal(lat)),
                     float(Decimal(ride_track.elevation_stream[i])),
                     ride_tz.localize(elapsed_time).isoformat())

            coordinates.append(point)

        linestrings.append(coordinates)

    geojson_structure = {"type": "MultiLineString", "coordinates": linestrings}

    #return geojson.dumps(geojson.MultiLineString(linestrings))
    return json.dumps(geojson_structure)
示例#9
0
def _write_strava_photo_primary(photo, ride):
    """
    Writes a strava native (source=1) primary photo to db.

    :param photo: The primary photo from an activity.
    :type photo: stravalib.orm.ActivityPhotoPrimary
    :param ride: The db model object for ride.
    :type ride: bafs.orm.Ride
    :return: The newly added ride photo object.
    :rtype: bafs.orm.RidePhoto
    """
    # 'photos': {u'count': 1,
    #   u'primary': {u'id': None,
    #    u'source': 1,
    #    u'unique_id': u'35453b4b-0fc1-46fd-a824-a4548426b57d',
    #    u'urls': {u'100': u'https://dgtzuqphqg23d.cloudfront.net/Vvm_Mcfk1SP-VWdglQJImBvKzGKRJrHlNN4BqAqD1po-128x96.jpg',
    #     u'600': u'https://dgtzuqphqg23d.cloudfront.net/Vvm_Mcfk1SP-VWdglQJImBvKzGKRJrHlNN4BqAqD1po-768x576.jpg'}},
    #   u'use_primary_photo': False},

    if not photo.urls:
        log.warning(
            "Photo {} present, but has no URLs (skipping)".format(photo))
        return None

    p = RidePhoto()
    p.id = photo.unique_id
    p.primary = True
    p.source = photo.source
    p.ref = None
    p.img_l = photo.urls['600']
    p.img_t = photo.urls['100']
    p.ride_id = ride.id

    log.debug("Writing (primary) Strava ride photo: {}".format(p))

    meta.scoped_session().add(p)
    meta.scoped_session().flush()
    return p
示例#10
0
def write_ride_efforts(strava_activity, ride):
    """
    Writes out all effort associated with a ride to the database.

    :param strava_activity: The :class:`stravalib.orm.Activity` that is associated with this effort.
    :type strava_activity: :class:`stravalib.orm.Activity`

    :param ride: The db model object for ride.
    :type ride: :class:`bafs.orm.Ride`
    """
    assert isinstance(strava_activity, strava_model.Activity)
    assert isinstance(ride, Ride)

    try:
        # Start by removing any existing segments for the ride.
        meta.engine.execute(RideEffort.__table__.delete().where(
            RideEffort.ride_id == strava_activity.id))

        # Then add them back in
        for se in strava_activity.segment_efforts:
            effort = RideEffort(id=se.id,
                                ride_id=strava_activity.id,
                                elapsed_time=timedelta_to_seconds(
                                    se.elapsed_time),
                                segment_name=se.segment.name,
                                segment_id=se.segment.id)

            log.debug("Writing ride effort: {se_id}: {effort!r}".format(
                se_id=se.id, effort=effort.segment_name))

            meta.scoped_session().add(effort)
            meta.scoped_session().flush()

        ride.efforts_fetched = True

    except:
        log.exception("Error adding effort for ride: {0}".format(ride))
        raise
示例#11
0
def _write_strava_photo_primary(photo, ride):
    """
    Writes a strava native (source=1) primary photo to db.

    :param photo: The primary photo from an activity.
    :type photo: stravalib.orm.ActivityPhotoPrimary
    :param ride: The db model object for ride.
    :type ride: bafs.orm.Ride
    :return: The newly added ride photo object.
    :rtype: bafs.orm.RidePhoto
    """
    # 'photos': {u'count': 1,
    #   u'primary': {u'id': None,
    #    u'source': 1,
    #    u'unique_id': u'35453b4b-0fc1-46fd-a824-a4548426b57d',
    #    u'urls': {u'100': u'https://dgtzuqphqg23d.cloudfront.net/Vvm_Mcfk1SP-VWdglQJImBvKzGKRJrHlNN4BqAqD1po-128x96.jpg',
    #     u'600': u'https://dgtzuqphqg23d.cloudfront.net/Vvm_Mcfk1SP-VWdglQJImBvKzGKRJrHlNN4BqAqD1po-768x576.jpg'}},
    #   u'use_primary_photo': False},

    if not photo.urls:
        log.warning("Photo {} present, but has no URLs (skipping)".format(photo))
        return None

    p = RidePhoto()
    p.id = photo.unique_id
    p.primary = True
    p.source = photo.source
    p.ref = None
    p.img_l = photo.urls['600']
    p.img_t = photo.urls['100']
    p.ride_id = ride.id

    log.debug("Writing (primary) Strava ride photo: {}".format(p))

    meta.scoped_session().add(p)
    meta.scoped_session().flush()
    return p
示例#12
0
def write_ride_efforts(strava_activity, ride):
    """
    Writes out all effort associated with a ride to the database.

    :param strava_activity: The :class:`stravalib.orm.Activity` that is associated with this effort.
    :type strava_activity: :class:`stravalib.orm.Activity`

    :param ride: The db model object for ride.
    :type ride: :class:`bafs.orm.Ride`
    """
    assert isinstance(strava_activity, strava_model.Activity)
    assert isinstance(ride, Ride)

    try:
        # Start by removing any existing segments for the ride.
        meta.engine.execute(RideEffort.__table__.delete().where(RideEffort.ride_id == strava_activity.id))

        # Then add them back in
        for se in strava_activity.segment_efforts:
            effort = RideEffort(id=se.id,
                                ride_id=strava_activity.id,
                                elapsed_time=timedelta_to_seconds(se.elapsed_time),
                                segment_name=se.segment.name,
                                segment_id=se.segment.id)

            log.debug("Writing ride effort: {se_id}: {effort!r}".format(se_id=se.id,
                                                                        effort=effort.segment_name))

            meta.scoped_session().add(effort)
            meta.scoped_session().flush()

        ride.efforts_fetched = True

    except:
        log.exception("Error adding effort for ride: {0}".format(ride))
        raise
示例#13
0
def _write_instagram_photo_primary(photo, ride):
    """
    Writes an instagram primary photo to db.

    :param photo: The primary photo from an activity.
    :type photo: stravalib.orm.ActivityPhotoPrimary
    :param ride: The db model object for ride.
    :type ride: bafs.orm.Ride
    :return: The newly added ride photo object.
    :rtype: bafs.orm.RidePhoto
    """
    # Here is when we have an Instagram photo as primary:
    #  u'photos': {u'count': 1,
    #   u'primary': {u'id': 106409096,
    #    u'source': 2,
    #    u'unique_id': None,
    #    u'urls': {u'100': u'https://instagram.com/p/88qaqZvrBI/media?size=t',
    #     u'600': u'https://instagram.com/p/88qaqZvrBI/media?size=l'}},
    #   u'use_prima ry_photo': False},

    media = None

    # This doesn't work any more; Instagram changed their API to use OAuth.
    #insta_client = insta.configured_instagram_client()
    #shortcode = re.search(r'/p/([^/]+)/', photo.urls['100']).group(1)
    # try:
    #     #log.debug("Fetching Instagram media for shortcode: {}".format(shortcode))
    #     media = insta_client.media_shortcode(shortcode)
    # except (InstagramAPIError, InstagramClientError) as e:
    #     if e.status_code == 400:
    #         log.warning("Instagram photo {} for ride {}; user is set to private".format(shortcode, ride))
    #     elif e.status_code == 404:
    #         log.warning("Photo {} for ride {}; shortcode not found".format(shortcode, ride))
    #     else:
    #         log.exception("Error fetching instagram photo {}".format(photo))

    p = RidePhoto()

    if media:
        p.id = media.id
        p.ref = media.link
        p.img_l = media.get_standard_resolution_url()
        p.img_t = media.get_thumbnail_url()
        if media.caption:
            p.caption = media.caption.text
    else:
        p.id = photo.id
        p.ref = re.match(r'(.+/)media\?size=.$', photo.urls['100']).group(1)
        p.img_l = photo.urls['600']
        p.img_t = photo.urls['100']

    p.ride_id = ride.id
    p.primary = True
    p.source = photo.source

    log.debug("Writing (primary) Instagram ride photo: {!r}".format(p))

    meta.scoped_session().add(p)
    meta.scoped_session().flush()

    return p
示例#14
0
def disambiguate_athlete_display_names():
    q = meta.scoped_session().query(orm.Athlete)
    q = q.filter(orm.Athlete.access_token != None)
    athletes = q.all()

    # Ok, here is the plan; bin these things together based on firstname and last initial.
    # Then iterate over each bin and if there are multiple entries, find the least number
    # of letters to make them all different. (we should be able to use set intersection
    # to check for differences within the bins?)

    def firstlast(name):
        name_parts = a.name.split(' ')
        fname = name_parts[0]
        if len(name_parts) < 2:
            lname = None
        else:
            lname = name_parts[-1]
        return (fname, lname)

    athletes_bin = {}
    for a in athletes:
        (fname, lname) = firstlast(a.name)
        if lname is None:
            # We only care about people with first and last names for this exercise
            # key = fname
            continue
        else:
            key = '{0} {1}'.format(fname, lname[0])
        athletes_bin.setdefault(key, []).append(a)

    for (name_key, athletes) in athletes_bin.items():
        shortest_lname = min([firstlast(a.name)[1] for a in athletes], key=len)
        required_length = None
        for i in range(len(shortest_lname)):
            # Calculate fname + lname-of-x-chars for each athlete.
            # If unique, then use this number and update the model objects
            candidate_short_lasts = [
                firstlast(a.name)[1][:i + 1] for a in athletes
            ]
            if len(set(candidate_short_lasts)) == len(candidate_short_lasts):
                required_length = i + 1
                break

        if required_length is not None:
            for a in athletes:
                fname, lname = firstlast(a.name)
                log.debug(
                    "Converting '{fname} {lname}' -> '{fname} {minlname}".
                    format(fname=fname,
                           lname=lname,
                           minlname=lname[:required_length]))
                a.display_name = '{0} {1}'.format(fname,
                                                  lname[:required_length])
        else:
            log.debug(
                "Unable to find a minimum lastname; using full lastname.")
            # Just use full names
            for a in athletes:
                fname, lname = firstlast(a.name)
                a.display_name = '{0} {1}'.format(fname,
                                                  lname[:required_length])

    # Update the database with new values
    meta.scoped_session().commit()
示例#15
0
def _write_instagram_photo_primary(photo, ride):
    """
    Writes an instagram primary photo to db.

    :param photo: The primary photo from an activity.
    :type photo: stravalib.orm.ActivityPhotoPrimary
    :param ride: The db model object for ride.
    :type ride: bafs.orm.Ride
    :return: The newly added ride photo object.
    :rtype: bafs.orm.RidePhoto
    """
    # Here is when we have an Instagram photo as primary:
    #  u'photos': {u'count': 1,
    #   u'primary': {u'id': 106409096,
    #    u'source': 2,
    #    u'unique_id': None,
    #    u'urls': {u'100': u'https://instagram.com/p/88qaqZvrBI/media?size=t',
    #     u'600': u'https://instagram.com/p/88qaqZvrBI/media?size=l'}},
    #   u'use_prima ry_photo': False},


    media = None

    # This doesn't work any more; Instagram changed their API to use OAuth.
    #insta_client = insta.configured_instagram_client()
    #shortcode = re.search(r'/p/([^/]+)/', photo.urls['100']).group(1)
    # try:
    #     #log.debug("Fetching Instagram media for shortcode: {}".format(shortcode))
    #     media = insta_client.media_shortcode(shortcode)
    # except (InstagramAPIError, InstagramClientError) as e:
    #     if e.status_code == 400:
    #         log.warning("Instagram photo {} for ride {}; user is set to private".format(shortcode, ride))
    #     elif e.status_code == 404:
    #         log.warning("Photo {} for ride {}; shortcode not found".format(shortcode, ride))
    #     else:
    #         log.exception("Error fetching instagram photo {}".format(photo))

    p = RidePhoto()

    if media:
        p.id = media.id
        p.ref = media.link
        p.img_l = media.get_standard_resolution_url()
        p.img_t = media.get_thumbnail_url()
        if media.caption:
            p.caption = media.caption.text
    else:
        p.id = photo.id
        p.ref = re.match(r'(.+/)media\?size=.$', photo.urls['100']).group(1)
        p.img_l = photo.urls['600']
        p.img_t = photo.urls['100']

    p.ride_id = ride.id
    p.primary = True
    p.source = photo.source

    log.debug("Writing (primary) Instagram ride photo: {!r}".format(p))

    meta.scoped_session().add(p)
    meta.scoped_session().flush()

    return p
示例#16
0
def write_ride_photos_nonprimary(activity_photos, ride):
    """
    Writes out non-primary photos (currently only instagram) associated with a ride to the database.

    :param activity_photos: Photos for an activity.
    :type activity_photos: list[stravalib.orm.ActivityPhoto]

    :param ride: The db model object for ride.
    :type ride: bafs.orm.Ride
    """
    # [{u'activity_id': 414980300,
    #   u'activity_name': u'Pimmit Run CX',
    #   u'caption': u'Pimmit Run cx',
    #   u'created_at': u'2015-10-17T20:51:02Z',
    #   u'created_at_local': u'2015-10-17T16:51:02Z',
    #   u'id': 106409096,
    #   u'ref': u'https://instagram.com/p/88qaqZvrBI/',
    #   u'resource_state': 2,
    #   u'sizes': {u'0': [150, 150]},
    #   u'source': 2,
    #   u'type': u'InstagramPhoto',
    #   u'uid': u'1097938959360503880_297644011',
    #   u'unique_id': None,
    #   u'uploaded_at': u'2015-10-17T17:55:45Z',
    #   u'urls': {u'0': u'https://instagram.com/p/88qaqZvrBI/media?size=t'}}]

    meta.engine.execute(RidePhoto.__table__.delete().where(
        and_(RidePhoto.ride_id == ride.id, RidePhoto.primary == False)))

    insta_client = insta.configured_instagram_client()

    for activity_photo in activity_photos:

        # If it's already in the db, then skip it.
        existing = meta.scoped_session().query(RidePhoto).get(
            activity_photo.uid)
        if existing:
            log.info("Skipping photo {} because it's already in database: {}".
                     format(activity_photo, existing))
            continue

        try:
            media = insta_client.media(activity_photo.uid)

            photo = RidePhoto(id=activity_photo.uid,
                              ride_id=ride.id,
                              ref=activity_photo.ref,
                              caption=activity_photo.caption)

            photo.img_l = media.get_standard_resolution_url()
            photo.img_t = media.get_thumbnail_url()

            meta.scoped_session().add(photo)

            log.debug(
                "Writing (non-primary) ride photo: {p_id}: {photo!r}".format(
                    p_id=photo.id, photo=photo))

            meta.scoped_session().flush()
        except (InstagramAPIError, InstagramClientError) as e:
            if e.status_code == 400:
                log.warning(
                    "Skipping photo {0} for ride {1}; user is set to private".
                    format(activity_photo, ride))
            elif e.status_code == 404:
                log.warning(
                    "Skipping photo {0} for ride {1}; not found".format(
                        activity_photo, ride))
            else:
                log.exception(
                    "Error fetching instagram photo {0} (skipping)".format(
                        activity_photo))

    ride.photos_fetched = True
示例#17
0
def write_ride_photos_nonprimary(activity_photos, ride):
    """
    Writes out non-primary photos (currently only instagram) associated with a ride to the database.

    :param activity_photos: Photos for an activity.
    :type activity_photos: list[stravalib.orm.ActivityPhoto]

    :param ride: The db model object for ride.
    :type ride: bafs.orm.Ride
    """
    # [{u'activity_id': 414980300,
    #   u'activity_name': u'Pimmit Run CX',
    #   u'caption': u'Pimmit Run cx',
    #   u'created_at': u'2015-10-17T20:51:02Z',
    #   u'created_at_local': u'2015-10-17T16:51:02Z',
    #   u'id': 106409096,
    #   u'ref': u'https://instagram.com/p/88qaqZvrBI/',
    #   u'resource_state': 2,
    #   u'sizes': {u'0': [150, 150]},
    #   u'source': 2,
    #   u'type': u'InstagramPhoto',
    #   u'uid': u'1097938959360503880_297644011',
    #   u'unique_id': None,
    #   u'uploaded_at': u'2015-10-17T17:55:45Z',
    #   u'urls': {u'0': u'https://instagram.com/p/88qaqZvrBI/media?size=t'}}]

    meta.engine.execute(RidePhoto.__table__.delete().where(and_(RidePhoto.ride_id == ride.id,
                                                              RidePhoto.primary == False)))

    insta_client = insta.configured_instagram_client()

    for activity_photo in activity_photos:

        # If it's already in the db, then skip it.
        existing = meta.scoped_session().query(RidePhoto).get(activity_photo.uid)
        if existing:
            log.info("Skipping photo {} because it's already in database: {}".format(activity_photo, existing))
            continue

        try:
            media = insta_client.media(activity_photo.uid)

            photo = RidePhoto(id=activity_photo.uid,
                              ride_id=ride.id,
                              ref=activity_photo.ref,
                              caption=activity_photo.caption)

            photo.img_l = media.get_standard_resolution_url()
            photo.img_t = media.get_thumbnail_url()

            meta.scoped_session().add(photo)

            log.debug("Writing (non-primary) ride photo: {p_id}: {photo!r}".format(p_id=photo.id, photo=photo))

            meta.scoped_session().flush()
        except (InstagramAPIError, InstagramClientError) as e:
            if e.status_code == 400:
                log.warning("Skipping photo {0} for ride {1}; user is set to private".format(activity_photo, ride))
            elif e.status_code == 404:
                log.warning("Skipping photo {0} for ride {1}; not found".format(activity_photo, ride))
            else:
                log.exception("Error fetching instagram photo {0} (skipping)".format(activity_photo))


    ride.photos_fetched = True