API - FlaskSQLAlchemy update rollback

This commit is contained in:
Sam
2023-10-04 15:25:26 +02:00
parent 22c29bf33f
commit 23ef5fbb55
12 changed files with 221 additions and 266 deletions

View File

@ -1,13 +1,13 @@
import datetime
import os
from datetime import datetime, timedelta
from typing import Any, Dict, Optional, Union
from uuid import UUID, uuid4
from sqlalchemy.dialects import postgresql
from sqlalchemy.engine.base import Connection
from sqlalchemy.event import listens_for
from sqlalchemy.ext.declarative import DeclarativeMeta
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import mapped_column, relationship
from sqlalchemy.orm.mapper import Mapper
from sqlalchemy.orm.session import Session, object_session
from sqlalchemy.types import JSON, Enum
@ -18,6 +18,7 @@ from fittrackee.files import get_absolute_file_path
from .utils.convert import convert_in_duration, convert_value_to_integer
from .utils.short_id import encode_uuid
BaseModel: DeclarativeMeta = db.Model
record_types = [
'AS', # 'Best Average Speed'
'FD', # 'Farthest Distance'
@ -66,20 +67,18 @@ def update_records(
)
class Sport(db.Model): # type: ignore
class Sport(BaseModel):
__tablename__ = 'sports'
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
label = mapped_column(db.String(50), unique=True, nullable=False)
is_active = mapped_column(db.Boolean, default=True, nullable=False)
stopped_speed_threshold = mapped_column(
db.Float, default=1.0, nullable=False
)
workouts = relationship(
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
label = db.Column(db.String(50), unique=True, nullable=False)
is_active = db.Column(db.Boolean, default=True, nullable=False)
stopped_speed_threshold = db.Column(db.Float, default=1.0, nullable=False)
workouts = db.relationship(
'Workout',
lazy=True,
backref=db.backref('sport', lazy='joined', single_parent=True),
)
records = relationship(
records = db.relationship(
'Record',
lazy=True,
backref=db.backref('sport', lazy='joined', single_parent=True),
@ -121,49 +120,51 @@ class Sport(db.Model): # type: ignore
return serialized_sport
class Workout(db.Model): # type: ignore
class Workout(BaseModel):
__tablename__ = 'workouts'
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
uuid = mapped_column(
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
uuid = db.Column(
postgresql.UUID(as_uuid=True),
default=uuid4,
unique=True,
nullable=False,
)
user_id = mapped_column(
user_id = db.Column(
db.Integer, db.ForeignKey('users.id'), index=True, nullable=False
)
sport_id = mapped_column(
sport_id = db.Column(
db.Integer, db.ForeignKey('sports.id'), index=True, nullable=False
)
title = mapped_column(db.String(255), nullable=True)
gpx = mapped_column(db.String(255), nullable=True)
creation_date = mapped_column(db.DateTime, default=datetime.utcnow)
modification_date = mapped_column(db.DateTime, onupdate=datetime.utcnow)
workout_date = mapped_column(db.DateTime, index=True, nullable=False)
duration = mapped_column(db.Interval, nullable=False)
pauses = mapped_column(db.Interval, nullable=True)
moving = mapped_column(db.Interval, nullable=True)
distance = mapped_column(db.Numeric(6, 3), nullable=True) # kilometers
min_alt = mapped_column(db.Numeric(6, 2), nullable=True) # meters
max_alt = mapped_column(db.Numeric(6, 2), nullable=True) # meters
descent = mapped_column(db.Numeric(8, 3), nullable=True) # meters
ascent = mapped_column(db.Numeric(8, 3), nullable=True) # meters
max_speed = mapped_column(db.Numeric(6, 2), nullable=True) # km/h
ave_speed = mapped_column(db.Numeric(6, 2), nullable=True) # km/h
bounds = mapped_column(postgresql.ARRAY(db.Float), nullable=True)
map = mapped_column(db.String(255), nullable=True)
map_id = mapped_column(db.String(50), index=True, nullable=True)
weather_start = mapped_column(JSON, nullable=True)
weather_end = mapped_column(JSON, nullable=True)
notes = mapped_column(db.String(500), nullable=True)
segments = relationship(
title = db.Column(db.String(255), nullable=True)
gpx = db.Column(db.String(255), nullable=True)
creation_date = db.Column(db.DateTime, default=datetime.datetime.utcnow)
modification_date = db.Column(
db.DateTime, onupdate=datetime.datetime.utcnow
)
workout_date = db.Column(db.DateTime, index=True, nullable=False)
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(6, 3), nullable=True) # kilometers
min_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
max_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
descent = db.Column(db.Numeric(8, 3), nullable=True) # meters
ascent = db.Column(db.Numeric(8, 3), nullable=True) # meters
max_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
ave_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
bounds = db.Column(postgresql.ARRAY(db.Float), nullable=True)
map = db.Column(db.String(255), nullable=True)
map_id = db.Column(db.String(50), index=True, nullable=True)
weather_start = db.Column(JSON, nullable=True)
weather_end = db.Column(JSON, nullable=True)
notes = db.Column(db.String(500), nullable=True)
segments = db.relationship(
'WorkoutSegment',
lazy=True,
cascade='all, delete',
backref=db.backref('workout', lazy='joined', single_parent=True),
)
records = relationship(
records = db.relationship(
'Record',
lazy=True,
cascade='all, delete',
@ -177,9 +178,9 @@ class Workout(db.Model): # type: ignore
self,
user_id: int,
sport_id: int,
workout_date: datetime,
workout_date: datetime.datetime,
distance: float,
duration: timedelta,
duration: datetime.timedelta,
) -> None:
self.user_id = user_id
self.sport_id = sport_id
@ -241,10 +242,11 @@ class Workout(db.Model): # type: ignore
Workout.sport_id == sport_id if sport_id else True,
Workout.workout_date <= self.workout_date,
Workout.workout_date
>= datetime.strptime(date_from, '%Y-%m-%d')
>= datetime.datetime.strptime(date_from, '%Y-%m-%d')
if date_from
else True,
Workout.workout_date <= datetime.strptime(date_to, '%Y-%m-%d')
Workout.workout_date
<= datetime.datetime.strptime(date_to, '%Y-%m-%d')
if date_to
else True,
Workout.distance >= float(distance_from)
@ -282,10 +284,11 @@ class Workout(db.Model): # type: ignore
Workout.sport_id == sport_id if sport_id else True,
Workout.workout_date >= self.workout_date,
Workout.workout_date
>= datetime.strptime(date_from, '%Y-%m-%d')
>= datetime.datetime.strptime(date_from, '%Y-%m-%d')
if date_from
else True,
Workout.workout_date <= datetime.strptime(date_to, '%Y-%m-%d')
Workout.workout_date
<= datetime.datetime.strptime(date_to, '%Y-%m-%d')
if date_to
else True,
Workout.distance >= float(distance_from)
@ -377,10 +380,9 @@ def on_workout_insert(
def on_workout_update(
mapper: Mapper, connection: Connection, workout: Workout
) -> None:
workout_session = object_session(workout)
if workout_session is not None and workout_session.is_modified(
if object_session(workout).is_modified(
workout, include_collections=True
):
): # noqa
@listens_for(db.Session, 'after_flush', once=True)
def receive_after_flush(session: Session, context: Any) -> None:
@ -411,23 +413,23 @@ def on_workout_delete(
appLog.error('gpx file not found when deleting workout')
class WorkoutSegment(db.Model): # type: ignore
class WorkoutSegment(BaseModel):
__tablename__ = 'workout_segments'
workout_id = mapped_column(
workout_id = db.Column(
db.Integer, db.ForeignKey('workouts.id'), primary_key=True
)
workout_uuid = mapped_column(postgresql.UUID(as_uuid=True), nullable=False)
segment_id = mapped_column(db.Integer, primary_key=True)
duration = mapped_column(db.Interval, nullable=False)
pauses = mapped_column(db.Interval, nullable=True)
moving = mapped_column(db.Interval, nullable=True)
distance = mapped_column(db.Numeric(6, 3), nullable=True) # kilometers
min_alt = mapped_column(db.Numeric(6, 2), nullable=True) # meters
max_alt = mapped_column(db.Numeric(6, 2), nullable=True) # meters
descent = mapped_column(db.Numeric(8, 3), nullable=True) # meters
ascent = mapped_column(db.Numeric(8, 3), nullable=True) # meters
max_speed = mapped_column(db.Numeric(6, 2), nullable=True) # km/h
ave_speed = mapped_column(db.Numeric(6, 2), nullable=True) # km/h
workout_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False)
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(6, 3), nullable=True) # kilometers
min_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
max_alt = db.Column(db.Numeric(6, 2), nullable=True) # meters
descent = db.Column(db.Numeric(8, 3), nullable=True) # meters
ascent = db.Column(db.Numeric(8, 3), nullable=True) # meters
max_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
ave_speed = db.Column(db.Numeric(6, 2), nullable=True) # km/h
def __str__(self) -> str:
return (
@ -459,27 +461,25 @@ class WorkoutSegment(db.Model): # type: ignore
}
class Record(db.Model): # type: ignore
class Record(BaseModel):
__tablename__ = "records"
__table_args__ = (
db.UniqueConstraint(
'user_id', 'sport_id', 'record_type', name='user_sports_records'
),
)
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
user_id = mapped_column(
db.Integer, db.ForeignKey('users.id'), nullable=False
)
sport_id = mapped_column(
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
)
workout_id = mapped_column(
workout_id = db.Column(
db.Integer, db.ForeignKey('workouts.id'), nullable=False
)
workout_uuid = mapped_column(postgresql.UUID(as_uuid=True), nullable=False)
record_type = mapped_column(Enum(*record_types, name="record_types"))
workout_date = mapped_column(db.DateTime, nullable=False)
_value = mapped_column("value", db.Integer, nullable=True)
workout_uuid = db.Column(postgresql.UUID(as_uuid=True), nullable=False)
record_type = db.Column(Enum(*record_types, name="record_types"))
workout_date = db.Column(db.DateTime, nullable=False)
_value = db.Column("value", db.Integer, nullable=True)
def __str__(self) -> str:
return (
@ -497,11 +497,11 @@ class Record(db.Model): # type: ignore
self.workout_date = workout.workout_date
@hybrid_property
def value(self) -> Optional[Union[timedelta, float]]:
def value(self) -> Optional[Union[datetime.timedelta, float]]:
if self._value is None:
return None
if self.record_type == 'LD':
return timedelta(seconds=self._value)
return datetime.timedelta(seconds=self._value)
elif self.record_type in ['AS', 'MS']:
return float(self._value / 100)
else: # 'FD' or 'HA'