API & Client: add weather to Activities - fix #8
This commit is contained in:
@ -6,7 +6,7 @@ from sqlalchemy.dialects import postgresql
|
||||
from sqlalchemy.event import listens_for
|
||||
from sqlalchemy.ext.hybrid import hybrid_property
|
||||
from sqlalchemy.orm.session import object_session
|
||||
from sqlalchemy.types import Enum
|
||||
from sqlalchemy.types import JSON, Enum
|
||||
|
||||
from .utils_format import convert_in_duration, convert_value_to_integer
|
||||
|
||||
@ -121,6 +121,8 @@ class Activity(db.Model):
|
||||
bounds = db.Column(postgresql.ARRAY(db.Float), nullable=True)
|
||||
map = db.Column(db.String(255), nullable=True)
|
||||
map_id = db.Column(db.String(50), nullable=True)
|
||||
weather_start = db.Column(JSON, nullable=True)
|
||||
weather_end = db.Column(JSON, nullable=True)
|
||||
segments = db.relationship('ActivitySegment',
|
||||
lazy=True,
|
||||
cascade='all, delete',
|
||||
@ -234,7 +236,9 @@ class Activity(db.Model):
|
||||
"next_activity": next_activity.id if next_activity else None,
|
||||
"segments": [segment.serialize() for segment in self.segments],
|
||||
"records": [record.serialize() for record in self.records],
|
||||
"map": self.map_id if self.map else None
|
||||
"map": self.map_id if self.map else None,
|
||||
"weather_start": self.weather_start,
|
||||
"weather_end": self.weather_end
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -14,6 +14,7 @@ from werkzeug.utils import secure_filename
|
||||
|
||||
from ..users.models import User
|
||||
from .models import Activity, ActivitySegment, Sport
|
||||
from .utils_weather import get_weather
|
||||
|
||||
|
||||
class ActivityException(Exception):
|
||||
@ -183,12 +184,17 @@ def get_gpx_info(gpx_file):
|
||||
max_speed = 0
|
||||
start = 0
|
||||
map_data = []
|
||||
weather_data = []
|
||||
|
||||
for segment_idx, segment in enumerate(gpx.tracks[0].segments):
|
||||
segment_start = 0
|
||||
for point_idx, point in enumerate(segment.points):
|
||||
if point_idx == 0 and start == 0:
|
||||
start = point.time
|
||||
weather_data.append(get_weather(point))
|
||||
if (point_idx == (len(segment.points) - 1) and
|
||||
segment_idx == (len(gpx.tracks[0].segments) - 1)):
|
||||
weather_data.append(get_weather(point))
|
||||
map_data.append([
|
||||
point.longitude, point.latitude
|
||||
])
|
||||
@ -215,7 +221,7 @@ def get_gpx_info(gpx_file):
|
||||
bounds.max_longitude
|
||||
]
|
||||
|
||||
return gpx_data, map_data
|
||||
return gpx_data, map_data, weather_data
|
||||
|
||||
|
||||
def get_chart_data(gpx_file):
|
||||
@ -305,7 +311,7 @@ def get_map_hash(map_filepath):
|
||||
|
||||
def process_one_gpx_file(params, filename):
|
||||
try:
|
||||
gpx_data, map_data = get_gpx_info(params['file_path'])
|
||||
gpx_data, map_data, weather_data = get_gpx_info(params['file_path'])
|
||||
auth_user_id = params['user'].id
|
||||
new_filepath = get_new_file_path(
|
||||
auth_user_id=auth_user_id,
|
||||
@ -333,6 +339,8 @@ def process_one_gpx_file(params, filename):
|
||||
params['user'], params['activity_data'], gpx_data)
|
||||
new_activity.map = map_filepath
|
||||
new_activity.map_id = get_map_hash(map_filepath)
|
||||
new_activity.weather_start = weather_data[0]
|
||||
new_activity.weather_end = weather_data[1]
|
||||
db.session.add(new_activity)
|
||||
db.session.flush()
|
||||
|
||||
|
32
fittrackee_api/fittrackee_api/activities/utils_weather.py
Normal file
32
fittrackee_api/fittrackee_api/activities/utils_weather.py
Normal file
@ -0,0 +1,32 @@
|
||||
import os
|
||||
|
||||
import forecastio
|
||||
import pytz
|
||||
from fittrackee_api import appLog
|
||||
|
||||
API_KEY = os.getenv('WEATHER_API')
|
||||
|
||||
|
||||
def get_weather(point):
|
||||
if not API_KEY or API_KEY == '':
|
||||
return None
|
||||
try:
|
||||
point_time = pytz.utc.localize(point.time)
|
||||
forecast = forecastio.load_forecast(
|
||||
API_KEY,
|
||||
point.latitude,
|
||||
point.longitude,
|
||||
time=point_time,
|
||||
units='si'
|
||||
)
|
||||
weather = forecast.currently()
|
||||
return {
|
||||
'summary': weather.summary,
|
||||
'icon': weather.icon,
|
||||
'temperature': weather.temperature,
|
||||
'humidity': weather.humidity,
|
||||
'wind': weather.windSpeed,
|
||||
}
|
||||
except Exception as e:
|
||||
appLog.error(e)
|
||||
return None
|
@ -14,6 +14,33 @@ def test_add_activity(
|
||||
assert 'Test' == activity_cycling_user_1.title
|
||||
assert '<Activity \'Cycling\' - 2018-01-01 00:00:00>' == str(activity_cycling_user_1) # noqa
|
||||
|
||||
serialized_activity = activity_cycling_user_1.serialize()
|
||||
assert 1 == serialized_activity['id']
|
||||
assert 1 == serialized_activity['user_id']
|
||||
assert 1 == serialized_activity['sport_id']
|
||||
assert serialized_activity['title'] == 'Test'
|
||||
assert 'creation_date' in serialized_activity
|
||||
assert serialized_activity['modification_date'] is not None
|
||||
assert str(serialized_activity['activity_date']) == '2018-01-01 00:00:00'
|
||||
assert serialized_activity['duration'] == '0:17:04'
|
||||
assert serialized_activity['pauses'] is None
|
||||
assert serialized_activity['moving'] == '0:17:04'
|
||||
assert serialized_activity['distance'] == 10.0
|
||||
assert serialized_activity['max_alt'] is None
|
||||
assert serialized_activity['descent'] is None
|
||||
assert serialized_activity['ascent'] is None
|
||||
assert serialized_activity['max_speed'] == 10.0
|
||||
assert serialized_activity['ave_speed'] == 10.0
|
||||
assert serialized_activity['with_gpx'] is False
|
||||
assert serialized_activity['bounds'] == []
|
||||
assert serialized_activity['previous_activity'] is None
|
||||
assert serialized_activity['next_activity'] is None
|
||||
assert serialized_activity['segments'] == []
|
||||
assert serialized_activity['records'] != []
|
||||
assert serialized_activity['map'] is None
|
||||
assert serialized_activity['weather_start'] is None
|
||||
assert serialized_activity['weather_end'] is None
|
||||
|
||||
|
||||
def test_add_segment(
|
||||
app, sport_1_cycling, user_1, activity_cycling_user_1,
|
||||
|
30
fittrackee_api/migrations/versions/71093ac9ca44_.py
Normal file
30
fittrackee_api/migrations/versions/71093ac9ca44_.py
Normal file
@ -0,0 +1,30 @@
|
||||
"""add weather infos in 'Activity' table
|
||||
|
||||
Revision ID: 71093ac9ca44
|
||||
Revises: e82e5e9447de
|
||||
Create Date: 2018-06-13 15:29:11.715377
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '71093ac9ca44'
|
||||
down_revision = 'e82e5e9447de'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('activities', sa.Column('weather_end', sa.JSON(), nullable=True))
|
||||
op.add_column('activities', sa.Column('weather_start', sa.JSON(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('activities', 'weather_start')
|
||||
op.drop_column('activities', 'weather_end')
|
||||
# ### end Alembic commands ###
|
@ -8,6 +8,7 @@ cffi==1.11.5
|
||||
chardet==3.0.4
|
||||
click==6.7
|
||||
codacy-coverage==1.3.11
|
||||
cookies==2.2.1
|
||||
coverage==4.5.1
|
||||
execnet==1.5.0
|
||||
flake8==3.5.0
|
||||
@ -43,8 +44,10 @@ pytest-isort==0.2.0
|
||||
pytest-runner==4.2
|
||||
python-dateutil==2.7.3
|
||||
python-editor==1.0.3
|
||||
python-forecastio==1.4.0
|
||||
pytz==2018.4
|
||||
requests==2.18.4
|
||||
responses==0.9.0
|
||||
six==1.11.0
|
||||
SQLAlchemy==1.2.8
|
||||
staticmap==0.5.3
|
||||
|
Reference in New Issue
Block a user