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
 | 
			
		||||
Revises:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
"""empty message
 | 
			
		||||
"""create Activity & Sport tables
 | 
			
		||||
 | 
			
		||||
Revision ID: b7cfe0c17708
 | 
			
		||||
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.users import users_blueprint  # noqa
 | 
			
		||||
    from .activities.activities import activities_blueprint  # noqa
 | 
			
		||||
    from .activities.records import records_blueprint  # noqa
 | 
			
		||||
    from .activities.sports import sports_blueprint  # noqa
 | 
			
		||||
 | 
			
		||||
    app.register_blueprint(users_blueprint, url_prefix='/api')
 | 
			
		||||
    app.register_blueprint(auth_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')
 | 
			
		||||
 | 
			
		||||
    if app.debug:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,14 @@
 | 
			
		||||
import datetime
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
@@ -10,6 +18,9 @@ class Sport(db.Model):
 | 
			
		||||
    activities = db.relationship('Activity',
 | 
			
		||||
                                 lazy=True,
 | 
			
		||||
                                 backref=db.backref('sports', lazy='joined'))
 | 
			
		||||
    records = db.relationship('Record',
 | 
			
		||||
                              lazy=True,
 | 
			
		||||
                              backref=db.backref('sports', lazy='joined'))
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return self.label
 | 
			
		||||
@@ -55,6 +66,9 @@ class Activity(db.Model):
 | 
			
		||||
    ascent = db.Column(db.Numeric(5, 2), nullable=True)      # meters
 | 
			
		||||
    max_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):
 | 
			
		||||
        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,
 | 
			
		||||
            "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
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -58,3 +58,14 @@ def add_activity(user_id, sport_id, activity_date, distance, duration):
 | 
			
		||||
def get_gpx_filepath(activity_id):
 | 
			
		||||
    activity = Activity.query.filter_by(id=activity_id).first()
 | 
			
		||||
    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',
 | 
			
		||||
                                 lazy=True,
 | 
			
		||||
                                 backref=db.backref('users', lazy='joined'))
 | 
			
		||||
    records = db.relationship('Record',
 | 
			
		||||
                              lazy=True,
 | 
			
		||||
                              backref=db.backref('users', lazy='joined'))
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return '<User %r>' % self.username
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user