Exemple #1
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.model.Activity` that is associated with this effort.
    :type strava_activity: :class:`stravalib.model.Activity`

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

    try:
        # Start by removing any existing segments for the ride.
        db.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))

            db.session.add(effort)

        ride.efforts_fetched = True

    except:
        log.exception("Error adding effort for ride: {0}".format(ride))
        raise
Exemple #2
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.model.Activity` object.
    :type strava_activity: :class:`stravalib.model.Activity`

    :param ride: The db model object for ride.
    :type ride: bafs.model.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.
    db.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)
Exemple #3
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.model.ActivityPhotoPrimary
    :param ride: The db model object for ride.
    :type ride: bafs.model.Ride
    :return: The newly added ride photo object.
    :rtype: bafs.model.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},

    insta_client = insta.configured_instagram_client()
    shortcode = re.search(r'/p/([^/]+)/', photo.urls['100']).group(1)

    media = None
    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))

    db.session.add(p)
    db.session.flush()

    return p
Exemple #4
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.model.Activity` object.
    :type strava_activity: :class:`stravalib.model.Activity`

    :param ride: The db model object for ride.
    :type ride: bafs.model.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.
    db.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)
Exemple #5
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.model.Activity` that is associated with this effort.
    :type strava_activity: :class:`stravalib.model.Activity`

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

    try:
        # Start by removing any existing segments for the ride.
        db.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))

            db.session.add(effort)

        ride.efforts_fetched = True

    except:
        log.exception("Error adding effort for ride: {0}".format(ride))
        raise
Exemple #6
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 = db.session.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)
Exemple #7
0
def disambiguate_athlete_display_names():
    q = db.session.query(model.Athlete)
    q = q.filter(model.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
    db.session.commit()
Exemple #8
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.model.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')))
Exemple #9
0
def disambiguate_athlete_display_names():
    q = db.session.query(model.Athlete)
    q = q.filter(model.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
    db.session.commit()
Exemple #10
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.model.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')))
Exemple #11
0
def register_athlete_team(strava_athlete, athlete_model):
    """
    Updates db with configured team that matches the athlete's teams.

    Updates the passed-in Athlete model object with created/updated team.

    :param strava_athlete: The Strava model object for the athlete.
    :type strava_athlete: :class:`stravalib.model.Athlete`

    :param athlete_model: The athlete model object.
    :type athlete_model: :class:`bafs.model.Athlete`

    :return: The :class:`bafs.model.Team` object will be returned which matches
             configured teams.
    :rtype: :class:`bafs.model.Team`

    :raise MultipleTeamsError: If this athlete is registered for multiple of
                               the configured teams.  That won't work.
    :raise NoTeamsError: If no teams match.
    """
    assert isinstance(strava_athlete, strava_model.Athlete)
    assert isinstance(athlete_model, Athlete)

    all_teams =  app.config['BAFS_TEAMS']
    log.info("Checking {0!r} against {1!r}".format(strava_athlete.clubs, all_teams))
    try:
        matches = [c for c in strava_athlete.clubs if c.id in all_teams]
        log.debug("Matched: {0!r}".format(matches))
        athlete_model.team = None
        if len(matches) > 1:
            log.info("Multiple teams matcheed.")
            raise MultipleTeamsError(matches)
        elif len(matches) == 0:
            raise NoTeamsError()
        else:
            club = matches[0]
            # create the team row if it does not exist
            team = db.session.query(Team).get(club.id)
            if team is None:
                team = Team()
            team.id = club.id
            team.name = club.name
            team.leaderboard_exclude = club.id in app.config['BAFS_OBSERVER_TEAMS']
            athlete_model.team = team
            db.session.add(team)
            return team
    finally:
        db.session.commit()
Exemple #12
0
def register_athlete_team(strava_athlete, athlete_model):
    """
    Updates db with configured team that matches the athlete's teams.

    Updates the passed-in Athlete model object with created/updated team.

    :param strava_athlete: The Strava model object for the athlete.
    :type strava_athlete: :class:`stravalib.model.Athlete`

    :param athlete_model: The athlete model object.
    :type athlete_model: :class:`bafs.model.Athlete`

    :return: The :class:`bafs.model.Team` object will be returned which matches
             configured teams.
    :rtype: :class:`bafs.model.Team`

    :raise MultipleTeamsError: If this athlete is registered for multiple of
                               the configured teams.  That won't work.
    :raise NoTeamsError: If no teams match.
    """
    assert isinstance(strava_athlete, strava_model.Athlete)
    assert isinstance(athlete_model, Athlete)

    all_teams =  app.config['BAFS_TEAMS']
    log.info("Checking {0!r} against {1!r}".format(strava_athlete.clubs, all_teams))
    try:
        matches = [c for c in strava_athlete.clubs if c.id in all_teams]
        log.debug("Matched: {0!r}".format(matches))
        athlete_model.team = None
        if len(matches) > 1:
            log.info("Multiple teams matcheed.")
            raise MultipleTeamsError(matches)
        elif len(matches) == 0:
            raise NoTeamsError()
        else:
            club = matches[0]
            # create the team row if it does not exist
            team = db.session.query(Team).get(club.id)
            if team is None:
                team = Team()
            team.id = club.id
            team.name = club.name
            team.leaderboard_exclude = club.id in app.config['BAFS_OBSERVER_TEAMS']
            athlete_model.team = team
            db.session.add(team)
            return team
    finally:
        db.session.commit()
Exemple #13
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.model.ActivityPhotoPrimary
    :param ride: The db model object for ride.
    :type ride: bafs.model.Ride
    :return: The newly added ride photo object.
    :rtype: bafs.model.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))

    db.session.add(p)
    db.session.flush()
    return p
Exemple #14
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.model.ActivityPhotoPrimary
    :param ride: The db model object for ride.
    :type ride: bafs.model.Ride
    :return: The newly added ride photo object.
    :rtype: bafs.model.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))

    db.session.add(p)
    db.session.flush()
    return p
Exemple #15
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.model.ActivityPhoto]

    :param ride: The db model object for ride.
    :type ride: bafs.model.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'}}]

    db.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 = db.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()

            db.session.add(photo)

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

            db.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
Exemple #16
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.model.ActivityPhotoPrimary
    :param ride: The db model object for ride.
    :type ride: bafs.model.Ride
    :return: The newly added ride photo object.
    :rtype: bafs.model.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))

    db.session.add(p)
    db.session.flush()

    return p
Exemple #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.model.ActivityPhoto]

    :param ride: The db model object for ride.
    :type ride: bafs.model.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'}}]

    db.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 = db.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()

            db.session.add(photo)

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

            db.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