API - update to latest version of Flask-SQLAlchemy
(and to SQLAlchemy 2.x)
This commit is contained in:
@ -20,6 +20,7 @@ from flask_limiter.util import get_remote_address
|
||||
from flask_migrate import Migrate
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from sqlalchemy.exc import ProgrammingError
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
|
||||
from fittrackee.emails.email import EmailService
|
||||
@ -38,7 +39,12 @@ logging.basicConfig(
|
||||
)
|
||||
appLog = logging.getLogger('fittrackee')
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
db = SQLAlchemy(model_class=Base)
|
||||
bcrypt = Bcrypt()
|
||||
migrate = Migrate()
|
||||
email_service = EmailService()
|
||||
|
@ -5,29 +5,30 @@ from flask import current_app
|
||||
from sqlalchemy import exc
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.event import listens_for
|
||||
from sqlalchemy.ext.declarative import DeclarativeMeta
|
||||
from sqlalchemy.orm import mapped_column
|
||||
from sqlalchemy.orm.mapper import Mapper
|
||||
from sqlalchemy.orm.session import Session
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
from fittrackee import VERSION, db
|
||||
from fittrackee.users.models import User
|
||||
|
||||
BaseModel: DeclarativeMeta = db.Model
|
||||
|
||||
|
||||
class AppConfig(BaseModel):
|
||||
class AppConfig(db.Model): # type: ignore
|
||||
__tablename__ = 'app_config'
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
max_users = db.Column(db.Integer, default=0, nullable=False)
|
||||
gpx_limit_import = db.Column(db.Integer, default=10, nullable=False)
|
||||
max_single_file_size = db.Column(
|
||||
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
|
||||
max_users = mapped_column(db.Integer, default=0, nullable=False)
|
||||
gpx_limit_import = mapped_column(db.Integer, default=10, nullable=False)
|
||||
max_single_file_size = mapped_column(
|
||||
db.Integer, default=1048576, nullable=False
|
||||
)
|
||||
max_zip_file_size = db.Column(db.Integer, default=10485760, nullable=False)
|
||||
admin_contact = db.Column(db.String(255), nullable=True)
|
||||
privacy_policy_date = db.Column(db.DateTime, nullable=True)
|
||||
privacy_policy = db.Column(db.Text, nullable=True)
|
||||
about = db.Column(db.Text, nullable=True)
|
||||
max_zip_file_size = mapped_column(
|
||||
db.Integer, default=10485760, nullable=False
|
||||
)
|
||||
admin_contact = mapped_column(db.String(255), nullable=True)
|
||||
privacy_policy_date = mapped_column(db.DateTime, nullable=True)
|
||||
privacy_policy = mapped_column(db.Text, nullable=True)
|
||||
about = mapped_column(db.Text, nullable=True)
|
||||
|
||||
@property
|
||||
def is_registration_enabled(self) -> bool:
|
||||
@ -36,8 +37,9 @@ class AppConfig(BaseModel):
|
||||
except exc.ProgrammingError as e:
|
||||
# workaround for user model related migrations
|
||||
if 'psycopg2.errors.UndefinedColumn' in str(e):
|
||||
result = db.engine.execute("SELECT COUNT(*) FROM users;")
|
||||
nb_users = result.fetchone()[0]
|
||||
query = db.session.execute(text("SELECT COUNT(*) FROM users;"))
|
||||
result = query.fetchone()
|
||||
nb_users = result[0] if result else 0
|
||||
else:
|
||||
raise e
|
||||
return self.max_users == 0 or nb_users < self.max_users
|
||||
|
@ -3,6 +3,7 @@ import shutil
|
||||
|
||||
import click
|
||||
from flask_migrate import upgrade
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
from fittrackee import db
|
||||
from fittrackee.cli.app import app
|
||||
@ -36,7 +37,7 @@ def drop_db() -> None:
|
||||
err=True,
|
||||
)
|
||||
return
|
||||
db.engine.execute("DROP TABLE IF EXISTS alembic_version;")
|
||||
db.session.execute(text("DROP TABLE IF EXISTS alembic_version;"))
|
||||
db.drop_all()
|
||||
db.session.commit()
|
||||
click.echo('Database dropped.')
|
||||
|
@ -4,6 +4,6 @@ from fittrackee.utils import clean
|
||||
def clean_tokens(days: int) -> int:
|
||||
sql = """
|
||||
DELETE FROM oauth2_token
|
||||
WHERE oauth2_token.issued_at + oauth2_token.expires_in < %(limit)s;
|
||||
WHERE oauth2_token.issued_at + oauth2_token.expires_in < :limit;
|
||||
"""
|
||||
return clean(sql, days)
|
||||
|
@ -8,23 +8,22 @@ from authlib.integrations.sqla_oauth2 import (
|
||||
)
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.event import listens_for
|
||||
from sqlalchemy.ext.declarative import DeclarativeMeta
|
||||
from sqlalchemy.orm import mapped_column, relationship
|
||||
from sqlalchemy.orm.mapper import Mapper
|
||||
from sqlalchemy.orm.session import Session
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
from fittrackee import db
|
||||
|
||||
BaseModel: DeclarativeMeta = db.Model
|
||||
|
||||
|
||||
class OAuth2Client(BaseModel, OAuth2ClientMixin):
|
||||
class OAuth2Client(OAuth2ClientMixin, db.Model): # type: ignore
|
||||
__tablename__ = 'oauth2_client'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(
|
||||
id = mapped_column(db.Integer, primary_key=True)
|
||||
user_id = mapped_column(
|
||||
db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), index=True
|
||||
)
|
||||
user = db.relationship('User')
|
||||
user = relationship('User')
|
||||
|
||||
def serialize(self, with_secret: bool = False) -> Dict:
|
||||
client = {
|
||||
@ -63,7 +62,9 @@ def on_old_oauth2_delete(
|
||||
).delete(synchronize_session=False)
|
||||
|
||||
|
||||
class OAuth2AuthorizationCode(BaseModel, OAuth2AuthorizationCodeMixin):
|
||||
class OAuth2AuthorizationCode(
|
||||
OAuth2AuthorizationCodeMixin, db.Model # type: ignore
|
||||
):
|
||||
__tablename__ = 'oauth2_code'
|
||||
__table_args__ = (
|
||||
db.Index(
|
||||
@ -72,21 +73,21 @@ class OAuth2AuthorizationCode(BaseModel, OAuth2AuthorizationCodeMixin):
|
||||
),
|
||||
)
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(
|
||||
id = mapped_column(db.Integer, primary_key=True)
|
||||
user_id = mapped_column(
|
||||
db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), index=True
|
||||
)
|
||||
user = db.relationship('User')
|
||||
user = relationship('User')
|
||||
|
||||
|
||||
class OAuth2Token(BaseModel, OAuth2TokenMixin):
|
||||
class OAuth2Token(OAuth2TokenMixin, db.Model): # type: ignore
|
||||
__tablename__ = 'oauth2_token'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(
|
||||
id = mapped_column(db.Integer, primary_key=True)
|
||||
user_id = mapped_column(
|
||||
db.Integer, db.ForeignKey('users.id', ondelete='CASCADE'), index=True
|
||||
)
|
||||
user = db.relationship('User')
|
||||
user = relationship('User')
|
||||
|
||||
def is_refresh_token_active(self) -> bool:
|
||||
if self.is_revoked():
|
||||
@ -98,10 +99,10 @@ class OAuth2Token(BaseModel, OAuth2TokenMixin):
|
||||
def revoke_client_tokens(cls, client_id: str) -> None:
|
||||
sql = """
|
||||
UPDATE oauth2_token
|
||||
SET access_token_revoked_at = %(revoked_at)s
|
||||
WHERE client_id = %(client_id)s;
|
||||
SET access_token_revoked_at = :revoked_at
|
||||
WHERE client_id = :client_id;
|
||||
"""
|
||||
db.engine.execute(
|
||||
sql, {'client_id': client_id, 'revoked_at': int(time.time())}
|
||||
db.session.execute(
|
||||
text(sql), {'client_id': client_id, 'revoked_at': int(time.time())}
|
||||
)
|
||||
db.session.commit()
|
||||
|
@ -23,6 +23,29 @@ class TestUserModel:
|
||||
) -> None:
|
||||
assert '<User \'test\'>' == str(user_1)
|
||||
|
||||
def test_it_returns_workout_count_when_no_workouts(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
sport_1_cycling: Sport,
|
||||
workout_cycling_user_2: Workout,
|
||||
) -> None:
|
||||
assert user_1.workouts_count == 0
|
||||
|
||||
def test_it_returns_workout_count_when_user_has_workout(
|
||||
self,
|
||||
app: Flask,
|
||||
user_1: User,
|
||||
user_2: User,
|
||||
sport_1_cycling: Sport,
|
||||
sport_2_running: Sport,
|
||||
workout_cycling_user_1: Workout,
|
||||
workout_running_user_1: Workout,
|
||||
workout_cycling_user_2: Workout,
|
||||
) -> None:
|
||||
assert user_1.workouts_count == 2
|
||||
|
||||
|
||||
class UserModelAssertMixin:
|
||||
@staticmethod
|
||||
|
@ -7,8 +7,8 @@ from flask import current_app
|
||||
from sqlalchemy import func
|
||||
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
|
||||
from sqlalchemy.sql.expression import select
|
||||
@ -21,48 +21,48 @@ from .exceptions import UserNotFoundException
|
||||
from .roles import UserRole
|
||||
from .utils.token import decode_user_token, get_user_token
|
||||
|
||||
BaseModel: DeclarativeMeta = db.Model
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
class User(db.Model): # type: ignore
|
||||
__tablename__ = 'users'
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
username = db.Column(db.String(255), unique=True, nullable=False)
|
||||
email = db.Column(db.String(255), unique=True, nullable=False)
|
||||
password = db.Column(db.String(255), nullable=False)
|
||||
created_at = db.Column(db.DateTime, nullable=False)
|
||||
admin = db.Column(db.Boolean, default=False, nullable=False)
|
||||
first_name = db.Column(db.String(80), nullable=True)
|
||||
last_name = db.Column(db.String(80), nullable=True)
|
||||
birth_date = db.Column(db.DateTime, nullable=True)
|
||||
location = db.Column(db.String(80), nullable=True)
|
||||
bio = db.Column(db.String(200), nullable=True)
|
||||
picture = db.Column(db.String(255), nullable=True)
|
||||
timezone = db.Column(db.String(50), nullable=True)
|
||||
date_format = db.Column(db.String(50), nullable=True)
|
||||
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
|
||||
username = mapped_column(db.String(255), unique=True, nullable=False)
|
||||
email = mapped_column(db.String(255), unique=True, nullable=False)
|
||||
password = mapped_column(db.String(255), nullable=False)
|
||||
created_at = mapped_column(db.DateTime, nullable=False)
|
||||
admin = mapped_column(db.Boolean, default=False, nullable=False)
|
||||
first_name = mapped_column(db.String(80), nullable=True)
|
||||
last_name = mapped_column(db.String(80), nullable=True)
|
||||
birth_date = mapped_column(db.DateTime, nullable=True)
|
||||
location = mapped_column(db.String(80), nullable=True)
|
||||
bio = mapped_column(db.String(200), nullable=True)
|
||||
picture = mapped_column(db.String(255), nullable=True)
|
||||
timezone = mapped_column(db.String(50), nullable=True)
|
||||
date_format = mapped_column(db.String(50), nullable=True)
|
||||
# does the week start Monday?
|
||||
weekm = db.Column(db.Boolean, default=False, nullable=False)
|
||||
workouts = db.relationship(
|
||||
weekm = mapped_column(db.Boolean, default=False, nullable=False)
|
||||
workouts = relationship(
|
||||
'Workout',
|
||||
lazy=True,
|
||||
backref=db.backref('user', lazy='joined', single_parent=True),
|
||||
)
|
||||
records = db.relationship(
|
||||
records = relationship(
|
||||
'Record',
|
||||
lazy=True,
|
||||
backref=db.backref('user', lazy='joined', single_parent=True),
|
||||
)
|
||||
language = db.Column(db.String(50), nullable=True)
|
||||
imperial_units = db.Column(db.Boolean, default=False, nullable=False)
|
||||
is_active = db.Column(db.Boolean, default=False, nullable=False)
|
||||
email_to_confirm = db.Column(db.String(255), nullable=True)
|
||||
confirmation_token = db.Column(db.String(255), nullable=True)
|
||||
display_ascent = db.Column(db.Boolean, default=True, nullable=False)
|
||||
accepted_policy_date = db.Column(db.DateTime, nullable=True)
|
||||
start_elevation_at_zero = db.Column(
|
||||
language = mapped_column(db.String(50), nullable=True)
|
||||
imperial_units = mapped_column(db.Boolean, default=False, nullable=False)
|
||||
is_active = mapped_column(db.Boolean, default=False, nullable=False)
|
||||
email_to_confirm = mapped_column(db.String(255), nullable=True)
|
||||
confirmation_token = mapped_column(db.String(255), nullable=True)
|
||||
display_ascent = mapped_column(db.Boolean, default=True, nullable=False)
|
||||
accepted_policy_date = mapped_column(db.DateTime, nullable=True)
|
||||
start_elevation_at_zero = mapped_column(
|
||||
db.Boolean, default=True, nullable=False
|
||||
)
|
||||
use_raw_gpx_speed = db.Column(db.Boolean, default=False, nullable=False)
|
||||
use_raw_gpx_speed = mapped_column(
|
||||
db.Boolean, default=False, nullable=False
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<User {self.username!r}>'
|
||||
@ -138,7 +138,7 @@ class User(BaseModel):
|
||||
@workouts_count.expression # type: ignore
|
||||
def workouts_count(self) -> int:
|
||||
return (
|
||||
select([func.count(Workout.id)])
|
||||
select(func.count(Workout.id))
|
||||
.where(Workout.user_id == self.id)
|
||||
.label('workouts_count')
|
||||
)
|
||||
@ -225,22 +225,24 @@ class User(BaseModel):
|
||||
return serialized_user
|
||||
|
||||
|
||||
class UserSportPreference(BaseModel):
|
||||
class UserSportPreference(db.Model): # type: ignore
|
||||
__tablename__ = 'users_sports_preferences'
|
||||
|
||||
user_id = db.Column(
|
||||
user_id = mapped_column(
|
||||
db.Integer,
|
||||
db.ForeignKey('users.id'),
|
||||
primary_key=True,
|
||||
)
|
||||
sport_id = db.Column(
|
||||
sport_id = mapped_column(
|
||||
db.Integer,
|
||||
db.ForeignKey('sports.id'),
|
||||
primary_key=True,
|
||||
)
|
||||
color = db.Column(db.String(50), nullable=True)
|
||||
is_active = db.Column(db.Boolean, default=True, nullable=False)
|
||||
stopped_speed_threshold = db.Column(db.Float, default=1.0, nullable=False)
|
||||
color = mapped_column(db.String(50), nullable=True)
|
||||
is_active = mapped_column(db.Boolean, default=True, nullable=False)
|
||||
stopped_speed_threshold = mapped_column(
|
||||
db.Float, default=1.0, nullable=False
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@ -263,13 +265,13 @@ class UserSportPreference(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
class BlacklistedToken(BaseModel):
|
||||
class BlacklistedToken(db.Model): # type: ignore
|
||||
__tablename__ = 'blacklisted_tokens'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
token = db.Column(db.String(500), unique=True, nullable=False)
|
||||
expired_at = db.Column(db.Integer, nullable=False)
|
||||
blacklisted_on = db.Column(db.DateTime, nullable=False)
|
||||
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
|
||||
token = mapped_column(db.String(500), unique=True, nullable=False)
|
||||
expired_at = mapped_column(db.Integer, nullable=False)
|
||||
blacklisted_on = mapped_column(db.DateTime, nullable=False)
|
||||
|
||||
def __init__(
|
||||
self, token: str, blacklisted_on: Optional[datetime] = None
|
||||
@ -290,25 +292,25 @@ class BlacklistedToken(BaseModel):
|
||||
return cls.query.filter_by(token=str(auth_token)).first() is not None
|
||||
|
||||
|
||||
class UserDataExport(BaseModel):
|
||||
class UserDataExport(db.Model): # type: ignore
|
||||
__tablename__ = 'users_data_export'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
user_id = db.Column(
|
||||
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
|
||||
user_id = mapped_column(
|
||||
db.Integer,
|
||||
db.ForeignKey('users.id', ondelete='CASCADE'),
|
||||
index=True,
|
||||
unique=True,
|
||||
)
|
||||
created_at = db.Column(
|
||||
created_at = mapped_column(
|
||||
db.DateTime, nullable=False, default=datetime.utcnow
|
||||
)
|
||||
updated_at = db.Column(
|
||||
updated_at = mapped_column(
|
||||
db.DateTime, nullable=True, onupdate=datetime.utcnow
|
||||
)
|
||||
completed = db.Column(db.Boolean, nullable=False, default=False)
|
||||
file_name = db.Column(db.String(100), nullable=True)
|
||||
file_size = db.Column(db.Integer, nullable=True)
|
||||
completed = mapped_column(db.Boolean, nullable=False, default=False)
|
||||
file_name = mapped_column(db.String(100), nullable=True)
|
||||
file_size = mapped_column(db.Integer, nullable=True)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -55,6 +55,6 @@ def clean_blacklisted_tokens(days: int) -> int:
|
||||
"""
|
||||
sql = """
|
||||
DELETE FROM blacklisted_tokens
|
||||
WHERE blacklisted_tokens.expired_at < %(limit)s;
|
||||
WHERE blacklisted_tokens.expired_at < :limit;
|
||||
"""
|
||||
return clean(sql, days)
|
||||
|
@ -3,6 +3,7 @@ from datetime import timedelta
|
||||
from typing import Optional
|
||||
|
||||
import humanize
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
from fittrackee import db
|
||||
|
||||
@ -26,5 +27,6 @@ def get_readable_duration(duration: int, locale: Optional[str] = None) -> str:
|
||||
|
||||
def clean(sql: str, days: int) -> int:
|
||||
limit = int(time.time()) - (days * 86400)
|
||||
result = db.engine.execute(sql, {'limit': limit})
|
||||
return result.rowcount
|
||||
result = db.session.execute(text(sql), {'limit': limit})
|
||||
# DELETE statement returns CursorResult with the rowcount attribute
|
||||
return result.rowcount # type: ignore
|
||||
|
@ -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,7 +18,6 @@ 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'
|
||||
@ -67,18 +66,20 @@ def update_records(
|
||||
)
|
||||
|
||||
|
||||
class Sport(BaseModel):
|
||||
class Sport(db.Model): # type: ignore
|
||||
__tablename__ = 'sports'
|
||||
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(
|
||||
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(
|
||||
'Workout',
|
||||
lazy=True,
|
||||
backref=db.backref('sport', lazy='joined', single_parent=True),
|
||||
)
|
||||
records = db.relationship(
|
||||
records = relationship(
|
||||
'Record',
|
||||
lazy=True,
|
||||
backref=db.backref('sport', lazy='joined', single_parent=True),
|
||||
@ -120,51 +121,49 @@ class Sport(BaseModel):
|
||||
return serialized_sport
|
||||
|
||||
|
||||
class Workout(BaseModel):
|
||||
class Workout(db.Model): # type: ignore
|
||||
__tablename__ = 'workouts'
|
||||
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
||||
uuid = db.Column(
|
||||
id = mapped_column(db.Integer, primary_key=True, autoincrement=True)
|
||||
uuid = mapped_column(
|
||||
postgresql.UUID(as_uuid=True),
|
||||
default=uuid4,
|
||||
unique=True,
|
||||
nullable=False,
|
||||
)
|
||||
user_id = db.Column(
|
||||
user_id = mapped_column(
|
||||
db.Integer, db.ForeignKey('users.id'), index=True, nullable=False
|
||||
)
|
||||
sport_id = db.Column(
|
||||
sport_id = mapped_column(
|
||||
db.Integer, db.ForeignKey('sports.id'), index=True, nullable=False
|
||||
)
|
||||
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(
|
||||
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(
|
||||
'WorkoutSegment',
|
||||
lazy=True,
|
||||
cascade='all, delete',
|
||||
backref=db.backref('workout', lazy='joined', single_parent=True),
|
||||
)
|
||||
records = db.relationship(
|
||||
records = relationship(
|
||||
'Record',
|
||||
lazy=True,
|
||||
cascade='all, delete',
|
||||
@ -178,9 +177,9 @@ class Workout(BaseModel):
|
||||
self,
|
||||
user_id: int,
|
||||
sport_id: int,
|
||||
workout_date: datetime.datetime,
|
||||
workout_date: datetime,
|
||||
distance: float,
|
||||
duration: datetime.timedelta,
|
||||
duration: timedelta,
|
||||
) -> None:
|
||||
self.user_id = user_id
|
||||
self.sport_id = sport_id
|
||||
@ -242,11 +241,10 @@ class Workout(BaseModel):
|
||||
Workout.sport_id == sport_id if sport_id else True,
|
||||
Workout.workout_date <= self.workout_date,
|
||||
Workout.workout_date
|
||||
>= datetime.datetime.strptime(date_from, '%Y-%m-%d')
|
||||
>= datetime.strptime(date_from, '%Y-%m-%d')
|
||||
if date_from
|
||||
else True,
|
||||
Workout.workout_date
|
||||
<= datetime.datetime.strptime(date_to, '%Y-%m-%d')
|
||||
Workout.workout_date <= datetime.strptime(date_to, '%Y-%m-%d')
|
||||
if date_to
|
||||
else True,
|
||||
Workout.distance >= float(distance_from)
|
||||
@ -284,11 +282,10 @@ class Workout(BaseModel):
|
||||
Workout.sport_id == sport_id if sport_id else True,
|
||||
Workout.workout_date >= self.workout_date,
|
||||
Workout.workout_date
|
||||
>= datetime.datetime.strptime(date_from, '%Y-%m-%d')
|
||||
>= datetime.strptime(date_from, '%Y-%m-%d')
|
||||
if date_from
|
||||
else True,
|
||||
Workout.workout_date
|
||||
<= datetime.datetime.strptime(date_to, '%Y-%m-%d')
|
||||
Workout.workout_date <= datetime.strptime(date_to, '%Y-%m-%d')
|
||||
if date_to
|
||||
else True,
|
||||
Workout.distance >= float(distance_from)
|
||||
@ -380,9 +377,10 @@ def on_workout_insert(
|
||||
def on_workout_update(
|
||||
mapper: Mapper, connection: Connection, workout: Workout
|
||||
) -> None:
|
||||
if object_session(workout).is_modified(
|
||||
workout_session = object_session(workout)
|
||||
if workout_session is not None and workout_session.is_modified(
|
||||
workout, include_collections=True
|
||||
): # noqa
|
||||
):
|
||||
|
||||
@listens_for(db.Session, 'after_flush', once=True)
|
||||
def receive_after_flush(session: Session, context: Any) -> None:
|
||||
@ -413,23 +411,23 @@ def on_workout_delete(
|
||||
appLog.error('gpx file not found when deleting workout')
|
||||
|
||||
|
||||
class WorkoutSegment(BaseModel):
|
||||
class WorkoutSegment(db.Model): # type: ignore
|
||||
__tablename__ = 'workout_segments'
|
||||
workout_id = db.Column(
|
||||
workout_id = mapped_column(
|
||||
db.Integer, db.ForeignKey('workouts.id'), primary_key=True
|
||||
)
|
||||
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
|
||||
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
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
@ -461,25 +459,27 @@ class WorkoutSegment(BaseModel):
|
||||
}
|
||||
|
||||
|
||||
class Record(BaseModel):
|
||||
class Record(db.Model): # type: ignore
|
||||
__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(
|
||||
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(
|
||||
db.Integer, db.ForeignKey('sports.id'), nullable=False
|
||||
)
|
||||
workout_id = db.Column(
|
||||
workout_id = mapped_column(
|
||||
db.Integer, db.ForeignKey('workouts.id'), nullable=False
|
||||
)
|
||||
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)
|
||||
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)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
@ -497,11 +497,11 @@ class Record(BaseModel):
|
||||
self.workout_date = workout.workout_date
|
||||
|
||||
@hybrid_property
|
||||
def value(self) -> Optional[Union[datetime.timedelta, float]]:
|
||||
def value(self) -> Optional[Union[timedelta, float]]:
|
||||
if self._value is None:
|
||||
return None
|
||||
if self.record_type == 'LD':
|
||||
return datetime.timedelta(seconds=self._value)
|
||||
return timedelta(seconds=self._value)
|
||||
elif self.record_type in ['AS', 'MS']:
|
||||
return float(self._value / 100)
|
||||
else: # 'FD' or 'HA'
|
||||
|
Reference in New Issue
Block a user