API: timezone support for activity creation and edition - #11
This commit is contained in:
parent
31d23da473
commit
027fa8f699
@ -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',
|
||||||
|
@ -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'))
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user