API: use of events (listen_for) to update records

This commit is contained in:
Sam 2018-05-15 16:00:13 +02:00
parent fc09766c3a
commit cadf5d81d1
6 changed files with 200 additions and 158 deletions

View File

@ -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 (
check_records, create_activity, create_segment, edit_activity, create_activity, create_segment, edit_activity, get_file_path,
get_file_path, get_gpx_info, get_new_file_path get_gpx_info, get_new_file_path
) )
activities_blueprint = Blueprint('activities', __name__) activities_blueprint = Blueprint('activities', __name__)
@ -172,10 +172,6 @@ def post_activity(auth_user_id):
db.session.add(new_activity) db.session.add(new_activity)
db.session.flush() db.session.flush()
records = check_records(new_activity)
for record in records:
db.session.add(record)
for segment_data in gpx_data['segments']: for segment_data in gpx_data['segments']:
new_segment = create_segment(new_activity.id, segment_data) new_segment = create_segment(new_activity.id, segment_data)
db.session.add(new_segment) db.session.add(new_segment)

View File

@ -2,7 +2,9 @@ import datetime
from mpwo_api import db from mpwo_api import db
from sqlalchemy.dialects import postgresql from sqlalchemy.dialects import postgresql
from sqlalchemy.event import listens_for
from sqlalchemy.ext.hybrid import Comparator, hybrid_property from sqlalchemy.ext.hybrid import Comparator, hybrid_property
from sqlalchemy.orm.session import object_session
from sqlalchemy.types import Enum from sqlalchemy.types import Enum
record_types = [ record_types = [
@ -18,6 +20,49 @@ def convert_timedelta_to_integer(value):
return int(hours) * 3600 + int(minutes) * 60 + int(seconds) return int(hours) * 3600 + int(minutes) * 60 + int(seconds)
def convert_value_to_integer(record_type, val):
if val is None:
return None
if record_type == 'LD':
return convert_timedelta_to_integer(val)
elif record_type in ['AS', 'MS']:
return int(val * 100)
else: # 'FD'
return int(val * 1000)
def update_records(activity, sport_id, connection, session):
record_table = Record.__table__
new_records = Activity.get_user_activity_records(
activity.user_id,
sport_id)
for record_type, record_data in new_records.items():
if record_data['record_value']:
record = Record.query.filter_by(
user_id=activity.user_id,
sport_id=activity.sport_id,
record_type=record_type,
).first()
if record:
value = convert_value_to_integer(
record_type, record_data['record_value']
)
connection.execute(record_table.update().where(
record_table.c.id == record.id,
).values(
value=value,
activity_id=record_data['activity'].id
))
else:
new_record = Record(
activity=record_data['activity'],
record_type=record_type
)
new_record.value = record_data['record_value']
session.add(new_record)
class Sport(db.Model): class Sport(db.Model):
__tablename__ = "sports" __tablename__ = "sports"
id = db.Column(db.Integer, primary_key=True, autoincrement=True) id = db.Column(db.Integer, primary_key=True, autoincrement=True)
@ -125,6 +170,55 @@ class Activity(db.Model):
"segments": [segment.serialize() for segment in self.segments] "segments": [segment.serialize() for segment in self.segments]
} }
@classmethod
def get_user_activity_records(cls, user_id, sport_id, as_integer=False):
record_types_columns = {
'AS': 'ave_speed', # 'Average speed'
'FD': 'distance', # 'Farthest Distance'
'LD': 'duration', # 'Longest Duration'
'MS': 'max_speed', # 'Max speed'
}
records = {}
for record_type, column in record_types_columns.items():
column_sorted = getattr(getattr(Activity, column), 'desc')()
record_activity = Activity.query.filter_by(
user_id=user_id,
sport_id=sport_id,
).order_by(
column_sorted,
Activity.activity_date,
).first()
records[record_type] = dict(
record_value=(getattr(record_activity, column)
if record_activity else None),
activity=record_activity)
return records
@listens_for(Activity, 'after_insert')
def on_activity_insert(mapper, connection, activity):
@listens_for(db.Session, 'after_flush', once=True)
def receive_after_flush(session, context):
update_records(activity, activity.sport_id, connection, session)
@listens_for(Activity, 'after_update')
def on_activity_update(mapper, connection, activity):
if object_session(activity).is_modified(activity, include_collections=True): # noqa
sports_list = []
records = Record.query.filter_by(
activity_id=activity.id,
).all()
for rec in records:
if rec.sport_id not in sports_list:
sports_list.append(rec.sport_id)
@listens_for(db.Session, 'after_flush', once=True)
def receive_after_flush(session, context):
for sport_id in sports_list:
update_records(activity, sport_id, connection, session)
class ActivitySegment(db.Model): class ActivitySegment(db.Model):
__tablename__ = "activity_segments" __tablename__ = "activity_segments"
@ -209,9 +303,9 @@ class Record(db.Model):
self.activity_date.strftime('%Y-%m-%d') self.activity_date.strftime('%Y-%m-%d')
) )
def __init__(self, user_id, sport_id, activity, record_type): def __init__(self, activity, record_type):
self.user_id = user_id self.user_id = activity.user_id
self.sport_id = sport_id self.sport_id = activity.sport_id
self.activity_id = activity.id self.activity_id = activity.id
self.record_type = record_type self.record_type = record_type
self.activity_date = activity.activity_date self.activity_date = activity.activity_date
@ -230,12 +324,7 @@ class Record(db.Model):
@value.setter @value.setter
def value(self, val): def value(self, val):
if self.record_type == 'LD': self._value = convert_value_to_integer(self.record_type, val)
self._value = convert_timedelta_to_integer(val)
elif self.record_type in ['AS', 'MS']:
self._value = int(val * 100)
else: # 'FD'
self._value = int(val * 1000)
@value.comparator @value.comparator
def value(cls): def value(cls):
@ -258,3 +347,23 @@ class Record(db.Model):
"activity_date": self.activity_date, "activity_date": self.activity_date,
"value": value, "value": value,
} }
@listens_for(Record, 'after_delete')
def on_record_delete(mapper, connection, old_record):
@listens_for(db.Session, 'after_flush', once=True)
def receive_after_flush(session, context):
activity = old_record.activities
new_records = Activity.get_user_activity_records(
activity.user_id,
activity.sport_id)
for record_type, record_data in new_records.items():
if record_data['record_value'] \
and record_type == old_record.record_type:
new_record = Record(
activity=record_data['activity'],
record_type=record_type
)
new_record.value = record_data['record_value']
session.add(new_record)

