From 31db14f904c2bab725ca8a69fef9975c08acbd71 Mon Sep 17 00:00:00 2001 From: harvey Date: Tue, 7 Apr 2026 16:50:22 +0100 Subject: [PATCH 1/3] started working on activities page --- database/db_init.py | 68 +++++++++++++++++++++++++++ main.py | 2 +- routes/activities/activities.py | 34 ++++++++++++++ templates/activities/activities.jinja | 66 ++++++++++++++++++++++++++ 4 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 routes/activities/activities.py create mode 100644 templates/activities/activities.jinja diff --git a/database/db_init.py b/database/db_init.py index 1764976..6ef635e 100644 --- a/database/db_init.py +++ b/database/db_init.py @@ -294,9 +294,77 @@ def init_programs_database(): """) print("Programs database tables initialised.") +def init_activities_database(): + activities_list = [ + { + "title": "Build a Bird Feeder", + "description": "Create a bird feeder using recycled materials.", + "species": "Bird Conservation", + "due_date": "3 days", + "difficulty": "Easy" + }, + { + "title": "Plant a Tree", + "description": "Plant a tree in your garden or local area to help restore natural habitats and support wildlife.", + "species": "Habitat Restoration", + "due_date": "5 days", + "difficulty": "Medium" + }, + { + "title": "Organise a Conservation Awareness Campaign", + "description": "Plan and deliver a campaign to raise awareness about endangered species.", + "species": "Education & Conservation", + "due_date": "2 weeks", + "difficulty": "Hard" + } + ] + + with get_db() as conn: + # Create activities table if it doesn't exist + conn.execute(""" + CREATE TABLE IF NOT EXISTS activities ( + activity_id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + description TEXT, + species TEXT, + due_date TEXT, + difficulty TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + print("Activities table created/found.") + + # Insert activities if not already in table + for activity in activities_list: + exists = conn.execute( + "SELECT activity_id FROM activities WHERE title = ?", + (activity["title"],) + ).fetchone() + if exists: + print(f" SKIP {activity['title']} (already exists)") + continue + + conn.execute(""" + INSERT INTO activities + (title, description, species, due_date, difficulty, created_at) + VALUES (?, ?, ?, ?, ?, ?) + """, ( + activity["title"], + activity["description"], + activity["species"], + activity["due_date"], + activity["difficulty"], + datetime.now(), + )) + print(f" ADD {activity['title']}") + print("Activities seeding complete.") + + + init_database() init_accounts_database() init_classes_database() init_programs_database() init_articles() +init_activities_database() register_user("admin", "admin@komodohub.org", "admin", "admin") \ No newline at end of file diff --git a/main.py b/main.py index a7c7bb5..c9c5a81 100644 --- a/main.py +++ b/main.py @@ -68,4 +68,4 @@ def handle_bad_request(e): import_pages("routes", app) if __name__ == "__main__": - app.run(host="0.0.0.0", port=3000, debug=True) + app.run(host="0.0.0.0", port=3001, debug=True) diff --git a/routes/activities/activities.py b/routes/activities/activities.py new file mode 100644 index 0000000..decfffd --- /dev/null +++ b/routes/activities/activities.py @@ -0,0 +1,34 @@ +from flask import render_template, Blueprint, g, request, redirect, url_for +from database.db_connection import get_db + +page = Blueprint('activities', __name__, url_prefix='/activities') + + +@page.route('/', methods=['GET', 'POST']) +def home(): + # Handle admin creating activity + if request.method == 'POST': + if g.user and g.user["account_type"] == "admin": + with get_db() as conn: + conn.execute(""" + INSERT INTO activities + (title, description, species, due_date, difficulty) + VALUES (?, ?, ?, ?, ?) + """, ( + request.form['title'], + request.form['description'], + request.form['species'], + request.form['due_date'], + request.form['difficulty'] + )) + + return redirect(url_for('activities.home')) + + # GET: load activities from DB + with get_db() as conn: + activities = conn.execute("SELECT * FROM activities").fetchall() + + return render_template( + 'activities/activities.jinja', + activities=activities + ) \ No newline at end of file diff --git a/templates/activities/activities.jinja b/templates/activities/activities.jinja new file mode 100644 index 0000000..4463016 --- /dev/null +++ b/templates/activities/activities.jinja @@ -0,0 +1,66 @@ +{% extends "base.jinja" %} +{% block title %}Activities{% endblock %} +{% block content %} +{% from 'button.jinja' import button %} + +{% block head %} + +{% endblock %} + +
+

Activities

+ + +
+ {% for activity in activities %} +
+

{{ activity.title }}

+

{{ activity.description }}

+

Category: {{ activity.species }}

+

Due: {{ activity.due_date }}

+

Difficulty: {{ activity.difficulty }}

+ + {{ button(url='#', button_text='Start Activity', extra_classes='homepage-btn') }} +
+ {% endfor %} +
+
+ + +{% if g.user and g.user["account_type"] == "admin" %} +
+

Create New Activity (Admin)

+ +
+

+ +

+ +

+ +

+ +

+ + +
+
+{% endif %} + +{% endblock %} \ No newline at end of file From 70be20aa7740b4e34f39e7345f24a2f2e3a49119 Mon Sep 17 00:00:00 2001 From: harvey Date: Wed, 8 Apr 2026 11:42:38 +0100 Subject: [PATCH 2/3] users can now start activities and the due date counts down --- database/db_init.py | 28 +++++++--- main.py | 2 +- routes/activities/activities.py | 76 ++++++++++++++++++++++++--- templates/activities/activities.jinja | 13 +++-- templates/home/home.jinja | 1 + 5 files changed, 104 insertions(+), 16 deletions(-) diff --git a/database/db_init.py b/database/db_init.py index 6ef635e..0063b01 100644 --- a/database/db_init.py +++ b/database/db_init.py @@ -300,21 +300,21 @@ def init_activities_database(): "title": "Build a Bird Feeder", "description": "Create a bird feeder using recycled materials.", "species": "Bird Conservation", - "due_date": "3 days", + "duration_days": 3, "difficulty": "Easy" }, { "title": "Plant a Tree", "description": "Plant a tree in your garden or local area to help restore natural habitats and support wildlife.", "species": "Habitat Restoration", - "due_date": "5 days", + "duration_days": 5, "difficulty": "Medium" }, { "title": "Organise a Conservation Awareness Campaign", "description": "Plan and deliver a campaign to raise awareness about endangered species.", "species": "Education & Conservation", - "due_date": "2 weeks", + "duration_days": 14, "difficulty": "Hard" } ] @@ -327,7 +327,7 @@ def init_activities_database(): title TEXT NOT NULL, description TEXT, species TEXT, - due_date TEXT, + duration_days INTEGER, difficulty TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) @@ -346,19 +346,34 @@ def init_activities_database(): conn.execute(""" INSERT INTO activities - (title, description, species, due_date, difficulty, created_at) + (title, description, species, duration_days, difficulty, created_at) VALUES (?, ?, ?, ?, ?, ?) """, ( activity["title"], activity["description"], activity["species"], - activity["due_date"], + activity["duration_days"], activity["difficulty"], datetime.now(), )) print(f" ADD {activity['title']}") print("Activities seeding complete.") +def init_user_activities_table(): + with get_db() as conn: + conn.execute(""" + CREATE TABLE IF NOT EXISTS user_activities ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + activity_id INTEGER NOT NULL, + started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + completed INTEGER DEFAULT 0, + + FOREIGN KEY (user_id) REFERENCES users(user_id), + FOREIGN KEY (activity_id) REFERENCES activities(activity_id) + ) + """) + init_database() @@ -367,4 +382,5 @@ init_classes_database() init_programs_database() init_articles() init_activities_database() +init_user_activities_table() register_user("admin", "admin@komodohub.org", "admin", "admin") \ No newline at end of file diff --git a/main.py b/main.py index c9c5a81..a7c7bb5 100644 --- a/main.py +++ b/main.py @@ -68,4 +68,4 @@ def handle_bad_request(e): import_pages("routes", app) if __name__ == "__main__": - app.run(host="0.0.0.0", port=3001, debug=True) + app.run(host="0.0.0.0", port=3000, debug=True) diff --git a/routes/activities/activities.py b/routes/activities/activities.py index decfffd..40d52cb 100644 --- a/routes/activities/activities.py +++ b/routes/activities/activities.py @@ -1,34 +1,98 @@ from flask import render_template, Blueprint, g, request, redirect, url_for from database.db_connection import get_db +from datetime import datetime page = Blueprint('activities', __name__, url_prefix='/activities') @page.route('/', methods=['GET', 'POST']) def home(): - # Handle admin creating activity if request.method == 'POST': if g.user and g.user["account_type"] == "admin": with get_db() as conn: conn.execute(""" INSERT INTO activities - (title, description, species, due_date, difficulty) + (title, description, species, duration_days, difficulty) VALUES (?, ?, ?, ?, ?) """, ( request.form['title'], request.form['description'], request.form['species'], - request.form['due_date'], + int(request.form['duration_days']), request.form['difficulty'] )) - return redirect(url_for('activities.home')) - # GET: load activities from DB with get_db() as conn: activities = conn.execute("SELECT * FROM activities").fetchall() + started_ids = [] + if g.user: + started = conn.execute(""" + SELECT activity_id FROM user_activities + WHERE user_id = ? + """, (g.user["user_id"],)).fetchall() + + started_ids = [row["activity_id"] for row in started] + return render_template( 'activities/activities.jinja', - activities=activities + activities=activities, + started_ids=started_ids + ) + + +@page.route('/start/') +def start_activity(activity_id): + if not g.user: + return redirect(url_for('login.login')) + + with get_db() as conn: + exists = conn.execute(""" + SELECT * FROM user_activities + WHERE user_id = ? AND activity_id = ? + """, (g.user["user_id"], activity_id)).fetchone() + + if not exists: + conn.execute(""" + INSERT INTO user_activities (user_id, activity_id) + VALUES (?, ?) + """, (g.user["user_id"], activity_id)) + + return redirect(url_for('activities.home')) + + +@page.route('/my-activities') +def my_activities(): + if not g.user: + return redirect(url_for('login.login')) + + with get_db() as conn: + activities = conn.execute(""" + SELECT a.*, ua.started_at, ua.completed + FROM user_activities ua + JOIN activities a ON ua.activity_id = a.activity_id + WHERE ua.user_id = ? + """, (g.user["user_id"],)).fetchall() + + activities_with_time = [] + + for activity in activities: + try: + started_at = datetime.fromisoformat(activity["started_at"]) + except: + started_at = datetime.strptime(activity["started_at"], "%Y-%m-%d %H:%M:%S") + + duration = activity["duration_days"] + days_passed = (datetime.now() - started_at).days + days_left = duration - days_passed + + activity_dict = dict(activity) + activity_dict["days_left"] = days_left + + activities_with_time.append(activity_dict) + + return render_template( + 'activities/my_activities.jinja', + activities=activities_with_time ) \ No newline at end of file diff --git a/templates/activities/activities.jinja b/templates/activities/activities.jinja index 4463016..ea9a316 100644 --- a/templates/activities/activities.jinja +++ b/templates/activities/activities.jinja @@ -33,10 +33,17 @@

