API: use of events (listen_for) to update records
This commit is contained in:
parent
fc09766c3a
commit
cadf5d81d1
@ -8,8 +8,8 @@ from sqlalchemy import exc
|
||||
from ..users.utils import authenticate, verify_extension
|
||||
from .models import Activity, Sport
|
||||
from .utils import (
|
||||
check_records, create_activity, create_segment, edit_activity,
|
||||
get_file_path, get_gpx_info, get_new_file_path
|
||||
create_activity, create_segment, edit_activity, get_file_path,
|
||||
get_gpx_info, get_new_file_path
|
||||
)
|
||||
|
||||
activities_blueprint = Blueprint('activities', __name__)
|
||||
@ -172,10 +172,6 @@ def post_activity(auth_user_id):
|
||||
db.session.add(new_activity)
|
||||
db.session.flush()
|
||||
|
||||
records = check_records(new_activity)
|
||||
for record in records:
|
||||
db.session.add(record)
|
||||
|
||||
for segment_data in gpx_data['segments']:
|
||||
new_segment = create_segment(new_activity.id, segment_data)
|
||||
db.session.add(new_segment)
|
||||
|
@ -2,7 +2,9 @@ import datetime
|
||||
|
||||
from mpwo_api import db
|
||||
from sqlalchemy.dialects import postgresql
|
||||
from sqlalchemy.event import listens_for
|
||||
from sqlalchemy.ext.hybrid import Comparator, hybrid_property
|
||||
from sqlalchemy.orm.session import object_session
|
||||
from sqlalchemy.types import Enum
|
||||
|
||||
record_types = [
|
||||
@ -18,6 +20,49 @@ def convert_timedelta_to_integer(value):
|
||||
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):
|
||||
__tablename__ = "sports"
|
||||
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]
|
||||
}
|
||||
|
||||
@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):
|
||||
__tablename__ = "activity_segments"
|
||||
@ -209,9 +303,9 @@ class Record(db.Model):
|
||||
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
|
||||
def __init__(self, activity, record_type):
|
||||
self.user_id = activity.user_id
|
||||
self.sport_id = activity.sport_id
|
||||
self.activity_id = activity.id
|
||||
self.record_type = record_type
|
||||
self.activity_date = activity.activity_date
|
||||
@ -230,12 +324,7 @@ class Record(db.Model):
|
||||
|
||||
@value.setter
|
||||
def value(self, val):
|
||||
if self.record_type == 'LD':
|
||||
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)
|
||||
self._value = convert_value_to_integer(self.record_type, val)
|
||||
|
||||
@value.comparator
|
||||
def value(cls):
|
||||
@ -258,3 +347,23 @@ class Record(db.Model):
|
||||
"activity_date": self.activity_date,
|
||||
"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)
|
||||
|
@ -7,7 +7,7 @@ from flask import current_app
|
||||
from mpwo_api import appLog
|
||||
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):
|
||||
@ -23,33 +23,6 @@ def update_activity_data(activity, gpx_data):
|
||||
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(
|
||||
auth_user_id, activity_data, gpx_data=None
|
||||
):
|
||||
|
@ -3,7 +3,7 @@ import os
|
||||
|
||||
import pytest
|
||||
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
|
||||
|
||||
os.environ["FLASK_ENV"] = 'testing'
|
||||
@ -88,6 +88,8 @@ def activity_cycling_user_1():
|
||||
distance=10,
|
||||
duration=datetime.timedelta(seconds=1024)
|
||||
)
|
||||
activity.max_speed = 10
|
||||
activity.ave_speed = 10
|
||||
db.session.add(activity)
|
||||
db.session.commit()
|
||||
return activity
|
||||
@ -130,6 +132,7 @@ def seven_activities_user_1():
|
||||
duration=datetime.timedelta(seconds=1024)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
@ -138,6 +141,7 @@ def seven_activities_user_1():
|
||||
duration=datetime.timedelta(seconds=6000)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
@ -146,6 +150,7 @@ def seven_activities_user_1():
|
||||
duration=datetime.timedelta(seconds=1024)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
@ -154,6 +159,7 @@ def seven_activities_user_1():
|
||||
duration=datetime.timedelta(seconds=3000)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
@ -162,6 +168,7 @@ def seven_activities_user_1():
|
||||
duration=datetime.timedelta(seconds=3456)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
@ -170,6 +177,7 @@ def seven_activities_user_1():
|
||||
duration=datetime.timedelta(seconds=600)
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.flush()
|
||||
activity = Activity(
|
||||
user_id=1,
|
||||
sport_id=1,
|
||||
@ -196,78 +204,6 @@ def activity_cycling_user_2():
|
||||
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()
|
||||
def gpx_file():
|
||||
return (
|
||||
|
@ -1,10 +1,9 @@
|
||||
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,
|
||||
activity_cycling_user_1, activity_cycling_user_2, activity_running_user_1,
|
||||
record_ld, record_ms_user_2_cycling, record_fd_running
|
||||
activity_cycling_user_1
|
||||
):
|
||||
client = app.test_client()
|
||||
resp_login = client.post(
|
||||
@ -27,18 +26,32 @@ def test_get_all_records_for_authenticated_user(
|
||||
|
||||
assert response.status_code == 200
|
||||
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 2 == data['data']['records'][0]['sport_id']
|
||||
assert 3 == data['data']['records'][0]['activity_id']
|
||||
assert 'FD' == data['data']['records'][0]['record_type']
|
||||
assert 1 == data['data']['records'][0]['sport_id']
|
||||
assert 1 == data['data']['records'][0]['activity_id']
|
||||
assert 'AS' == data['data']['records'][0]['record_type']
|
||||
assert 'value' 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 1 == data['data']['records'][1]['sport_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 '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]
|
||||
|
@ -1,10 +1,16 @@
|
||||
import datetime
|
||||
|
||||
from mpwo_api.activities.models import Record
|
||||
|
||||
|
||||
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.sport_id
|
||||
assert 1 == record_ld.activity_id
|
||||
@ -22,49 +28,50 @@ def test_record_model(
|
||||
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(
|
||||
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.value = 4.61
|
||||
record_as = Record.query.filter_by(
|
||||
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 record_as.value == 4.61
|
||||
assert record_as._value == 461
|
||||
assert record_as.value == 10.0
|
||||
assert record_as._value == 1000
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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 record_fd.value == 0.322
|
||||
assert record_fd._value == 322
|
||||
assert record_fd.value == 10.0
|
||||
assert record_fd._value == 10000
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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 str(record_ld.value) == '0:17:04'
|
||||
@ -76,10 +83,14 @@ def test_add_ld_records(
|
||||
|
||||
|
||||
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.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()
|
||||
record_ld.value = datetime.timedelta(seconds=0)
|
||||
|
||||
assert isinstance(record_ld.value, datetime.timedelta)
|
||||
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(
|
||||
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 record_ms.value == 23.5
|
||||
assert record_ms._value == 2350
|
||||
assert record_ms.value == 10.0
|
||||
assert record_ms._value == 1000
|
||||
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user