Add options to choose type of bike, and also persist distance, power, calories, speed to db

This commit is contained in:
Peter Stockings
2023-03-14 10:19:37 +11:00
parent f1c89e967a
commit faf9b82137
5 changed files with 141 additions and 38 deletions

53
app.py
View File

@@ -28,7 +28,17 @@ class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=False)
bike_id = db.Column(db.Integer, db.ForeignKey(
'bikes.id', ondelete='CASCADE'), nullable=False)
workouts = db.relationship('Workout', backref='user', lazy=True)
bike = db.relationship('Bike', backref='user', lazy=True)
class Bike(db.Model):
__tablename__ = 'bikes'
id = db.Column(db.Integer, primary_key=True)
display_name = db.Column(db.String(255), nullable=False)
code_name = db.Column(db.String(255), nullable=False)
class Workout(db.Model):
@@ -48,10 +58,13 @@ class CadenceReading(db.Model):
'workouts.id', ondelete='CASCADE'), nullable=False)
created_at = db.Column(db.DateTime, nullable=False)
rpm = db.Column(db.Integer, nullable=False)
distance = db.Column(db.Numeric, nullable=False)
speed = db.Column(db.Numeric, nullable=False)
calories = db.Column(db.Numeric, nullable=False)
power = db.Column(db.Integer, nullable=False)
@app.route('/', methods=['GET'])
def get_workouts():
def render_users_and_workouts():
users = User.query.all()
users_data = []
for user in users:
@@ -59,11 +72,17 @@ def get_workouts():
user_data = {
'id': user.id,
'name': user.name,
'bike_id': user.bike_id,
'workouts_count': len(workouts),
'workouts': workouts
}
users_data.append(user_data)
return render_template('users_and_workouts.html', users=users_data)
return render_template('users_and_workouts.html', users=users_data, bikes=Bike.query.all())
@app.route('/', methods=['GET'])
def get_workouts():
return render_users_and_workouts()
@app.route("/user/<int:user_id>/new_workout")
@@ -85,14 +104,14 @@ def users():
# create a new user
data = request.form
name = data['name']
bike_id = data['bike_id']
# create a new user and add it to the database
new_user = User(name=name)
new_user = User(name=name, bike_id=bike_id)
db.session.add(new_user)
db.session.commit()
users = User.query.all()
return render_template('users.html', users=users)
return render_users_and_workouts()
@app.route('/user/<int:user_id>', methods=['DELETE'])
@@ -101,8 +120,7 @@ def delete_user(user_id):
if user:
db.session.delete(user)
db.session.commit()
users = User.query.all()
return render_template('users.html', users=users)
return render_users_and_workouts()
else:
return jsonify({'error': 'User not found.'}), 404
@@ -115,7 +133,7 @@ def workouts(user_id):
elif request.method == 'POST':
data = request.json
rpm_readings = data['workout']
data = data['workout']
# create a new workout
workout = Workout(user_id=user_id)
@@ -123,9 +141,9 @@ def workouts(user_id):
db.session.commit()
# add cadence readings to the workout
for reading in rpm_readings:
for d in data:
cadence_reading = CadenceReading(
workout_id=workout.id, created_at=reading['timestamp'], rpm=reading['rpm'])
workout_id=workout.id, created_at=d['timestamp'], rpm=d['rpm'], distance=d['distance'], speed=d['speed'], calories=d['calories'], power=d['power'])
db.session.add(cadence_reading)
db.session.commit()
@@ -186,6 +204,19 @@ def workouts_for_user(user_id):
return render_template('workouts_list.html', workouts=workouts_data)
@app.route('/user/<int:user_id>/bike', methods=['GET'])
def update_users_bike(user_id):
bike_id = request.args.get('bike_id')
user = User.query.get(user_id)
bike = Bike.query.get(bike_id)
if user and bike:
user.bike_id = bike_id
db.session.commit()
return render_users_and_workouts()
else:
return jsonify({'error': 'User or bike not found.'}), 404
def get_workouts_for_user(user_id):
workouts_data = []
workouts = Workout.query.filter_by(user_id=user_id).order_by(

View File

@@ -1,5 +1,9 @@
CREATE TABLE
users (id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL);
users (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
bike_id INTEGER NOT NULL REFERENCES bikes (id) ON DELETE CASCADE DEFAULT '1',
);
CREATE TABLE
workouts (
@@ -14,5 +18,26 @@ CREATE TABLE
workout_id INTEGER NOT NULL REFERENCES workouts (id) ON DELETE CASCADE,
created_at TIMESTAMP NOT NULL,
rpm INTEGER NOT NULL,
distance NUMERIC NOT NULL DEFAULT '0',
power NUMERIC NOT NULL DEFAULT '0',
speed NUMERIC NOT NULL DEFAULT '0',
calories NUMERIC NOT NULL DEFAULT '0',
CONSTRAINT unique_cadence_reading_per_workout_time UNIQUE (workout_id, created_at)
);
CREATE TABLE
bikes (
id SERIAL PRIMARY KEY,
display_name VARCHAR(255) NOT NULL,
code_name VARCHAR(255) NOT NULL
);
INSERT INTO
bikes (code_name, display_name)
VALUES
('ad6', 'AirDyne6'),
('aab', 'Assault Air Bike'),
('echo', 'Echo Bike'),
('bikeergDamper10', 'BikeErg D10'),
('bikeergDamper5', 'BikeErg D5'),
('bikeergDamper1', 'BikeErg D1');

View File

@@ -23,7 +23,7 @@
</svg>
<div>
{{ user.name }}
{{ user.name }} - {{ user.bike.display_name }}
</div>
</div>
</a>
@@ -324,8 +324,8 @@
// Add the new data point to the data array
data.push(newData);
let watts = generators.aab.rpm.power(rpm)
let speed = generators.aab.rpm.speed(rpm)
let watts = generators['{{ user.bike.code_name }}'].rpm.power(rpm)
let speed = generators['{{ user.bike.code_name }}'].rpm.speed(rpm)
if (previousReadingTime) {
distance += calculateDistance(previousReadingTime, new Date(), speed)
calories += calculateCalories(previousReadingTime, new Date(), watts)

View File

@@ -2,7 +2,8 @@
<thead>
<tr class="bg-gray-200 text-gray-600 uppercase text-sm leading-normal">
<th class="py-3 px-6 text-left">Name</th>
<th class="py-3 px-6 text-left">Workouts</th>
<th class="py-3 px-6 text-left">Bike</th>
<th class="py-3 px-6 text-left"></th>
</tr>
</thead>
<tbody>
@@ -19,19 +20,39 @@
</path>
</svg>
<div>
{{ u.name }}
<div class="flex">
<p>{{ u.name }}</p>
<p class="ml-1 prose-sm">({{ u.workouts_count }})</p>
</div>
</div>
</a>
</td>
<td class="py-4 px-6 border-b border-gray-200 flex inline">
<td class="py-2 px-6 border-b border-gray-200">
<div class="relative">
<select
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
id="grid-state" name="bike_id" required
hx-get="{{ url_for('update_users_bike', user_id=u.id) }}" hx-target="#users-container">
{% for b in bikes %}
<option value="{{ b.id }}" {% if u.bike_id==b.id %} selected {% endif %}>{{ b.display_name }}
</option>
{% endfor %}
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" />
</svg>
</div>
</div>
</td>
<td class="py-5 px-6 border-b border-gray-200 flex inline">
<div class="flex justify-between w-full">
<div>{{ u.workouts_count }}</div>
<div></div>
<div class="flex">
<div hx-delete="{{ url_for('delete_user', user_id=u.id) }}"
hx-confirm="Are you sure you wish to delete your account {{ u.name }}?"
hx-target="#users-container" class="pr-2">
hx-target="#users-container" class="cursor-pointer pr-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round"
@@ -39,7 +60,8 @@
</svg>
</div>
<div hx-get="{{ url_for('workouts', user_id=u.id) }}" hx-target="#container">
<div hx-get="{{ url_for('workouts', user_id=u.id) }}" hx-target="#container"
class="cursor-pointer">
<svg class="w-6 h-6 dark:text-white" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
@@ -55,16 +77,41 @@
</tbody>
</table>
<form class="w-full" hx-post="{{ url_for('users') }}" hx-target="#users-container">
<div class="flex items-center border-b border-teal-500 py-2">
<input
class="appearance-none bg-transparent border-none w-full text-gray-700 mr-3 py-1 px-2 leading-tight focus:outline-none"
type="text" placeholder="Jane Doe" aria-label="Full name" name="name" required>
<button
class="flex-shrink-0 bg-teal-500 hover:bg-teal-700 border-teal-500 hover:border-teal-700 text-sm border-4 text-white py-1 px-2 rounded mr-5"
type="submit">
Add
</button>
<form class="w-full mt-4" hx-post="{{ url_for('users') }}" hx-target="#users-container">
<div class="flex flex-wrap mb-2">
<div class="w-full md:w-1/2 px-3 mb-6 md:mb-0">
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-city">
Name
</label>
<input
class="appearance-none block w-full bg-gray-200 text-gray-700 border border-gray-200 rounded py-3 px-4 leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
id="grid-city" type="text" placeholder="Full name" name="name" required>
</div>
<div class="w-full md:w-1/2 px-3 mb-4">
<label class="block uppercase tracking-wide text-gray-700 text-xs font-bold mb-2" for="grid-state">
Bike
</label>
<div class="relative">
<select
class="block appearance-none w-full bg-gray-200 border border-gray-200 text-gray-700 py-3 px-4 pr-8 rounded leading-tight focus:outline-none focus:bg-white focus:border-gray-500"
id="grid-state" name="bike_id" required>
{% for b in bikes %}
<option value="{{ b.id }}">{{ b.display_name }}</option>
{% endfor %}
</select>
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" />
</svg>
</div>
</div>
</div>
<button
class="shadow bg-purple-500 hover:bg-purple-400 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded ml-3 w-full md:w-auto"
type="submit">
Add User
</button>
</div>
</form>

View File

@@ -33,19 +33,19 @@
</div>
</div>
</nav>
<div class="container mx-auto mt-8">
<div class="flex flex-col sm:flex-row justify-between">
<div class="w-full sm:w-1/2 px-4 mb-8">
<div class="max-w-4xl mx-auto mt-8">
<div class="flex flex-col justify-between">
<div class="w-full px-4 mb-8">
<div class="bg-white shadow-md rounded-lg overflow-hidden" id="users-container">
{% with users=users %}
{% with users=users, bikes=bikes %}
{% include 'users.html' %}
{% endwith %}
</div>
</div>
<div class="w-full sm:w-1/2 px-4 mb-8" id="container">
<div class="w-full px-4 mb-8" id="container">
</div>
</div>
</div>