API: timezone support for activity creation and edition - #11

This commit is contained in:
Sam 2018-06-11 16:44:49 +02:00
parent 31d23da473
commit 027fa8f699
6 changed files with 219 additions and 10 deletions

View File

@ -300,7 +300,7 @@ def update_activity(auth_user_id, activity_id):
try: try:
activity = Activity.query.filter_by(id=activity_id).first() activity = Activity.query.filter_by(id=activity_id).first()
if activity: if activity:
activity = edit_activity(activity, activity_data) activity = edit_activity(activity, activity_data, auth_user_id)
db.session.commit() db.session.commit()
response_object = { response_object = {
'status': 'success', 'status': 'success',

View File

@ -5,12 +5,14 @@ import zipfile
from datetime import datetime, timedelta from datetime import datetime, timedelta
import gpxpy.gpx import gpxpy.gpx
import pytz
from fittrackee_api import db from fittrackee_api import db
from flask import current_app from flask import current_app
from sqlalchemy import exc from sqlalchemy import exc
from staticmap import Line, StaticMap from staticmap import Line, StaticMap
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from ..users.models import User
from .models import Activity, ActivitySegment, Sport from .models import Activity, ActivitySegment, Sport
@ -37,8 +39,20 @@ def update_activity_data(activity, gpx_data):
def create_activity( def create_activity(
auth_user_id, activity_data, gpx_data=None auth_user_id, activity_data, gpx_data=None
): ):
user = User.query.filter_by(id=auth_user_id).first()
activity_date = gpx_data['start'] if gpx_data else datetime.strptime( activity_date = gpx_data['start'] if gpx_data else datetime.strptime(
activity_data.get('activity_date'), '%Y-%m-%d %H:%M') activity_data.get('activity_date'), '%Y-%m-%d %H:%M')
activity_date_tz = None
# activity date in gpx are directly in UTC
if user.timezone and not gpx_data:
user_tz = pytz.timezone(user.timezone)
activity_date_tz = user_tz.localize(activity_date)
if not gpx_data:
# make datetime 'naive' like in gpx file
activity_date = activity_date_tz.astimezone(pytz.utc)
activity_date = activity_date.replace(tzinfo=None)
duration = gpx_data['duration'] if gpx_data \ duration = gpx_data['duration'] if gpx_data \
else timedelta(seconds=activity_data.get('duration')) else timedelta(seconds=activity_data.get('duration'))
distance = gpx_data['distance'] if gpx_data \ distance = gpx_data['distance'] if gpx_data \
@ -58,7 +72,12 @@ def create_activity(
new_activity.title = title new_activity.title = title
else: else:
sport = Sport.query.filter_by(id=new_activity.sport_id).first() sport = Sport.query.filter_by(id=new_activity.sport_id).first()
new_activity.title = f'{sport.label} - {new_activity.activity_date}' fmt = "%Y-%m-%d %H:%M:%S"
activity_datetime = (
activity_date_tz.strftime(fmt)
if activity_date_tz
else new_activity.activity_date.strftime(fmt))
new_activity.title = f'{sport.label} - {activity_datetime}'
if gpx_data: if gpx_data:
new_activity.gpx = gpx_data['filename'] new_activity.gpx = gpx_data['filename']
@ -85,15 +104,23 @@ def create_segment(activity_id, segment_data):
return new_segment return new_segment
def edit_activity(activity, activity_data): def edit_activity(activity, activity_data, auth_user_id):
if activity_data.get('sport_id'): if activity_data.get('sport_id'):
activity.sport_id = activity_data.get('sport_id') activity.sport_id = activity_data.get('sport_id')
if activity_data.get('title'): if activity_data.get('title'):
activity.title = activity_data.get('title') activity.title = activity_data.get('title')
if not activity.gpx: if not activity.gpx:
if activity_data.get('activity_date'): if activity_data.get('activity_date'):
activity.activity_date = datetime.strptime( activity_date = datetime.strptime(
activity_data.get('activity_date'), '%Y-%m-%d %H:%M') activity_data.get('activity_date'), '%Y-%m-%d %H:%M')
user = User.query.filter_by(id=auth_user_id).first()
if user.timezone:
# make datetime 'naive' like in gpx file
user_tz = pytz.timezone(user.timezone)
activity_date_tz = user_tz.localize(activity_date)
activity_date = activity_date_tz.astimezone(pytz.utc)
activity_date = activity_date.replace(tzinfo=None)
activity.activity_date = activity_date
if activity_data.get('duration'): if activity_data.get('duration'):
activity.duration = timedelta( activity.duration = timedelta(
seconds=activity_data.get('duration')) seconds=activity_data.get('duration'))

View File

@ -569,6 +569,49 @@ def test_get_an_activity_wo_gpx(app, user_1, sport_1_cycling):
assert_activity_data_wo_gpx(data) assert_activity_data_wo_gpx(data)
def test_get_an_activity_wo_gpx_with_timezone(app, user_1, sport_1_cycling):
user_1.timezone = 'Europe/Paris'
client = app.test_client()
resp_login = client.post(
'/api/auth/login',
data=json.dumps(dict(
email='test@test.com',
password='12345678'
)),
content_type='application/json'
)
client.post(
'/api/activities/no_gpx',
content_type='application/json',
data=json.dumps(dict(
sport_id=1,
duration=3600,
activity_date='2018-05-15 14:05',
distance=10
)),
headers=dict(
Authorization='Bearer ' + json.loads(
resp_login.data.decode()
)['auth_token']
)
)
response = client.get(
'/api/activities/1',
headers=dict(
Authorization='Bearer ' + json.loads(
resp_login.data.decode()
)['auth_token']
)
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['activities']) == 1
assert data['data']['activities'][0]['activity_date'] == 'Tue, 15 May 2018 12:05:00 GMT' # noqa
assert data['data']['activities'][0]['title'] == 'Cycling - 2018-05-15 14:05:00' # noqa
def test_add_an_activity_no_gpx_invalid_payload(app, user_1, sport_1_cycling): def test_add_an_activity_no_gpx_invalid_payload(app, user_1, sport_1_cycling):
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(

View File

@ -306,7 +306,7 @@ def test_edit_an_activity_wo_gpx(
data=json.dumps(dict( data=json.dumps(dict(
sport_id=2, sport_id=2,
duration=3600, duration=3600,
activity_date='2018-05-15 14:05', activity_date='2018-05-15 15:05',
distance=8, distance=8,
title='Activity test' title='Activity test'
)), )),
@ -322,7 +322,7 @@ def test_edit_an_activity_wo_gpx(
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['activities']) == 1 assert len(data['data']['activities']) == 1
assert 'creation_date' in data['data']['activities'][0] assert 'creation_date' in data['data']['activities'][0]
assert data['data']['activities'][0]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT' # noqa assert data['data']['activities'][0]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT' # noqa
assert data['data']['activities'][0]['user_id'] == 1 assert data['data']['activities'][0]['user_id'] == 1
assert data['data']['activities'][0]['sport_id'] == 2 assert data['data']['activities'][0]['sport_id'] == 2
assert data['data']['activities'][0]['duration'] == '1:00:00' assert data['data']['activities'][0]['duration'] == '1:00:00'
@ -343,22 +343,159 @@ def test_edit_an_activity_wo_gpx(
assert records[0]['sport_id'] == 2 assert records[0]['sport_id'] == 2
assert records[0]['activity_id'] == 1 assert records[0]['activity_id'] == 1
assert records[0]['record_type'] == 'MS' assert records[0]['record_type'] == 'MS'
assert records[0]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT' assert records[0]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
assert records[0]['value'] == 8.0 assert records[0]['value'] == 8.0
assert records[1]['sport_id'] == 2 assert records[1]['sport_id'] == 2
assert records[1]['activity_id'] == 1 assert records[1]['activity_id'] == 1
assert records[1]['record_type'] == 'LD' assert records[1]['record_type'] == 'LD'
assert records[1]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT' assert records[1]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
assert records[1]['value'] == '1:00:00' assert records[1]['value'] == '1:00:00'
assert records[2]['sport_id'] == 2 assert records[2]['sport_id'] == 2
assert records[2]['activity_id'] == 1 assert records[2]['activity_id'] == 1
assert records[2]['record_type'] == 'FD' assert records[2]['record_type'] == 'FD'
assert records[2]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT' assert records[2]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
assert records[2]['value'] == 8.0 assert records[2]['value'] == 8.0
assert records[3]['sport_id'] == 2 assert records[3]['sport_id'] == 2
assert records[3]['activity_id'] == 1 assert records[3]['activity_id'] == 1
assert records[3]['record_type'] == 'AS' assert records[3]['record_type'] == 'AS'
assert records[3]['activity_date'] == 'Tue, 15 May 2018 14:05:00 GMT' assert records[3]['activity_date'] == 'Tue, 15 May 2018 15:05:00 GMT'
assert records[3]['value'] == 8.0
def test_edit_an_activity_wo_gpx_timezone(
app, user_1, sport_1_cycling, sport_2_running
):
client = app.test_client()
user_1.timezone = 'Europe/Paris'
resp_login = client.post(
'/api/auth/login',
data=json.dumps(dict(
email='test@test.com',
password='12345678'
)),
content_type='application/json'
)
response = client.post(
'/api/activities/no_gpx',
content_type='application/json',
data=json.dumps(dict(
sport_id=1,
duration=3600,
activity_date='2018-05-14 14:05',
distance=7
)),
headers=dict(
Authorization='Bearer ' + json.loads(
resp_login.data.decode()
)['auth_token']
)
)
data = json.loads(response.data.decode())
assert response.status_code == 201
assert 'created' in data['status']
assert len(data['data']['activities']) == 1
assert 'creation_date' in data['data']['activities'][0]
assert data['data']['activities'][0]['activity_date'] == 'Mon, 14 May 2018 12:05:00 GMT' # noqa
assert data['data']['activities'][0]['user_id'] == 1
assert data['data']['activities'][0]['sport_id'] == 1
assert data['data']['activities'][0]['duration'] == '1:00:00'
assert data['data']['activities'][0]['title'] == 'Cycling - 2018-05-14 14:05:00' # noqa
assert data['data']['activities'][0]['ascent'] is None
assert data['data']['activities'][0]['ave_speed'] == 7.0
assert data['data']['activities'][0]['descent'] is None
assert data['data']['activities'][0]['distance'] == 7.0
assert data['data']['activities'][0]['max_alt'] is None
assert data['data']['activities'][0]['max_speed'] == 7.0
assert data['data']['activities'][0]['min_alt'] is None
assert data['data']['activities'][0]['moving'] == '1:00:00'
assert data['data']['activities'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is False
records = data['data']['activities'][0]['records']
assert len(records) == 4
assert records[0]['sport_id'] == 1
assert records[0]['activity_id'] == 1
assert records[0]['record_type'] == 'MS'
assert records[0]['activity_date'] == 'Mon, 14 May 2018 12:05:00 GMT'
assert records[0]['value'] == 7.0
assert records[1]['sport_id'] == 1
assert records[1]['activity_id'] == 1
assert records[1]['record_type'] == 'LD'
assert records[1]['activity_date'] == 'Mon, 14 May 2018 12:05:00 GMT'
assert records[1]['value'] == '1:00:00'
assert records[2]['sport_id'] == 1
assert records[2]['activity_id'] == 1
assert records[2]['record_type'] == 'FD'
assert records[2]['activity_date'] == 'Mon, 14 May 2018 12:05:00 GMT'
assert records[2]['value'] == 7.0
assert records[3]['sport_id'] == 1
assert records[3]['activity_id'] == 1
assert records[3]['record_type'] == 'AS'
assert records[3]['activity_date'] == 'Mon, 14 May 2018 12:05:00 GMT'
assert records[3]['value'] == 7.0
response = client.patch(
'/api/activities/1',
content_type='application/json',
data=json.dumps(dict(
sport_id=2,
duration=3600,
activity_date='2018-05-15 15:05',
distance=8,
title='Activity test'
)),
headers=dict(
Authorization='Bearer ' + json.loads(
resp_login.data.decode()
)['auth_token']
)
)
data = json.loads(response.data.decode())
assert response.status_code == 200
assert 'success' in data['status']
assert len(data['data']['activities']) == 1
assert 'creation_date' in data['data']['activities'][0]
assert data['data']['activities'][0]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT' # noqa
assert data['data']['activities'][0]['user_id'] == 1
assert data['data']['activities'][0]['sport_id'] == 2
assert data['data']['activities'][0]['duration'] == '1:00:00'
assert data['data']['activities'][0]['title'] == 'Activity test' # noqa
assert data['data']['activities'][0]['ascent'] is None
assert data['data']['activities'][0]['ave_speed'] == 8.0
assert data['data']['activities'][0]['descent'] is None
assert data['data']['activities'][0]['distance'] == 8.0
assert data['data']['activities'][0]['max_alt'] is None
assert data['data']['activities'][0]['max_speed'] == 8.0
assert data['data']['activities'][0]['min_alt'] is None
assert data['data']['activities'][0]['moving'] == '1:00:00'
assert data['data']['activities'][0]['pauses'] is None
assert data['data']['activities'][0]['with_gpx'] is False
records = data['data']['activities'][0]['records']
assert len(records) == 4
assert records[0]['sport_id'] == 2
assert records[0]['activity_id'] == 1
assert records[0]['record_type'] == 'MS'
assert records[0]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
assert records[0]['value'] == 8.0
assert records[1]['sport_id'] == 2
assert records[1]['activity_id'] == 1
assert records[1]['record_type'] == 'LD'
assert records[1]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
assert records[1]['value'] == '1:00:00'
assert records[2]['sport_id'] == 2
assert records[2]['activity_id'] == 1
assert records[2]['record_type'] == 'FD'
assert records[2]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
assert records[2]['value'] == 8.0
assert records[3]['sport_id'] == 2
assert records[3]['activity_id'] == 1
assert records[3]['record_type'] == 'AS'
assert records[3]['activity_date'] == 'Tue, 15 May 2018 13:05:00 GMT'
assert records[3]['value'] == 8.0 assert records[3]['value'] == 8.0

View File

@ -59,6 +59,7 @@ def register_user():
email=email, email=email,
password=password password=password
) )
new_user.timezone = 'Europe/Paris'
db.session.add(new_user) db.session.add(new_user)
db.session.commit() db.session.commit()
# generate auth token # generate auth token

View File

@ -43,6 +43,7 @@ pytest-isort==0.1.0
pytest-runner==3.0 pytest-runner==3.0
python-dateutil==2.7.2 python-dateutil==2.7.2
python-editor==1.0.3 python-editor==1.0.3
pytz==2018.4
requests==2.18.4 requests==2.18.4
six==1.11.0 six==1.11.0
SQLAlchemy==1.2.7 SQLAlchemy==1.2.7