View File

@ -7,7 +7,7 @@ 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, ActivitySegment, Record, Sport from .models import Activity, ActivitySegment, Sport
def update_activity_data(activity, gpx_data): def update_activity_data(activity, gpx_data):
@ -23,33 +23,6 @@ def update_activity_data(activity, gpx_data):
return activity return activity
def check_records(activity):
record_types_columns = {
'AS': 'ave_speed', # 'Average speed'
'FD': 'distance', # 'Farthest Distance'
'LD': 'duration', # 'Longest Duration'
'MS': 'max_speed', # 'Max speed'
}
records = []
for record_type, column in record_types_columns.items():
print('{} = {}'.format(record_type, str(column)))
record = Activity.query.filter_by(
user_id=activity.user_id,
sport_id=activity.sport_id,
).filter(Record.value > getattr(activity, column)).first()
print(record)
if not record:
new_record = Record(
user_id=activity.user_id,
sport_id=activity.sport_id,
activity=activity,
record_type=record_type,
)
new_record.value = getattr(activity, column)
records.append(new_record)
return records
def create_activity( def create_activity(
auth_user_id, activity_data, gpx_data=None auth_user_id, activity_data, gpx_data=None
): ):

View File

@ -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, ActivitySegment, Record, Sport from mpwo_api.activities.models import Activity, ActivitySegment, 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'
@ -88,6 +88,8 @@ def activity_cycling_user_1():
distance=10, distance=10,
duration=datetime.timedelta(seconds=1024) duration=datetime.timedelta(seconds=1024)
) )
activity.max_speed = 10
activity.ave_speed = 10
db.session.add(activity) db.session.add(activity)
db.session.commit() db.session.commit()
return activity return activity
@ -130,6 +132,7 @@ def seven_activities_user_1():
duration=datetime.timedelta(seconds=1024) duration=datetime.timedelta(seconds=1024)
) )
db.session.add(activity) db.session.add(activity)
db.session.flush()
activity = Activity( activity = Activity(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
@ -138,6 +141,7 @@ def seven_activities_user_1():
duration=datetime.timedelta(seconds=6000) duration=datetime.timedelta(seconds=6000)
) )
db.session.add(activity) db.session.add(activity)
db.session.flush()
activity = Activity( activity = Activity(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
@ -146,6 +150,7 @@ def seven_activities_user_1():
duration=datetime.timedelta(seconds=1024) duration=datetime.timedelta(seconds=1024)
) )
db.session.add(activity) db.session.add(activity)
db.session.flush()
activity = Activity( activity = Activity(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
@ -154,6 +159,7 @@ def seven_activities_user_1():
duration=datetime.timedelta(seconds=3000) duration=datetime.timedelta(seconds=3000)
) )
db.session.add(activity) db.session.add(activity)
db.session.flush()
activity = Activity( activity = Activity(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
@ -162,6 +168,7 @@ def seven_activities_user_1():
duration=datetime.timedelta(seconds=3456) duration=datetime.timedelta(seconds=3456)
) )
db.session.add(activity) db.session.add(activity)
db.session.flush()
activity = Activity( activity = Activity(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
@ -170,6 +177,7 @@ def seven_activities_user_1():
duration=datetime.timedelta(seconds=600) duration=datetime.timedelta(seconds=600)
) )
db.session.add(activity) db.session.add(activity)
db.session.flush()
activity = Activity( activity = Activity(
user_id=1, user_id=1,
sport_id=1, sport_id=1,
@ -196,78 +204,6 @@ def activity_cycling_user_2():
return activity return activity
@pytest.fixture()
def record_as(activity_cycling_user_1):
record = Record(
user_id=1,
sport_id=1,
activity=activity_cycling_user_1,
record_type='AS')
db.session.add(record)
db.session.commit()
return record
@pytest.fixture()
def record_fd(activity_cycling_user_1):
record = Record(
user_id=1,
sport_id=1,
activity=activity_cycling_user_1,
record_type='FD')
db.session.add(record)
db.session.commit()
return record
@pytest.fixture()
def record_fd_running(activity_running_user_1):
record = Record(
user_id=1,
sport_id=2,
activity=activity_running_user_1,
record_type='FD')
db.session.add(record)
db.session.commit()
return record
@pytest.fixture()
def record_ld(activity_cycling_user_1):
record = Record(
user_id=1,
sport_id=1,
activity=activity_cycling_user_1,
record_type='LD')
db.session.add(record)
db.session.commit()
return record
@pytest.fixture()
def record_ms(activity_cycling_user_1):
record = Record(
user_id=1,
sport_id=1,
activity=activity_cycling_user_1,
record_type='MS')
db.session.add(record)
db.session.commit()
return record
@pytest.fixture()
def record_ms_user_2_cycling(activity_cycling_user_2):
record = Record(
user_id=2,
sport_id=1,
activity=activity_cycling_user_2,
record_type='MS')
db.session.add(record)
db.session.commit()
return record
@pytest.fixture() @pytest.fixture()
def gpx_file(): def gpx_file():
return ( return (

View File

@ -1,10 +1,9 @@
import json import json
def test_get_all_records_for_authenticated_user( def test_get_records_for_authenticated_user(
app, user_1, user_2, sport_1_cycling, sport_2_running, app, user_1, user_2, sport_1_cycling, sport_2_running,
activity_cycling_user_1, activity_cycling_user_2, activity_running_user_1, activity_cycling_user_1
record_ld, record_ms_user_2_cycling, record_fd_running
): ):
client = app.test_client() client = app.test_client()
resp_login = client.post( resp_login = client.post(
@ -27,18 +26,32 @@ def test_get_all_records_for_authenticated_user(
assert response.status_code == 200 assert response.status_code == 200
assert 'success' in data['status'] assert 'success' in data['status']
assert len(data['data']['records']) == 2 assert len(data['data']['records']) == 4
assert 'Sun, 01 Apr 2018 00:00:00 GMT' == data['data']['records'][0]['activity_date'] # noqa assert 'Mon, 01 Jan 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]['user_id']
assert 2 == data['data']['records'][0]['sport_id'] assert 1 == data['data']['records'][0]['sport_id']
assert 3 == data['data']['records'][0]['activity_id'] assert 1 == data['data']['records'][0]['activity_id']
assert 'FD' == data['data']['records'][0]['record_type'] assert 'AS' == data['data']['records'][0]['record_type']
assert 'value' in data['data']['records'][0] assert 'value' in data['data']['records'][0]
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['records'][1]['activity_date'] # noqa 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 1 == data['data']['records'][1]['user_id']
assert 1 == data['data']['records'][1]['sport_id'] assert 1 == data['data']['records'][1]['sport_id']
assert 1 == data['data']['records'][1]['activity_id'] assert 1 == data['data']['records'][1]['activity_id']
assert 'LD' == data['data']['records'][1]['record_type'] assert 'FD' == data['data']['records'][1]['record_type']
assert 'value' in data['data']['records'][1] assert 'value' in data['data']['records'][1]
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['records'][2]['activity_date'] # noqa
assert 1 == data['data']['records'][2]['user_id']
assert 1 == data['data']['records'][2]['sport_id']
assert 1 == data['data']['records'][2]['activity_id']
assert 'LD' == data['data']['records'][2]['record_type']
assert 'value' in data['data']['records'][2]
assert 'Mon, 01 Jan 2018 00:00:00 GMT' == data['data']['records'][3]['activity_date'] # noqa
assert 1 == data['data']['records'][3]['user_id']
assert 1 == data['data']['records'][3]['sport_id']
assert 1 == data['data']['records'][3]['activity_id']
assert 'MS' == data['data']['records'][3]['record_type']
assert 'value' in data['data']['records'][3]

View File

@ -1,10 +1,16 @@
import datetime import datetime
from mpwo_api.activities.models import Record
def test_record_model( def test_record_model(
app, user_1, sport_1_cycling, activity_cycling_user_1, record_ld app, user_1, sport_1_cycling, activity_cycling_user_1
): ):
assert 1 == record_ld.id record_ld = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id,
record_type='LD',
).first()
assert 1 == record_ld.user_id assert 1 == record_ld.user_id
assert 1 == record_ld.sport_id assert 1 == record_ld.sport_id
assert 1 == record_ld.activity_id assert 1 == record_ld.activity_id
@ -22,49 +28,50 @@ def test_record_model(
assert 'value' in record_serialize assert 'value' in record_serialize
def test_add_record_no_value(
app, user_1, sport_1_cycling, activity_cycling_user_1, record_as
):
assert record_as.value is None
assert record_as._value is None
record_serialize = record_as.serialize()
assert record_serialize.get('value') is None
def test_add_as_records( def test_add_as_records(
app, user_1, sport_1_cycling, activity_cycling_user_1, record_as app, user_1, sport_1_cycling, activity_cycling_user_1
): ):
# record.value = 4.6 record_as = Record.query.filter_by(
record_as.value = 4.61 user_id=activity_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id,
record_type='AS',
).first()
assert isinstance(record_as.value, float) assert isinstance(record_as.value, float)
assert record_as.value == 4.61 assert record_as.value == 10.0
assert record_as._value == 461 assert record_as._value == 1000
record_serialize = record_as.serialize() record_serialize = record_as.serialize()
assert record_serialize.get('value') == 4.61 assert record_serialize.get('value') == 10.0
assert isinstance(record_serialize.get('value'), float) assert isinstance(record_serialize.get('value'), float)
def test_add_fd_records( def test_add_fd_records(
app, user_1, sport_1_cycling, activity_cycling_user_1, record_fd app, user_1, sport_1_cycling, activity_cycling_user_1
): ):
record_fd.value = 0.322 record_fd = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id,
record_type='FD',
).first()
assert isinstance(record_fd.value, float) assert isinstance(record_fd.value, float)
assert record_fd.value == 0.322 assert record_fd.value == 10.0
assert record_fd._value == 322 assert record_fd._value == 10000
record_serialize = record_fd.serialize() record_serialize = record_fd.serialize()
assert record_serialize.get('value') == 0.322 assert record_serialize.get('value') == 10.0
assert isinstance(record_serialize.get('value'), float) assert isinstance(record_serialize.get('value'), float)
def test_add_ld_records( def test_add_ld_records(
app, user_1, sport_1_cycling, activity_cycling_user_1, record_ld app, user_1, sport_1_cycling, activity_cycling_user_1
): ):
record_ld.value = activity_cycling_user_1.duration record_ld = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id,
record_type='LD',
).first()
assert isinstance(record_ld.value, datetime.timedelta) assert isinstance(record_ld.value, datetime.timedelta)
assert str(record_ld.value) == '0:17:04' assert str(record_ld.value) == '0:17:04'
@ -76,10 +83,14 @@ def test_add_ld_records(
def test_add_ld_records_zero( def test_add_ld_records_zero(
app, user_1, sport_1_cycling, activity_cycling_user_1, record_ld app, user_1, sport_1_cycling, activity_cycling_user_1
): ):
activity_cycling_user_1.duration = datetime.timedelta(seconds=0) record_ld = Record.query.filter_by(
record_ld.value = activity_cycling_user_1.duration user_id=activity_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id,
record_type='LD',
).first()
record_ld.value = datetime.timedelta(seconds=0)
assert isinstance(record_ld.value, datetime.timedelta) assert isinstance(record_ld.value, datetime.timedelta)
assert str(record_ld.value) == '0:00:00' assert str(record_ld.value) == '0:00:00'
@ -91,14 +102,18 @@ def test_add_ld_records_zero(
def test_add_ms_records_no_value( def test_add_ms_records_no_value(
app, user_1, sport_1_cycling, activity_cycling_user_1, record_ms app, user_1, sport_1_cycling, activity_cycling_user_1
): ):
record_ms.value = 23.5 record_ms = Record.query.filter_by(
user_id=activity_cycling_user_1.user_id,
sport_id=activity_cycling_user_1.sport_id,
record_type='MS',
).first()
assert isinstance(record_ms.value, float) assert isinstance(record_ms.value, float)
assert record_ms.value == 23.5 assert record_ms.value == 10.0
assert record_ms._value == 2350 assert record_ms._value == 1000
record_serialize = record_ms.serialize() record_serialize = record_ms.serialize()
assert record_serialize.get('value') == 23.5 assert record_serialize.get('value') == 10.0
assert isinstance(record_serialize.get('value'), float) assert isinstance(record_serialize.get('value'), float)