Ejemplo n.º 1
0
class GoalCategory(SurrogatePK, db.Model):
    __tablename__ = 'training_goal_categories'
    id = Column(db.Integer, primary_key=True, index=True)
    name = Column(db.String(32), nullable=False)
    unit = Column(db.String(16), nullable=True)

    workout_category_id = reference_col('workout_categories', nullable=True)
    workout_category = relationship('WorkoutCategory')

    __table_args__ = (UniqueConstraint('name',
                                       'workout_category_id',
                                       name='_name_workout_cat_id_uc'), )

    def __init__(self, name, unit=None, workout_category_id=None):
        db.Model.__init__(self,
                          name=name,
                          unit=unit,
                          workout_category_id=workout_category_id)

    @property
    def workout_category_name(self):
        if self.workout_category is not None:
            return self.workout_category.name
        else:
            return ""

    def __repr__(self):
        return '<GoalCategory({name!r},{wcid!r})>'.format(
            name=self.name, wcid=self.workout_category_name)
Ejemplo n.º 2
0
class ProfileWeightHistory(SurrogatePK, Model):
    __tablename__ = 'profile_weight_history'

    id = Column(db.Integer, primary_key=True, index=True)
    profile_id = reference_col('user_profiles')
    weight = Column(db.Float())
    created_at = Column(db.DateTime,
                        nullable=False,
                        default=dt.datetime.utcnow)
Ejemplo n.º 3
0
class PolarUser(SurrogatePK, db.Model):
	__tablename__ = 'polar_users'
	profile_id = reference_col('user_profiles', unique=True, nullable=False, index=True)
	member_id = Column(db.String(24), unique=True, nullable=False)
	polar_user_id = Column(db.Integer, nullable=False)
	state = Column(db.String(16), unique=False, nullable=True)
	access_token = Column(db.String(64), nullable=True)
	access_token_expires = Column(db.DateTime, nullable=True)
	updated_at = Column(db.DateTime, nullable=True)

	def __init__(self, profile_id, username):
		member_id = 'R4IT_{0}'.format(username)
		db.Model.__init__(self, profile_id=profile_id, member_id=member_id, polar_user_id=0)

	@classmethod
	def find_by_member_id(cls, id):
		return cls.query.filter_by(member_id=id).first()

	@classmethod
	def find_by_polar_user_id(cls, id):
		return cls.query.filter_by(polar_user_id=id).first()

	@classmethod
	def find_by_state_code(cls, code):
		polar_users = cls.query.filter_by(state=code).all()

		if (polar_users is None or len(polar_users) != 1):
			return None

		return polar_users[0]
	
	@property
	def auth_url(self):
		if self.has_valid_access_token() or self.state is None:
			return ''
		return POLAR_AUTHORIZATION_URL.format(current_app.config["POLAR_API_CLIENT_ID"], self.state)

	def generate_state_code(self):
		self.state = ''.join(choice(ascii_letters + digits) for _ in range(15))
	
	def is_registered(self):
		return self.polar_user_id > 0 and self.access_token is not None and len(self.access_token) > 0

	def has_valid_access_token(self):
		if self.is_registered():
			now = dt.datetime.utcnow()
			return self.access_token_expires > now
		return False

	def __repr__(self):
		return '<PolarUser({id!r}:{member!r})>'.format(
			id=self.profile_id,
			member=self.member_id)
