API: Activity segments table init
This commit is contained in:
parent
6d4574332b
commit
e4a65f4c79
@ -23,10 +23,9 @@ def upgrade():
|
|||||||
sa.Column('user_id', sa.Integer(), nullable=False),
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
sa.Column('sport_id', sa.Integer(), nullable=False),
|
sa.Column('sport_id', sa.Integer(), nullable=False),
|
||||||
sa.Column('activity_id', sa.Integer(), nullable=False),
|
sa.Column('activity_id', sa.Integer(), nullable=False),
|
||||||
sa.Column('record_type', sa.Enum('FD', 'LD', 'MS', name='record_types'), nullable=True),
|
sa.Column('record_type', sa.Enum('AS', 'FD', 'LD', 'MS', name='record_types'), nullable=True),
|
||||||
sa.Column('activity_date', sa.DateTime(), nullable=False),
|
sa.Column('activity_date', sa.DateTime(), nullable=False),
|
||||||
sa.Column('value_interval', sa.Interval(), nullable=True),
|
sa.Column('value', sa.Integer(), nullable=True),
|
||||||
sa.Column('value_float', sa.Numeric(precision=5, scale=3), nullable=True),
|
|
||||||
sa.ForeignKeyConstraint(['activity_id'], ['activities.id'], ),
|
sa.ForeignKeyConstraint(['activity_id'], ['activities.id'], ),
|
||||||
sa.ForeignKeyConstraint(['sport_id'], ['sports.id'], ),
|
sa.ForeignKeyConstraint(['sport_id'], ['sports.id'], ),
|
||||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||||
|
43
mpwo_api/migrations/versions/dd73d23a7a3d_.py
Normal file
43
mpwo_api/migrations/versions/dd73d23a7a3d_.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
"""create Activities Segments table
|
||||||
|
|
||||||
|
Revision ID: dd73d23a7a3d
|
||||||
|
Revises: caf0e0dc621a
|
||||||
|
Create Date: 2018-05-14 12:12:57.299200
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'dd73d23a7a3d'
|
||||||
|
down_revision = 'caf0e0dc621a'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('activity_segments',
|
||||||
|
sa.Column('activity_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('segment_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('duration', sa.Interval(), nullable=False),
|
||||||
|
sa.Column('pauses', sa.Interval(), nullable=True),
|
||||||
|
sa.Column('moving', sa.Interval(), nullable=True),
|
||||||
|
sa.Column('distance', sa.Numeric(precision=5, scale=3), nullable=True),
|
||||||
|
sa.Column('min_alt', sa.Numeric(precision=5, scale=2), nullable=True),
|
||||||
|
sa.Column('max_alt', sa.Numeric(precision=5, scale=2), nullable=True),
|
||||||
|
sa.Column('descent', sa.Numeric(precision=5, scale=2), nullable=True),
|
||||||
|
sa.Column('ascent', sa.Numeric(precision=5, scale=2), nullable=True),
|
||||||
|
sa.Column('max_speed', sa.Numeric(precision=5, scale=2), nullable=True),
|
||||||
|
sa.Column('ave_speed', sa.Numeric(precision=5, scale=2), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['activity_id'], ['activities.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('activity_id', 'segment_id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('activity_segments')
|
||||||
|
# ### end Alembic commands ###
|
@ -8,8 +8,8 @@ from sqlalchemy import exc
|
|||||||
from ..users.utils import authenticate, verify_extension
|
from ..users.utils import authenticate, verify_extension
|
||||||
from .models import Activity, Sport
|
from .models import Activity, Sport
|
||||||
from .utils import (
|
from .utils import (
|
||||||
create_activity, edit_activity, get_file_path, get_gpx_info,
|
create_activity, create_segment, edit_activity, get_file_path,
|
||||||
get_new_file_path
|
get_gpx_info, get_new_file_path
|
||||||
)
|
)
|
||||||
|
|
||||||
activities_blueprint = Blueprint('activities', __name__)
|
activities_blueprint = Blueprint('activities', __name__)
|
||||||
@ -170,6 +170,10 @@ def post_activity(auth_user_id):
|
|||||||
new_activity = create_activity(
|
new_activity = create_activity(
|
||||||
auth_user_id, activity_data, gpx_data)
|
auth_user_id, activity_data, gpx_data)
|
||||||
db.session.add(new_activity)
|
db.session.add(new_activity)
|
||||||
|
db.session.flush()
|
||||||
|
for segment_data in gpx_data['segments']:
|
||||||
|
new_segment = create_segment(new_activity.id, segment_data)
|
||||||
|
db.session.add(new_segment)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
response_object = {
|
response_object = {
|
||||||
'status': 'created',
|
'status': 'created',
|
||||||
@ -178,7 +182,6 @@ def post_activity(auth_user_id):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jsonify(response_object), 201
|
return jsonify(response_object), 201
|
||||||
|
|
||||||
except (exc.IntegrityError, ValueError) as e:
|
except (exc.IntegrityError, ValueError) as e:
|
||||||
db.session.rollback()
|
db.session.rollback()
|
||||||
appLog.error(e)
|
appLog.error(e)
|
||||||
|
@ -68,6 +68,13 @@ class Activity(db.Model):
|
|||||||
ascent = db.Column(db.Numeric(5, 2), nullable=True) # meters
|
ascent = db.Column(db.Numeric(5, 2), nullable=True) # meters
|
||||||
max_speed = db.Column(db.Numeric(5, 2), nullable=True) # km/h
|
max_speed = db.Column(db.Numeric(5, 2), nullable=True) # km/h
|
||||||
ave_speed = db.Column(db.Numeric(5, 2), nullable=True) # km/h
|
ave_speed = db.Column(db.Numeric(5, 2), nullable=True) # km/h
|
||||||
|
segments = db.relationship('ActivitySegment',
|
||||||
|
lazy=True,
|
||||||
|
cascade='all, delete',
|
||||||
|
backref=db.backref(
|
||||||
|
'activities',
|
||||||
|
lazy='joined',
|
||||||
|
single_parent=True))
|
||||||
records = db.relationship('Record',
|
records = db.relationship('Record',
|
||||||
lazy=True,
|
lazy=True,
|
||||||
backref=db.backref('activities', lazy='joined'))
|
backref=db.backref('activities', lazy='joined'))
|
||||||
@ -102,7 +109,53 @@ class Activity(db.Model):
|
|||||||
"ascent": float(self.ascent) if self.ascent else None,
|
"ascent": float(self.ascent) if self.ascent else None,
|
||||||
"max_speed": float(self.max_speed) if self.max_speed else None,
|
"max_speed": float(self.max_speed) if self.max_speed else None,
|
||||||
"ave_speed": float(self.ave_speed) if self.ave_speed else None,
|
"ave_speed": float(self.ave_speed) if self.ave_speed else None,
|
||||||
"with_gpx": self.gpx is not None
|
"with_gpx": self.gpx is not None,
|
||||||
|
"segments": [segment.serialize() for segment in self.segments]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ActivitySegment(db.Model):
|
||||||
|
__tablename__ = "activity_segments"
|
||||||
|
activity_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
db.ForeignKey('activities.id'),
|
||||||
|
primary_key=True)
|
||||||
|
segment_id = db.Column(
|
||||||
|
db.Integer,
|
||||||
|
primary_key=True)
|
||||||
|
duration = db.Column(db.Interval, nullable=False)
|
||||||
|
pauses = db.Column(db.Interval, nullable=True)
|
||||||
|
moving = db.Column(db.Interval, nullable=True)
|
||||||
|
distance = db.Column(db.Numeric(5, 3), nullable=True) # kilometers
|
||||||
|
min_alt = db.Column(db.Numeric(5, 2), nullable=True) # meters
|
||||||
|
max_alt = db.Column(db.Numeric(5, 2), nullable=True) # meters
|
||||||
|
descent = db.Column(db.Numeric(5, 2), nullable=True) # meters
|
||||||
|
ascent = db.Column(db.Numeric(5, 2), nullable=True) # meters
|
||||||
|
max_speed = db.Column(db.Numeric(5, 2), nullable=True) # km/h
|
||||||
|
ave_speed = db.Column(db.Numeric(5, 2), nullable=True) # km/h
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '<Segment \'{}\' for activity \'{}\'>'.format(
|
||||||
|
self.segment_id, self.activity_id, )
|
||||||
|
|
||||||
|
def __init__(self, segment_id, activity_id):
|
||||||
|
self.segment_id = segment_id
|
||||||
|
self.activity_id = activity_id
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
return {
|
||||||
|
"activity_id": self.activity_id,
|
||||||
|
"segment_id": self.segment_id,
|
||||||
|
"duration": str(self.duration) if self.duration else None,
|
||||||
|
"pauses": str(self.pauses) if self.pauses else None,
|
||||||
|
"moving": str(self.moving) if self.moving else None,
|
||||||
|
"distance": float(self.distance) if self.distance else None,
|
||||||
|
"min_alt": float(self.min_alt) if self.min_alt else None,
|
||||||
|
"max_alt": float(self.max_alt) if self.max_alt else None,
|
||||||
|
"descent": float(self.descent) if self.descent else None,
|
||||||
|
"ascent": float(self.ascent) if self.ascent else None,
|
||||||
|
"max_speed": float(self.max_speed) if self.max_speed else None,
|
||||||
|
"ave_speed": float(self.ave_speed) if self.ave_speed else None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,20 @@ from flask import current_app
|
|||||||
from mpwo_api import appLog
|
from mpwo_api import appLog
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
from .models import Activity, Sport
|
from .models import Activity, ActivitySegment, Sport
|
||||||
|
|
||||||
|
|
||||||
|
def update_activity_data(activity, gpx_data):
|
||||||
|
"""activity could be a complete activity or an activity segment"""
|
||||||
|
activity.pauses = gpx_data['stop_time']
|
||||||
|
activity.moving = gpx_data['moving_time']
|
||||||
|
activity.min_alt = gpx_data['elevation_min']
|
||||||
|
activity.max_alt = gpx_data['elevation_max']
|
||||||
|
activity.descent = gpx_data['downhill']
|
||||||
|
activity.ascent = gpx_data['uphill']
|
||||||
|
activity.max_speed = gpx_data['max_speed']
|
||||||
|
activity.ave_speed = gpx_data['average_speed']
|
||||||
|
return activity
|
||||||
|
|
||||||
|
|
||||||
def create_activity(
|
def create_activity(
|
||||||
@ -39,14 +52,7 @@ def create_activity(
|
|||||||
|
|
||||||
if gpx_data:
|
if gpx_data:
|
||||||
new_activity.gpx = gpx_data['filename']
|
new_activity.gpx = gpx_data['filename']
|
||||||
new_activity.pauses = gpx_data['stop_time']
|
update_activity_data(new_activity, gpx_data)
|
||||||
new_activity.moving = gpx_data['moving_time']
|
|
||||||
new_activity.min_alt = gpx_data['elevation_min']
|
|
||||||
new_activity.max_alt = gpx_data['elevation_max']
|
|
||||||
new_activity.descent = gpx_data['downhill']
|
|
||||||
new_activity.ascent = gpx_data['uphill']
|
|
||||||
new_activity.max_speed = gpx_data['max_speed']
|
|
||||||
new_activity.ave_speed = gpx_data['average_speed']
|
|
||||||
else:
|
else:
|
||||||
new_activity.moving = duration
|
new_activity.moving = duration
|
||||||
new_activity.ave_speed = new_activity.distance / (
|
new_activity.ave_speed = new_activity.distance / (
|
||||||
@ -55,6 +61,17 @@ def create_activity(
|
|||||||
return new_activity
|
return new_activity
|
||||||
|
|
||||||
|
|
||||||
|
def create_segment(activity_id, segment_data):
|
||||||
|
new_segment = ActivitySegment(
|
||||||
|
activity_id=activity_id,
|
||||||
|
segment_id=segment_data['idx']
|
||||||
|
)
|
||||||
|
new_segment.duration = segment_data['duration']
|
||||||
|
new_segment.distance = segment_data['distance']
|
||||||
|
update_activity_data(new_segment, segment_data)
|
||||||
|
return new_segment
|
||||||
|
|
||||||
|
|
||||||
def edit_activity(activity, activity_data):
|
def edit_activity(activity, activity_data):
|
||||||
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'):
|
||||||
|
@ -3,7 +3,7 @@ import os
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from mpwo_api import create_app, db
|
from mpwo_api import create_app, db
|
||||||
from mpwo_api.activities.models import Activity, Record, Sport
|
from mpwo_api.activities.models import Activity, ActivitySegment, Record, Sport
|
||||||
from mpwo_api.users.models import User
|
from mpwo_api.users.models import User
|
||||||
|
|
||||||
os.environ["FLASK_ENV"] = 'testing'
|
os.environ["FLASK_ENV"] = 'testing'
|
||||||
@ -93,6 +93,19 @@ def activity_cycling_user_1():
|
|||||||
return activity
|
return activity
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def activity_cycling_user_1_segment():
|
||||||
|
activity_segment = ActivitySegment(
|
||||||
|
activity_id=1,
|
||||||
|
segment_id=0
|
||||||
|
)
|
||||||
|
activity_segment.duration = datetime.timedelta(seconds=6000)
|
||||||
|
activity_segment.distance = 5
|
||||||
|
db.session.add(activity_segment)
|
||||||
|
db.session.commit()
|
||||||
|
return activity_segment
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def activity_running_user_1():
|
def activity_running_user_1():
|
||||||
activity = Activity(
|
activity = Activity(
|
||||||
|
@ -13,3 +13,10 @@ def test_add_activity(
|
|||||||
assert '0:17:04' == str(activity_cycling_user_1.duration)
|
assert '0:17:04' == str(activity_cycling_user_1.duration)
|
||||||
assert 'Test' == activity_cycling_user_1.title
|
assert 'Test' == activity_cycling_user_1.title
|
||||||
assert '<Activity \'Cycling\' - 2018-01-01 00:00:00>' == str(activity_cycling_user_1) # noqa
|
assert '<Activity \'Cycling\' - 2018-01-01 00:00:00>' == str(activity_cycling_user_1) # noqa
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_segment(
|
||||||
|
app, sport_1_cycling, user_1, activity_cycling_user_1,
|
||||||
|
activity_cycling_user_1_segment
|
||||||
|
):
|
||||||
|
assert '<Segment \'0\' for activity \'1\'>' == str(activity_cycling_user_1_segment) # noqa
|
||||||
|
Loading…
Reference in New Issue
Block a user