{{ activity.title }}

{{ activity.description }}

Category: {{ activity.species }}

-

Due: {{ activity.due_date }}

+

Duration days: {{ activity.duration_days }}

Difficulty: {{ activity.difficulty }}

- {{ button(url='#', button_text='Start Activity', extra_classes='homepage-btn') }} + {% if activity.activity_id in started_ids %} +

In Progress

+ {% else %} + {{ button( + url=url_for('activities.start_activity', activity_id=activity.activity_id), + button_text='Start Activity' + ) }} + {% endif %} {% endfor %} @@ -54,7 +61,7 @@

-

+



diff --git a/templates/home/home.jinja b/templates/home/home.jinja index bded2e8..f8f5e24 100644 --- a/templates/home/home.jinja +++ b/templates/home/home.jinja @@ -16,6 +16,7 @@ {{ button(url='/donation', button_text='Donate Now', extra_classes='homepage-btn') }} {{ button(url='/species', button_text='Explore Species', extra_classes='homepage-btn') }} {{ button(url='/register', button_text='Join the Movement', extra_classes='homepage-btn') }} + {{ button(url='/activities', button_text ='Activities', extra_classes='homepage-btn')}} From f69283fea64f44d475394af0aad54a46bf583406 Mon Sep 17 00:00:00 2001 From: harvey Date: Wed, 8 Apr 2026 13:30:08 +0100 Subject: [PATCH 3/3] users can now start activities and see which ones they have in progress --- database/db_init.py | 41 ++++++++++++--------------- main.py | 2 +- routes/activities/activities.py | 25 ++-------------- templates/activities/activities.jinja | 14 ++++----- 4 files changed, 28 insertions(+), 54 deletions(-) diff --git a/database/db_init.py b/database/db_init.py index 0063b01..8e1cacb 100644 --- a/database/db_init.py +++ b/database/db_init.py @@ -300,41 +300,40 @@ def init_activities_database(): "title": "Build a Bird Feeder", "description": "Create a bird feeder using recycled materials.", "species": "Bird Conservation", - "duration_days": 3, + "due_date": "12/04/2026", "difficulty": "Easy" }, { "title": "Plant a Tree", "description": "Plant a tree in your garden or local area to help restore natural habitats and support wildlife.", "species": "Habitat Restoration", - "duration_days": 5, + "due_date": "13/04/2026", "difficulty": "Medium" }, { "title": "Organise a Conservation Awareness Campaign", "description": "Plan and deliver a campaign to raise awareness about endangered species.", "species": "Education & Conservation", - "duration_days": 14, + "due_date": "3/05/2026", "difficulty": "Hard" } ] with get_db() as conn: - # Create activities table if it doesn't exist + conn.execute(""" CREATE TABLE IF NOT EXISTS activities ( activity_id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, species TEXT, - duration_days INTEGER, + due_date TEXT, difficulty TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) print("Activities table created/found.") - # Insert activities if not already in table for activity in activities_list: exists = conn.execute( "SELECT activity_id FROM activities WHERE title = ?", @@ -346,34 +345,31 @@ def init_activities_database(): conn.execute(""" INSERT INTO activities - (title, description, species, duration_days, difficulty, created_at) + (title, description, species, due_date, difficulty, created_at) VALUES (?, ?, ?, ?, ?, ?) """, ( activity["title"], activity["description"], activity["species"], - activity["duration_days"], + activity["due_date"], activity["difficulty"], datetime.now(), )) - print(f" ADD {activity['title']}") - print("Activities seeding complete.") -def init_user_activities_table(): with get_db() as conn: conn.execute(""" - CREATE TABLE IF NOT EXISTS user_activities ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - user_id INTEGER NOT NULL, - activity_id INTEGER NOT NULL, - started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - completed INTEGER DEFAULT 0, - - FOREIGN KEY (user_id) REFERENCES users(user_id), - FOREIGN KEY (activity_id) REFERENCES activities(activity_id) - ) + CREATE TABLE IF NOT EXISTS user_activities ( + user_id INTEGER NOT NULL, + activity_id INTEGER NOT NULL, + status TEXT DEFAULT 'in_progress', + started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, activity_id), + FOREIGN KEY (user_id) REFERENCES users(user_id), + FOREIGN KEY (activity_id) REFERENCES activities(activity_id) + ) """) - + print(f" ADD {activity['title']}") + print("Activities seeding complete.") init_database() @@ -382,5 +378,4 @@ init_classes_database() init_programs_database() init_articles() init_activities_database() -init_user_activities_table() register_user("admin", "admin@komodohub.org", "admin", "admin") \ No newline at end of file diff --git a/main.py b/main.py index a7c7bb5..c9c5a81 100644 --- a/main.py +++ b/main.py @@ -68,4 +68,4 @@ def handle_bad_request(e): import_pages("routes", app) if __name__ == "__main__": - app.run(host="0.0.0.0", port=3000, debug=True) + app.run(host="0.0.0.0", port=3001, debug=True) diff --git a/routes/activities/activities.py b/routes/activities/activities.py index 40d52cb..e464f7b 100644 --- a/routes/activities/activities.py +++ b/routes/activities/activities.py @@ -1,10 +1,8 @@ from flask import render_template, Blueprint, g, request, redirect, url_for from database.db_connection import get_db -from datetime import datetime page = Blueprint('activities', __name__, url_prefix='/activities') - @page.route('/', methods=['GET', 'POST']) def home(): if request.method == 'POST': @@ -12,13 +10,13 @@ def home(): with get_db() as conn: conn.execute(""" INSERT INTO activities - (title, description, species, duration_days, difficulty) + (title, description, species, due_date, difficulty) VALUES (?, ?, ?, ?, ?) """, ( request.form['title'], request.form['description'], request.form['species'], - int(request.form['duration_days']), + request.form['due_date'], request.form['difficulty'] )) return redirect(url_for('activities.home')) @@ -75,24 +73,7 @@ def my_activities(): WHERE ua.user_id = ? """, (g.user["user_id"],)).fetchall() - activities_with_time = [] - - for activity in activities: - try: - started_at = datetime.fromisoformat(activity["started_at"]) - except: - started_at = datetime.strptime(activity["started_at"], "%Y-%m-%d %H:%M:%S") - - duration = activity["duration_days"] - days_passed = (datetime.now() - started_at).days - days_left = duration - days_passed - - activity_dict = dict(activity) - activity_dict["days_left"] = days_left - - activities_with_time.append(activity_dict) - return render_template( 'activities/my_activities.jinja', - activities=activities_with_time + activities=activities ) \ No newline at end of file diff --git a/templates/activities/activities.jinja b/templates/activities/activities.jinja index ea9a316..92a87a0 100644 --- a/templates/activities/activities.jinja +++ b/templates/activities/activities.jinja @@ -33,17 +33,15 @@

{{ activity.title }}

{{ activity.description }}

Category: {{ activity.species }}

-

Duration days: {{ activity.duration_days }}

+

Due date: {{ activity.due_date }}

Difficulty: {{ activity.difficulty }}

{% if activity.activity_id in started_ids %} -

In Progress

+

In Progress

{% else %} - {{ button( - url=url_for('activities.start_activity', activity_id=activity.activity_id), - button_text='Start Activity' - ) }} - {% endif %} + {{ button(url=url_for('activities.start_activity', activity_id=activity.activity_id), button_text='Start Activity', extra_classes='homepage-btn') }} + + {% endif %} {% endfor %} @@ -61,7 +59,7 @@

-

+