Ejemplo n.º 4
0
class Goal(SurrogatePK, db.Model):
    __tablename__ = 'training_goals'
    start_at = Column(db.DateTime, nullable=False)
    end_at = Column(db.DateTime, nullable=False)
    start_value = Column(db.Float, nullable=False)
    current_value = Column(db.Float, nullable=False)
    target_value = Column(db.Float, nullable=False)

    category_id = reference_col('training_goal_categories', nullable=False)
    category = relationship('GoalCategory')
    profile_id = reference_col('user_profiles', nullable=False, index=True)

    def __init__(self,
                 profile_id,
                 category,
                 start_at=dt.datetime.utcnow(),
                 end_at=dt.datetime.utcnow() + dt.timedelta(days=1),
                 start_value=0,
                 target_value=0,
                 current_value=0):
        db.Model.__init__(self,
                          profile_id=profile_id,
                          category=category,
                          start_at=start_at,
                          end_at=end_at,
                          start_value=start_value,
                          current_value=current_value,
                          target_value=target_value)

    def update_from_workout(self, workout):
        '''Add data from workout'''
        if self.profile_id == workout.profile_id:
            if self.start_at <= workout.start_at and self.end_at >= workout.start_at:
                if self.category.workout_category is not None and self.category.workout_category.id == workout.category.id:
                    # we can add data, because workout is relevant
                    if self.category_unit == "km":  #cumulative distance
                        self.current_value += workout.distance / 1000.0
                    elif self.category_unit == "#":  #num.of.workouts
                        self.current_value += 1
                    #elif self.category_unit == "time": # time target (i.e. complete faster than xyz, TODO: Tie this to distance records or something)
                    #	if self.current_value > workout.duration:
                    #		self.current_value = workout.duration
                    elif self.category_unit == "m":  #cumulative climb
                        self.current_value += workout.climb

                    self.save()

    def remove_from_workout(self, workout):
        '''Remove data from workout, workout has been deleted or changed'''
        if self.profile_id == workout.profile_id:
            if self.start_at <= workout.start_at and self.end_at >= workout.start_at:
                if self.category.workout_category is not None and self.category.workout_category.id == workout.category.id:
                    # we can remove data, because workout is relevant
                    if self.category_unit == "km":  #cumulative distance
                        self.current_value -= workout.distance / 1000.0
                        if self.current_value < 0:
                            self.current_value = 0
                    elif self.category_unit == "#":  #num.of.workouts
                        self.current_value -= 1
                        if self.current_value < 0:
                            self.current_value = 0
                    #elif self.category_unit == "time": # time target (i.e. complete faster than xyz, TODO: Tie this to distance records or something)
                    #	if self.current_value == workout.duration:
                    #		workout_class = type(workout) # ugly, but import of WorkoutModel gives circular import issue
                    #		goal_workouts = workout_class.get_workouts_for_goal(self)
                    #		first_goal=True
                    #		for goal_workout in goal_workouts:
                    #			if first_goal:
                    #				self.current_value = goal_workout.duration
                    #			else:
                    #				if self.current_value > goal_workout.duration:
                    #					self.current_value = goal_workout.duration
                    elif self.category_unit == "m":  #cumulative climb
                        self.current_value -= workout.climb
                        if self.current_value < 0:
                            self.current_value = 0

                    self.save()

    @property
    def category_name(self):
        return self.category.name

    @property
    def category_unit(self):
        if (self.category.unit is not None):
            return self.category.unit
        return ''

    @property
    def workout_category_name(self):
        return self.category.workout_category_name

    @property
    def duration(
        self
    ):  # returns days as decimal number with four digits after the decimal point
        return (self.end_at - self.start_at).days + round(
            (self.end_at - self.start_at).seconds / 86400, 4)

    def __repr__(self):
        return '<Goal({year!r}-{month!r}-{day!r}:{duration!r})>'.format(
            duration=self.duration,
            year=self.start_at.year,
            month=self.start_at.month,
            day=self.start_at.day)
