API: Activities records init
+ minor db change for Activity
This commit is contained in:
		@@ -1,4 +1,4 @@
 | 
				
			|||||||
"""empty message
 | 
					"""create User table
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Revision ID: 9741fc7834da
 | 
					Revision ID: 9741fc7834da
 | 
				
			||||||
Revises:
 | 
					Revises:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
"""empty message
 | 
					"""create Activity & Sport tables
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Revision ID: b7cfe0c17708
 | 
					Revision ID: b7cfe0c17708
 | 
				
			||||||
Revises: 9741fc7834da
 | 
					Revises: 9741fc7834da
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										42
									
								
								mpwo_api/migrations/versions/caf0e0dc621a_.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								mpwo_api/migrations/versions/caf0e0dc621a_.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					"""create Record table
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Revision ID: caf0e0dc621a
 | 
				
			||||||
 | 
					Revises: b7cfe0c17708
 | 
				
			||||||
 | 
					Create Date: 2018-05-11 22:33:08.267488
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from alembic import op
 | 
				
			||||||
 | 
					import sqlalchemy as sa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# revision identifiers, used by Alembic.
 | 
				
			||||||
 | 
					revision = 'caf0e0dc621a'
 | 
				
			||||||
 | 
					down_revision = 'b7cfe0c17708'
 | 
				
			||||||
 | 
					branch_labels = None
 | 
				
			||||||
 | 
					depends_on = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def upgrade():
 | 
				
			||||||
 | 
					    # ### commands auto generated by Alembic - please adjust! ###
 | 
				
			||||||
 | 
					    op.create_table('records',
 | 
				
			||||||
 | 
					    sa.Column('id', sa.Integer(), nullable=False),
 | 
				
			||||||
 | 
					    sa.Column('user_id', sa.Integer(), nullable=False),
 | 
				
			||||||
 | 
					    sa.Column('sport_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('activity_date', sa.DateTime(), nullable=False),
 | 
				
			||||||
 | 
					    sa.Column('value_interval', sa.Interval(), nullable=True),
 | 
				
			||||||
 | 
					    sa.Column('value_float', sa.Numeric(precision=5, scale=3), nullable=True),
 | 
				
			||||||
 | 
					    sa.ForeignKeyConstraint(['activity_id'], ['activities.id'], ),
 | 
				
			||||||
 | 
					    sa.ForeignKeyConstraint(['sport_id'], ['sports.id'], ),
 | 
				
			||||||
 | 
					    sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
 | 
				
			||||||
 | 
					    sa.PrimaryKeyConstraint('id'),
 | 
				
			||||||
 | 
					    sa.UniqueConstraint('user_id', 'sport_id', 'record_type', name='user_sports_records')
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    # ### end Alembic commands ###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def downgrade():
 | 
				
			||||||
 | 
					    # ### commands auto generated by Alembic - please adjust! ###
 | 
				
			||||||
 | 
					    op.drop_table('records')
 | 
				
			||||||
 | 
					    # ### end Alembic commands ###
 | 
				
			||||||
@@ -29,11 +29,13 @@ def create_app():
 | 
				
			|||||||
    from .users.auth import auth_blueprint  # noqa
 | 
					    from .users.auth import auth_blueprint  # noqa
 | 
				
			||||||
    from .users.users import users_blueprint  # noqa
 | 
					    from .users.users import users_blueprint  # noqa
 | 
				
			||||||
    from .activities.activities import activities_blueprint  # noqa
 | 
					    from .activities.activities import activities_blueprint  # noqa
 | 
				
			||||||
 | 
					    from .activities.records import records_blueprint  # noqa
 | 
				
			||||||
    from .activities.sports import sports_blueprint  # noqa
 | 
					    from .activities.sports import sports_blueprint  # noqa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    app.register_blueprint(users_blueprint, url_prefix='/api')
 | 
					    app.register_blueprint(users_blueprint, url_prefix='/api')
 | 
				
			||||||
    app.register_blueprint(auth_blueprint, url_prefix='/api')
 | 
					    app.register_blueprint(auth_blueprint, url_prefix='/api')
 | 
				
			||||||
    app.register_blueprint(activities_blueprint, url_prefix='/api')
 | 
					    app.register_blueprint(activities_blueprint, url_prefix='/api')
 | 
				
			||||||
 | 
					    app.register_blueprint(records_blueprint, url_prefix='/api')
 | 
				
			||||||
    app.register_blueprint(sports_blueprint, url_prefix='/api')
 | 
					    app.register_blueprint(sports_blueprint, url_prefix='/api')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if app.debug:
 | 
					    if app.debug:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,14 @@
 | 
				
			|||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from mpwo_api import db
 | 
					from mpwo_api import db
 | 
				
			||||||
 | 
					from sqlalchemy.types import Enum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					record_types = [
 | 
				
			||||||
 | 
					    'AS',  # 'Best Average Speed'
 | 
				
			||||||
 | 
					    'FD',  # 'Farthest Distance'
 | 
				
			||||||
 | 
					    'LD',  # 'Longest Duration'
 | 
				
			||||||
 | 
					    'MS',  # 'Max speed'
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Sport(db.Model):
 | 
					class Sport(db.Model):
 | 
				
			||||||
@@ -10,6 +18,9 @@ class Sport(db.Model):
 | 
				
			|||||||
    activities = db.relationship('Activity',
 | 
					    activities = db.relationship('Activity',
 | 
				
			||||||
                                 lazy=True,
 | 
					                                 lazy=True,
 | 
				
			||||||
                                 backref=db.backref('sports', lazy='joined'))
 | 
					                                 backref=db.backref('sports', lazy='joined'))
 | 
				
			||||||
 | 
					    records = db.relationship('Record',
 | 
				
			||||||
 | 
					                              lazy=True,
 | 
				
			||||||
 | 
					                              backref=db.backref('sports', lazy='joined'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return self.label
 | 
					        return self.label
 | 
				
			||||||
@@ -55,6 +66,9 @@ 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, 3), nullable=True)   # km/h
 | 
					    max_speed = db.Column(db.Numeric(5, 3), nullable=True)   # km/h
 | 
				
			||||||
    ave_speed = db.Column(db.Numeric(5, 3), nullable=True)   # km/h
 | 
					    ave_speed = db.Column(db.Numeric(5, 3), nullable=True)   # km/h
 | 
				
			||||||
 | 
					    records = db.relationship('Record',
 | 
				
			||||||
 | 
					                              lazy=True,
 | 
				
			||||||
 | 
					                              backref=db.backref('activities', lazy='joined'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return str(self.sports.label) + \
 | 
					        return str(self.sports.label) + \
 | 
				
			||||||
@@ -87,3 +101,55 @@ class Activity(db.Model):
 | 
				
			|||||||
            "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
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Record(db.Model):
 | 
				
			||||||
 | 
					    __tablename__ = "records"
 | 
				
			||||||
 | 
					    __table_args__ = (db.UniqueConstraint(
 | 
				
			||||||
 | 
					        'user_id', 'sport_id', 'record_type', name='user_sports_records'),)
 | 
				
			||||||
 | 
					    id = db.Column(
 | 
				
			||||||
 | 
					        db.Integer,
 | 
				
			||||||
 | 
					        primary_key=True,
 | 
				
			||||||
 | 
					        autoincrement=True)
 | 
				
			||||||
 | 
					    user_id = db.Column(
 | 
				
			||||||
 | 
					        db.Integer,
 | 
				
			||||||
 | 
					        db.ForeignKey('users.id'),
 | 
				
			||||||
 | 
					        nullable=False)
 | 
				
			||||||
 | 
					    sport_id = db.Column(
 | 
				
			||||||
 | 
					        db.Integer,
 | 
				
			||||||
 | 
					        db.ForeignKey('sports.id'),
 | 
				
			||||||
 | 
					        nullable=False)
 | 
				
			||||||
 | 
					    activity_id = db.Column(
 | 
				
			||||||
 | 
					        db.Integer,
 | 
				
			||||||
 | 
					        db.ForeignKey('activities.id'),
 | 
				
			||||||
 | 
					        nullable=False)
 | 
				
			||||||
 | 
					    record_type = db.Column(Enum(*record_types, name="record_types"))
 | 
				
			||||||
 | 
					    activity_date = db.Column(db.DateTime, nullable=False)
 | 
				
			||||||
 | 
					    value_interval = db.Column(db.Interval, nullable=True)
 | 
				
			||||||
 | 
					    value_float = db.Column(db.Numeric(5, 3), nullable=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __str__(self):
 | 
				
			||||||
 | 
					        return str(self.sports.label) + \
 | 
				
			||||||
 | 
					               " - " + self.record_type + \
 | 
				
			||||||
 | 
					               " - " + self.activity_date.strftime('%Y-%m-%d')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, user_id, sport_id, activity, record_type):
 | 
				
			||||||
 | 
					        self.user_id = user_id
 | 
				
			||||||
 | 
					        self.sport_id = sport_id
 | 
				
			||||||
 | 
					        self.activity_id = activity.id
 | 
				
			||||||
 | 
					        self.record_type = record_type
 | 
				
			||||||
 | 
					        self.activity_date = activity.activity_date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def serialize(self):
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            "id": self.id,
 | 
				
			||||||
 | 
					            "user_id": self.user_id,
 | 
				
			||||||
 | 
					            "sport_id": self.sport_id,
 | 
				
			||||||
 | 
					            "activity_id": self.activity_id,
 | 
				
			||||||
 | 
					            "record_type": self.record_type,
 | 
				
			||||||
 | 
					            "activity_date": self.activity_date,
 | 
				
			||||||
 | 
					            "value_interval": str(
 | 
				
			||||||
 | 
					                self.value_interval) if self.value_interval else None,
 | 
				
			||||||
 | 
					            "value_float": str(
 | 
				
			||||||
 | 
					                self.value_float) if self.value_float else None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								mpwo_api/mpwo_api/activities/records.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								mpwo_api/mpwo_api/activities/records.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					from flask import Blueprint, jsonify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..users.utils import authenticate
 | 
				
			||||||
 | 
					from .models import Record
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					records_blueprint = Blueprint('records', __name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@records_blueprint.route('/records', methods=['GET'])
 | 
				
			||||||
 | 
					@authenticate
 | 
				
			||||||
 | 
					def get_sports(auth_user_id):
 | 
				
			||||||
 | 
					    """Get all records for authenticated user"""
 | 
				
			||||||
 | 
					    records = Record.query.filter_by(user_id=auth_user_id)\
 | 
				
			||||||
 | 
					        .order_by(Record.record_type).all()
 | 
				
			||||||
 | 
					    records_list = []
 | 
				
			||||||
 | 
					    for record in records:
 | 
				
			||||||
 | 
					        records_list.append(record.serialize())
 | 
				
			||||||
 | 
					    response_object = {
 | 
				
			||||||
 | 
					        'status': 'success',
 | 
				
			||||||
 | 
					        'data': {
 | 
				
			||||||
 | 
					            'records': records_list
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return jsonify(response_object), 200
 | 
				
			||||||
							
								
								
									
										77
									
								
								mpwo_api/mpwo_api/tests/test_records.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								mpwo_api/mpwo_api/tests/test_records.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					import datetime
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from mpwo_api.tests.utils import add_activity, add_record, add_sport, add_user
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_get_all_activities_for_authenticated_user(app):
 | 
				
			||||||
 | 
					    add_user('test', 'test@test.com', '12345678')
 | 
				
			||||||
 | 
					    add_user('toto', 'toto@toto.com', '12345678')
 | 
				
			||||||
 | 
					    add_sport('cycling')
 | 
				
			||||||
 | 
					    add_sport('running')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    activity = add_activity(
 | 
				
			||||||
 | 
					        user_id=1,
 | 
				
			||||||
 | 
					        sport_id=2,
 | 
				
			||||||
 | 
					        activity_date=datetime.datetime.strptime('01/01/2018', '%d/%m/%Y'),
 | 
				
			||||||
 | 
					        distance=10,
 | 
				
			||||||
 | 
					        duration=datetime.timedelta(seconds=1024)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    add_record(1, 2, activity, 'LD')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    activity = add_activity(
 | 
				
			||||||
 | 
					        user_id=2,
 | 
				
			||||||
 | 
					        sport_id=1,
 | 
				
			||||||
 | 
					        activity_date=datetime.datetime.strptime('23/01/2018', '%d/%m/%Y'),
 | 
				
			||||||
 | 
					        distance=15,
 | 
				
			||||||
 | 
					        duration=datetime.timedelta(seconds=3600),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    add_record(2, 1, activity, 'MS')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    activity = add_activity(
 | 
				
			||||||
 | 
					        user_id=1,
 | 
				
			||||||
 | 
					        sport_id=1,
 | 
				
			||||||
 | 
					        activity_date=datetime.datetime.strptime('01/04/2018', '%d/%m/%Y'),
 | 
				
			||||||
 | 
					        distance=12,
 | 
				
			||||||
 | 
					        duration=datetime.timedelta(seconds=6000)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    add_record(1, 1, activity, 'FD')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    response = client.get(
 | 
				
			||||||
 | 
					        '/api/records',
 | 
				
			||||||
 | 
					        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']['records']) == 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert 'Sun, 01 Apr 2018 00:00:00 GMT' == data['data']['records'][0]['activity_date']  # noqa
 | 
				
			||||||
 | 
					    assert 1 == data['data']['records'][0]['user_id']
 | 
				
			||||||
 | 
					    assert 1 == data['data']['records'][0]['sport_id']
 | 
				
			||||||
 | 
					    assert 3 == data['data']['records'][0]['activity_id']
 | 
				
			||||||
 | 
					    assert 'FD' == data['data']['records'][0]['record_type']
 | 
				
			||||||
 | 
					    assert 'value_interval' in data['data']['records'][0]
 | 
				
			||||||
 | 
					    assert 'value_float' in data['data']['records'][0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['records'][1]['activity_date']  # noqa
 | 
				
			||||||
 | 
					    assert 1 == data['data']['records'][1]['user_id']
 | 
				
			||||||
 | 
					    assert 2 == data['data']['records'][1]['sport_id']
 | 
				
			||||||
 | 
					    assert 1 == data['data']['records'][1]['activity_id']
 | 
				
			||||||
 | 
					    assert 'LD' == data['data']['records'][1]['record_type']
 | 
				
			||||||
 | 
					    assert 'value_interval' in data['data']['records'][1]
 | 
				
			||||||
 | 
					    assert 'value_float' in data['data']['records'][1]
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from mpwo_api import db
 | 
					from mpwo_api import db
 | 
				
			||||||
from mpwo_api.activities.models import Activity, Sport
 | 
					from mpwo_api.activities.models import Activity, Record, Sport
 | 
				
			||||||
from mpwo_api.users.models import User
 | 
					from mpwo_api.users.models import User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -58,3 +58,14 @@ def add_activity(user_id, sport_id, activity_date, distance, duration):
 | 
				
			|||||||
def get_gpx_filepath(activity_id):
 | 
					def get_gpx_filepath(activity_id):
 | 
				
			||||||
    activity = Activity.query.filter_by(id=activity_id).first()
 | 
					    activity = Activity.query.filter_by(id=activity_id).first()
 | 
				
			||||||
    return activity.gpx
 | 
					    return activity.gpx
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def add_record(user_id, sport_id, activity, record_type):
 | 
				
			||||||
 | 
					    record = Record(
 | 
				
			||||||
 | 
					        user_id=user_id,
 | 
				
			||||||
 | 
					        sport_id=sport_id,
 | 
				
			||||||
 | 
					        activity=activity,
 | 
				
			||||||
 | 
					        record_type=record_type)
 | 
				
			||||||
 | 
					    db.session.add(record)
 | 
				
			||||||
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					    return record
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,9 @@ class User(db.Model):
 | 
				
			|||||||
    activities = db.relationship('Activity',
 | 
					    activities = db.relationship('Activity',
 | 
				
			||||||
                                 lazy=True,
 | 
					                                 lazy=True,
 | 
				
			||||||
                                 backref=db.backref('users', lazy='joined'))
 | 
					                                 backref=db.backref('users', lazy='joined'))
 | 
				
			||||||
 | 
					    records = db.relationship('Record',
 | 
				
			||||||
 | 
					                              lazy=True,
 | 
				
			||||||
 | 
					                              backref=db.backref('users', lazy='joined'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __repr__(self):
 | 
					    def __repr__(self):
 | 
				
			||||||
        return '<User %r>' % self.username
 | 
					        return '<User %r>' % self.username
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user