Ejemplo n.º 5
0
class Profile(SurrogatePK, TimestampedModel):
    __tablename__ = 'user_profiles'

    # id required for primary join
    id = Column(db.Integer, primary_key=True, index=True)
    height = Column(db.Integer, nullable=True)
    weight = Column(db.Float, nullable=True)
    birth_date = Column(db.Date, nullable=True)

    user_id = reference_col('users', unique=True, nullable=False, index=True)
    user = relationship('User', backref=db.backref('profile', uselist=False))
    weights = relationship('ProfileWeightHistory', lazy='dynamic')
    goals = relationship('Goal', lazy='dynamic')
    workouts = relationship('Workout', lazy='dynamic')
    polar = relationship('PolarUser', lazy='dynamic')

    def __init__(self, user, weights=[], **kwargs):
        db.Model.__init__(self, user=user, weights=weights, **kwargs)

    @property
    def username(self):
        return self.user.username

    def get_active_goals(self, timestamp=None):
        if timestamp is None:
            timestamp = dt.datetime.utcnow()
        return self.goals.filter(
            and_(GoalModel.start_at <= timestamp,
                 GoalModel.end_at >= timestamp)).order_by(
                     GoalModel.end_at.asc()).all()

    def get_expired_goals(self, timestamp=None):
        if timestamp is None:
            timestamp = dt.datetime.utcnow()
        return self.goals.filter(GoalModel.end_at <= timestamp).order_by(
            GoalModel.end_at.desc()).all()

    def get_future_goals(self, timestamp=None):
        if timestamp is None:
            timestamp = dt.datetime.utcnow()
        return self.goals.filter(GoalModel.start_at > timestamp).order_by(
            GoalModel.start_at.asc()).all()

    def get_completed_goals(self, timestamp=None):
        if timestamp is None:
            timestamp = dt.datetime.utcnow()
        return self.goals.filter(
            GoalModel.end_at <= timestamp,
            or_(
                and_(GoalModel.start_value < GoalModel.target_value,
                     GoalModel.current_value >= GoalModel.target_value),
                and_(GoalModel.start_value > GoalModel.target_value,
                     GoalModel.current_value <=
                     GoalModel.target_value))).order_by(
                         GoalModel.end_at.desc()).all()

    def get_incompleted_goals(self, timestamp=None):
        if timestamp is None:
            timestamp = dt.datetime.utcnow()
        return self.goals.filter(
            GoalModel.end_at <= timestamp,
            or_(
                and_(GoalModel.start_value < GoalModel.target_value,
                     GoalModel.current_value < GoalModel.target_value),
                and_(GoalModel.start_value > GoalModel.target_value,
                     GoalModel.current_value >
                     GoalModel.target_value))).order_by(
                         GoalModel.end_at.desc()).all()

    def get_goal_by_id(self, goal_id):
        return self.goals.filter(GoalModel.id == goal_id).first()

    def get_workouts(self, limit, offset, category_id=None):
        if category_id is None:
            return self.workouts.order_by(WorkoutModel.start_at.desc()).limit(
                limit).offset(offset).all()
        else:
            return self.workouts.filter(
                WorkoutModel.category_id == category_id).order_by(
                    WorkoutModel.start_at.desc()).limit(limit).offset(
                        offset).all()

    def get_workout_by_id(self, workout_id):
        return self.workouts.filter(WorkoutModel.id == workout_id).first()

    def get_polar_data(self):
        return self.polar.first()

    def set_height(self, height):
        if height is not None and height > 0:
            self.height = height
        else:
            self.height = None

    def set_weight(self, weight):
        if weight is not None and weight > 0.0:
            self.weight = weight
            self._update_weight_history()
            self._update_weight_goals()
        else:
            self.weight = None

    def set_birth_date(self, year, month, day):
        self.birth_date = dt.date(year, month, day)

    def _update_weight_history(self):
        now = dt.datetime.utcnow()
        midnight = dt.datetime(now.year, now.month, now.day, 0, 0, 0)
        next_midnight = midnight + dt.timedelta(days=1)

        try:
            weight_today = self.weights.filter(
                and_(ProfileWeightHistory.created_at >= midnight,
                     ProfileWeightHistory.created_at < next_midnight)).first()
            weight_today.weight = self.weight
            weight_today.save(False)
        except:
            weight_history_record = ProfileWeightHistory(weight=self.weight)
            self.weights.append(weight_history_record)
            weight_history_record.save(False)

    def _update_weight_goals(self):
        try:
            active_goals = self.get_active_goals()
            if active_goals is not None:
                for goal in active_goals:
                    if (goal.category_name
                            == "Weight loss") or (goal.category_unit == 'kg'):
                        goal.current_value = self.weight
                        goal.save(False)
        except:
            pass

    def __repr__(self):
        return '<UserProfile({username!r})>'.format(username=self.username)
Ejemplo n.º 6
0
class Workout(SurrogatePK, db.Model):
	__tablename__ = 'workouts'
	name = Column(db.String(128), nullable=False)
	start_at = Column(db.DateTime, nullable=False)
	distance = Column(db.Integer, nullable=False)
	duration = Column(db.Integer, nullable=False)
	climb = Column(db.Integer, nullable=False)
	resource_path = Column(db.String(255), unique=True, nullable=True)
	edited = Column(db.Boolean, nullable=False)

	category_id = reference_col('workout_categories', nullable=False)
	category = relationship('WorkoutCategory')
	profile_id = reference_col('user_profiles', nullable=False, index=True)

	def __init__(self, profile_id, category, name, start_at, distance, duration, climb, resource_path=None, edited=False):
		db.Model.__init__(self, profile_id=profile_id, category=category, name=name, start_at=start_at, distance=distance, duration=duration, climb=climb, resource_path=resource_path, edited=edited)

	def register_extended_data(self):
		self.extended_track_data = None
		self.extended_split_data = None
		self.extended_summary = None
		# parse file if it exists
		if is_filename_extension_of_type(self.resource_path, "gpx"):
			gpx = GpxParser(self.resource_path)
			if gpx.get_num_of_tracks() > 0:
				self.extended_track_data, self.extended_split_data, self.extended_summary = gpx.get_track_data(1) # we only expect one track per file
		elif is_filename_extension_of_type(self.resource_path, "tcx"):
			tcx = TcxParser(self.resource_path)
			if tcx.get_num_of_tracks() > 0:
				self.extended_track_data, self.extended_split_data, self.extended_summary = tcx.get_track_data()
		elif is_filename_extension_of_type(self.resource_path, "fit"):
			fit = FitParser(self.resource_path)
			if fit.get_num_of_tracks() > 0:
				self.extended_track_data, self.extended_split_data, self.extended_summary = fit.get_track_data()
		if not self.category.supports_gps_data:
			self.extended_track_data = None
			self.extended_split_data = None

	@property
	def resource_file(self):
		if self.resource_path is not None:
			return ntpath.basename(self.resource_path)
		else:
			return None

	@property
	def category_name(self):
		return self.category.name
	
	@property
	def average_speed(self):
		dist_km = self.distance / 1000.0
		dur_h = self.duration / 3600.0
		
		if dur_h > 0.0:
			return round(dist_km / dur_h, 2)
		else:
			return 0.0
	
	@property
	def average_pace(self):
		dist_km = self.distance / 1000.0
		dur_min = self.duration / 60.0

		if dist_km > 0.0:
			avg_pace = dur_min / dist_km
			avg_pace_min = floor(avg_pace)
			avg_pace_sec = int((avg_pace - avg_pace_min) * 60)
			return "{0:02d}:{1:02d}".format(avg_pace_min, avg_pace_sec)
		else:
			return ""

	@classmethod
	def get_workouts_for_goal(cls, goal):
		if goal.category.workout_category is not None:
			goal_workout_id = goal.category.workout_category.id
			return cls.query.filter(and_(
				Workout.category_id==goal_workout_id,
				Workout.profile_id==goal.profile_id,
				Workout.start_at >= goal.start_at,
				Workout.start_at < goal.end_at)
				).order_by(Workout.start_at.asc()).all()
		else:
			return []

	def __repr__(self):
		return '<Workout({name!r},{distance!r}m)>'.format(
			name=self.name,
			distance=self.distance)