Architecture Overview¶
This document provides a comprehensive overview of the School Clubs Management System architecture, detailing the application structure, components, and their interactions.
Application Factory Pattern¶
The application uses Flask's application factory pattern implemented in __init__.py. This design allows for better testing, multiple instances, and configuration flexibility.
Core Components¶
The application initializes several key components:
Application Factory¶
def create_app(test_config=None):
"""
Create and configure the Flask application.
Args:
test_config: Optional dictionary for test configuration
Returns:
Flask: Configured Flask application instance
"""
Configuration¶
The factory configures:
- Secret Key: Session encryption
- Database: SQLite with connection pooling
- Extensions: SQLAlchemy, Turbo, Bootstrap, Flask-Login
- Blueprints: Authentication and main routes
- Admin Panel: Flask-Admin interface
- CLI Commands: Custom commands like
seed-db
Database Configuration
User Loader¶
Flask-Login requires a user loader callback:
@login_manager.user_loader
def load_user(user_id):
"""
Load user by ID for Flask-Login.
Args:
user_id: Integer user ID
Returns:
User: User object or None
"""
return User.query.get(int(user_id))
Unauthorized Handler¶
@login_manager.unauthorized_handler
def unauthorized():
"""Handle unauthorized access attempts."""
flash("You must be logged in to access this page.", "warning")
return redirect(url_for("main.landing_page"))
Database Models¶
The application uses SQLAlchemy ORM with five main models and one association table.
Entity Relationship Diagram¶
erDiagram
User ||--o{ Membership : has
User ||--o{ Application : makes
User ||--o{ Post : creates
User }o--o{ Club : follows
Club ||--o{ Membership : has
Club ||--o{ Application : receives
Club ||--o{ Post : owns
User {
int user_id PK
string first_name
string last_name
string email UK
string password_hash
string user_type
datetime created_at
}
Club {
int club_id PK
string club_name
string description
string categories
bool is_verified
}
Post {
int post_id PK
string title
string content
string post_type
bool is_pinned
json file_urls
datetime post_date
datetime event_date
string location
int creator_id FK
int club_id FK
}
Membership {
int student_id PK,FK
int club_id PK,FK
datetime joined_at
bool is_president
}
Application {
int student_id PK,FK
int club_id PK,FK
datetime applied_at
string status
}
User Model¶
Represents system users (students and administrators).
class User(UserMixin, db.Model):
__tablename__ = "users"
user_id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(100), nullable=False)
last_name = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(100), nullable=False)
user_type = db.Column(db.String(10), nullable=False) # 'student' or 'admin'
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
Relationships:
followed_clubs: Many-to-many with Club viafollowstablememberships: One-to-many with Membershipapplications: One-to-many with Applicationcreated_posts: One-to-many with Post
Constraints:
user_typemust be 'student' or 'admin'- Email must be unique
Flask-Login Integration
The UserMixin provides default implementations for is_authenticated, is_active, is_anonymous, and get_id().
Club Model¶
Represents student clubs in the system.
class Club(db.Model):
__tablename__ = "clubs"
club_id = db.Column(db.Integer, primary_key=True)
club_name = db.Column(db.String(100), nullable=False)
description = db.Column(db.String, nullable=False)
categories = db.Column(db.String, nullable=False)
is_verified = db.Column(db.Boolean, default=False, nullable=False)
Relationships:
followers: Many-to-many with User viafollowstablememberships: One-to-many with Membershipapplications: One-to-many with Applicationposts: One-to-many with Post
Post Model¶
Represents posts, events, and gallery items.
class Post(db.Model):
__tablename__ = "posts"
post_id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, nullable=False)
content = db.Column(db.String, nullable=False)
post_type = db.Column(db.String, nullable=False) # 'post', 'event', 'galerie'
is_pinned = db.Column(db.Boolean, nullable=False, default=False)
file_urls = db.Column(db.JSON, nullable=True, default=list)
post_date = db.Column(db.DateTime(timezone=True), server_default=func.now())
# Event-specific fields
event_date = db.Column(db.DateTime(timezone=True), nullable=True)
location = db.Column(db.String(200), nullable=True)
creator_id = db.Column(db.Integer, db.ForeignKey("users.user_id"), nullable=False)
club_id = db.Column(db.Integer, db.ForeignKey("clubs.club_id"), nullable=False)
Relationships:
creator: Many-to-one with Userclub: Many-to-one with Club
Constraints:
post_typemust be 'post', 'event', or 'galerie'
Membership Model¶
Represents club membership with president designation.
class Membership(db.Model):
__tablename__ = "memberships"
student_id = db.Column(db.Integer, db.ForeignKey("users.user_id"), primary_key=True)
club_id = db.Column(db.Integer, db.ForeignKey("clubs.club_id"), primary_key=True)
joined_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
is_president = db.Column(db.Boolean, default=False, nullable=False)
Composite Primary Key: (student_id, club_id)
Application Model¶
Tracks club membership applications.
class Application(db.Model):
__tablename__ = "applications"
student_id = db.Column(db.Integer, db.ForeignKey("users.user_id"), primary_key=True)
club_id = db.Column(db.Integer, db.ForeignKey("clubs.club_id"), primary_key=True)
applied_at = db.Column(db.DateTime(timezone=True), server_default=func.now())
status = db.Column(db.String(20), default="pending", nullable=False)
Composite Primary Key: (student_id, club_id)
Constraints:
statusmust be 'pending', 'accepted', or 'denied'
Follows Association Table¶
Simple many-to-many relationship for users following clubs.
follows = db.Table(
"follows",
db.Column("student_id", db.Integer, db.ForeignKey("users.user_id"), primary_key=True),
db.Column("club_id", db.Integer, db.ForeignKey("clubs.club_id"), primary_key=True),
db.Column("followed_at", db.DateTime(timezone=True), server_default=func.now()),
)
Blueprint Structure¶
The application is organized into two main blueprints:
Authentication Blueprint (auth.py)¶
Handles user authentication operations.
Routes:
| Route | Method | Description |
|---|---|---|
/login_modal |
POST | Process login form via Turbo |
/logout |
GET | Log out current user |
/sign_up |
POST | Register new user |
Login Handler¶
@auth.route("/login_modal", methods=["POST"])
def login_modal():
"""
Handle login requests via modal form.
Uses Turbo Flask for dynamic error display.
Returns:
Redirect to landing page on success
204 No Content with Turbo update on failure
"""
email = request.form.get("email")
password = request.form.get("password")
user = User.query.filter_by(email=email).first()
if user and check_password_hash(user.password_hash, password):
login_user(user)
return redirect(url_for("main.landing_page"))
# Show error via Turbo without full page reload
turbo.push(turbo.update(
render_template("error-login.html", error_user=error),
target="login-error-container",
))
return "", 204
Turbo Flask Integration
The application uses Turbo Flask to update specific page sections without full reloads, providing a more responsive user experience.
Sign Up Handler¶
@auth.route("/sign_up", methods=["POST"])
def sign_up_post():
"""
Handle new user registration.
Validates:
- All fields present
- Email not already registered
- Password minimum 8 characters
Returns:
Redirect to landing page on success
204 with Turbo error on failure
"""
Main Blueprint (main.py)¶
Handles all club, post, and membership operations.
Key Routes:
| Route | Auth | Description |
|---|---|---|
/ |
No | Landing page with followed club posts |
/browse |
Yes | Browse clubs or events |
/club/<int:club_id> |
Yes | Club detail page |
/club/<int:club_id>/posts |
Yes | All posts for a club |
/post/create |
Yes | Create new post |
/post/<int:post_id>/edit |
Yes | Edit existing post |
/post/<int:post_id>/delete |
Yes | Delete post |
/clubs/<int:club_id>/follow |
Yes | Follow a club |
/clubs/<int:club_id>/unfollow |
Yes | Unfollow a club |
/clubs/<int:club_id>/join |
Yes | Apply for membership |
/clubs/<int:club_id>/applications |
Yes | View pending applications |
/clubs/<int:club_id>/applications/<int:student_id>/accept |
Yes | Accept application |
/clubs/<int:club_id>/applications/<int:student_id>/deny |
Yes | Deny application |
/remove/<int:club_id>/<int:user_id> |
Yes | Remove member |
/promote/<int:club_id>/<int:user_id> |
Yes | Promote to president |
Landing Page¶
@main.route("/")
def landing_page():
"""
Main landing page.
For authenticated users:
- Shows posts from followed clubs
- Pinned posts appear first
- Sorted by date descending
Returns:
Rendered landing page template with posts and role flags
"""
Club Detail¶
@main.route("/club/<int:club_id>")
@login_required
def club_detail(club_id):
"""
Display detailed club information.
Shows:
- Club details and description
- Member count and president
- Recent posts
- Pending application count (for presidents)
- User's application status
- Follow/Join/Leave actions
Returns:
Rendered club detail template
"""
Post Management¶
Create, edit, and delete posts with role-based permissions.
@main.route("/post/create", methods=["GET", "POST"])
@login_required
def create_post():
"""
Create new post, event, or gallery item.
POST: Process form and create post
GET: Show creation form
Permissions:
- Must be member, president, or admin of the club
Returns:
Redirect to club detail on success
Rendered form on GET
"""
Authorization
Post creation/editing verifies the user is a member, president, or admin of the target club.
Membership Management¶
@main.route("/remove/<int:club_id>/<int:user_id>", methods=["POST"])
@login_required
def remove_member(club_id, user_id):
"""
Remove a member from a club.
Permissions:
- Must be club president or system admin
- Cannot remove yourself
- Cannot remove the president
Returns:
Redirect to club detail with flash message
"""
@main.route("/promote/<int:club_id>/<int:user_id>", methods=["POST"])
@login_required
def make_president(club_id, user_id):
"""
Promote a member to club president.
Process:
1. Demote current president to regular member
2. Promote target member to president
Permissions:
- Must be current president or admin
Returns:
Redirect to club detail with flash message
"""
Application Processing¶
@main.route("/clubs/<int:club_id>/applications/<int:student_id>/accept", methods=["POST"])
@login_required
def accept_application(club_id, student_id):
"""
Accept a membership application.
Actions:
1. Update application status to 'accepted'
2. Create membership record
3. Set president flag if no president exists
Permissions:
- Must be club president or admin
Returns:
Redirect to applications page with flash message
"""
Utility Functions¶
Role checking functions used throughout the application:
def is_member(current_user, club_id):
"""Check if user is a member, president, or admin."""
if current_user.user_type == "admin":
return True
return Membership.query.filter_by(
student_id=current_user.user_id, club_id=club_id
).first() is not None
def is_president(current_user, club_id):
"""Check if user is president or admin."""
if current_user.user_type == "admin":
return True
membership = Membership.query.filter_by(
student_id=current_user.user_id, club_id=club_id
).first()
return membership and membership.is_president
def is_admin(current_user):
"""Check if user is an admin."""
return current_user.user_type == "admin"
Admin Panel¶
The admin panel is built with Flask-Admin and provides a web interface for system administration.
Access Control¶
class AdminModelView(ModelView):
"""Base admin view with access control."""
def is_accessible(self):
"""Only allow access to authenticated admins."""
return current_user.is_authenticated and current_user.user_type == "admin"
def inaccessible_callback(self, name, **kwargs):
"""Return 403 Forbidden for unauthorized access."""
abort(403)
Admin Views¶
Club Administration¶
class ClubAdminView(AdminModelView):
column_list = ["club_name", "description", "categories", "is_verified"]
form_columns = ["club_name", "description", "categories", "is_verified"]
Features: - View all clubs - Edit club details - Verify/unverify clubs - Delete clubs (cascades to related records)
Post Administration¶
class PostAdminView(AdminModelView):
column_list = ["title", "post_type", "is_pinned", "event_date", "location", "club"]
form_columns = ["title", "content", "post_type", "is_pinned", "event_date", "location", "club"]
column_filters = ["post_type", "is_pinned"]
def on_model_change(self, form, model, is_created):
"""Validate and set defaults before saving."""
if is_created:
model.creator_id = current_user.user_id
if not model.post_date:
model.post_date = datetime.now()
# Validate event posts have event_date
if model.post_type == "event" and not model.event_date:
raise ValueError("Event posts must have an event date")
Features: - Create posts on behalf of clubs - Pin/unpin posts - Filter by type and pinned status - Automatic creator assignment - Event validation
User Administration¶
class UserAdminView(AdminModelView):
column_list = ["user_id", "first_name", "last_name", "email", "user_type", "created_at"]
form_columns = ["first_name", "last_name", "email", "user_type", "followed_clubs"]
column_searchable_list = ["first_name", "last_name", "email"]
column_filters = ["user_type", "created_at"]
column_exclude_list = ["password_hash"]
Features: - Search users by name or email - Change user type (student/admin) - Manage followed clubs - Password hash excluded from display/editing
Membership Administration¶
class MembershipAdminView(AdminModelView):
column_list = ["student", "club", "is_president", "joined_at"]
form_columns = ["student", "club", "is_president"]
column_filters = ["is_president", "joined_at", "student", "club"]
Features: - View all memberships - Designate presidents - Filter by president status - Custom formatters for student/club names
Application Administration¶
class ApplicationAdminView(AdminModelView):
column_list = ["student", "club", "status", "applied_at"]
form_columns = ["student", "club", "status"]
column_filters = ["status", "applied_at"]
form_choices = {
"status": [("pending", "Pending"), ("accepted", "Accepted"), ("denied", "Denied")]
}
Features: - View all applications - Change application status - Filter by status or date - Custom formatters for readable names
Admin Index¶
class MyAdminIndexView(AdminIndexView):
@expose("/")
def index(self):
if not (current_user.is_authenticated and current_user.user_type == "admin"):
return self.render("admin/not_authorized.html")
return redirect(url_for("main.landing_page"))
The admin home redirects to the main landing page for authenticated admins.
Initialization¶
def init_admin(app):
"""Initialize Flask-Admin with all model views."""
admin = Admin(app, name="School Admin", index_view=MyAdminIndexView())
admin.add_view(ClubAdminView(Club, db.session))
admin.add_view(PostAdminView(Post, db.session))
admin.add_view(UserAdminView(User, db.session))
admin.add_view(MembershipAdminView(Membership, db.session))
admin.add_view(ApplicationAdminView(Application, db.session))
Security Considerations¶
Password Hashing¶
Passwords are hashed using Werkzeug's generate_password_hash:
from werkzeug.security import generate_password_hash, check_password_hash
# Registration
hashed_pw = generate_password_hash(password)
new_user = User(password_hash=hashed_pw, ...)
# Login
if check_password_hash(user.password_hash, password):
login_user(user)
Session Management¶
Flask-Login manages user sessions:
- Sessions are signed with
SECRET_KEY @login_requireddecorator protects routes- Automatic session cleanup on logout
Authorization Checks¶
Role-based access control throughout:
# Check membership before allowing post creation
if not is_member(current_user, club_id):
flash("You must be a member to create posts.", "danger")
return redirect(url_for("main.club_detail", club_id=club_id"))
# Check president status before member removal
if not is_president(current_user, club_id):
flash("Only presidents can remove members.", "danger")
return redirect(url_for("main.club_detail", club_id=club_id"))
SQL Injection Protection¶
SQLAlchemy ORM provides automatic query parameterization:
# Safe - parameterized query
User.query.filter_by(email=email).first()
# Safe - using bound parameters
User.query.filter(User.email == email).first()
Never Use String Formatting
Never use f-strings or .format() to build SQL queries. Always use SQLAlchemy's query methods.
Performance Optimization¶
Database Connection Pooling¶
app.config["SQLALCHEMY_ENGINE_OPTIONS"] = {
"connect_args": {
"timeout": 10,
"check_same_thread": False,
},
"pool_pre_ping": True, # Verify connections before use
"pool_recycle": 3600, # Recycle connections after 1 hour
}
Query Optimization¶
Eager Loading:
# Load related data in one query
club = Club.query.options(
db.joinedload(Club.memberships),
db.joinedload(Club.posts)
).get(club_id)
Selective Column Loading:
# Only load needed columns
users = User.query.with_entities(User.user_id, User.first_name, User.last_name).all()
Turbo Flask for Partial Updates¶
Instead of full page reloads:
turbo.push(turbo.update(
render_template("error-login.html", error_user=error),
target="login-error-container",
))
Only the error container is updated, reducing bandwidth and improving responsiveness.
Testing & Development¶
CLI Commands¶
Seed Database¶
Populates the database with test data defined in seed.py.
Development Server¶
Starts the development server on http://localhost:5000.
Database Migrations¶
While the application currently uses db.create_all(), production deployments should use Flask-Migrate:
Next Steps¶
- Database Models Reference - Detailed model documentation
- Authentication System - Deep dive into auth
- Admin Panel Guide - Using the admin interface
- Routes & Views - Complete route reference
__init__.py¶
This file creates and sets up the main Flask application. It configures the database, login system,
admin panel, UI tools, blueprints, and CLI commands, returning a ready-to-run app.
-
db = SQLAlchemy()(function)- Returns:
db: SQLAlchemy instance for database operations.
- Returns:
-
turbovariable- Returns:
turbo: Turbo-Flask instance for real-time updates.
- Returns:
-
bootstrap = Bootstrap5()(function)- Returns:
bootstrap: Flask-Bootstrap instance for styling.
- Returns:
-
def create_app(test_config=None)(function)- Parameters:
test_config: Optional dictionary of config values for testing or different modes.
- Returns:
app(Flask): Fully configured Flask application.
4.1
def load_user(user_id)(method)
* Parameters:
-user_id: ID of the user to load.
* Returns:
- User object from the database.4.2
def unauthorized()(method)
* Returns:
- Redirects to the landing page and flashes a warning.4.3
def seed_db_command()(function)
* Returns:
- None — populates the database with dummy data when run from CLI. - Parameters:
admin.py¶
This file defines the admin interface for the School Clubs application, including access control, admin views for clubs and posts, and the initialization of Flask-Admin.
-
admin_bp = Blueprint("admin", __name__, url_prefix="/admin")(function)- Parameters:
- "admin": Name of the blueprint.
- name: Current module name.
- url_prefix: URL prefix for all admin routes.
- Returns:
- admin_bp (Blueprint): Blueprint used for admin-related routes.
- Parameters:
-
class AdminModelView(ModelView)
Base model view that restricts access to admin users only.2.1
def is_accessible(self)(method)
* Parameters:
- None
* Returns:
- bool:Trueif the current user is logged in and is an admin, otherwise False.2.2
def inaccessible_callback(self, name, **kwargs)(method)
* Parameters:
-name: Name of the view being accessed.
-**kwargs: Additional arguments passed by Flask-Admin.
* Returns:
- None — raises a 403 Forbidden error if access is denied. -
class ClubAdminView(AdminModelView)
Admin view for managing Club records.- Parameters:
- Inherits all parameters from ModelView.
- Returns:
- ClubAdminView: Configured admin interface for clubs.
- Parameters:
-
class PostAdminView(AdminModelView)
Admin view for managing Post records, including validation and automatic field handling.def on_model_change(self, form, model, is_created)(method)
* Parameters:
-form: The submitted admin form.
-model: The Post model being created or updated.
-is_created:Trueif the record is being created,Falseif updated.
* Returns:
- None — modifies and validates the model before saving. -
class MyAdminIndexView(AdminIndexView)
Custom admin home page with access control.def index(self)(method)
* Parameters:
- None
* Returns:
- Response: Renders a “not authorized” page for non-admins or redirects admins
to the main landing page. -
def init_admin(app)(function)- Parameters:
app(Flask): The Flask application instance.
- Returns:
- None — initializes Flask-Admin and registers the Club and Post admin views.
- Parameters:
auth.py¶
This file handles user authentication for the School Clubs application, including login, logout, and sign-up functionality.
It uses Flask-Login, password hashing, and Turbo-Flask for dynamic UI updates.
-
auth = Blueprint("auth", __name__)(function)- Parameters:
"auth": Name of the blueprint.__name__: Current module name.
- Returns:
auth(Blueprint): Blueprint used for authentication routes.
- Parameters:
-
@auth.route("/login_modal", methods=["POST"])def login_modal()(function)- Parameters:
- None (form data is read from
request.form).
- None (form data is read from
- Returns:
- Redirects to the landing page on success.
- Returns HTTP 204 on failure while pushing login errors to the Turbo target container.
- Parameters:
-
@auth.route("/logout")
@login_required
def logout()(function)- Parameters:
- None
- Returns:
- Redirects to the main landing page.
- Parameters:
-
@auth.route("/sign_up", methods=["POST"])
def sign_up_post()(function)- Parameters:
- None (form data is read from
request.form).
- None (form data is read from
- Returns:
- Redirects to the landing page on successful sign-up.
- Returns HTTP 204 on failure while pushing sign-up errors to the Turbo target container.
- Parameters:
main-flask.py¶
This file defines the database models for the School Clubs application, including users, clubs, posts, memberships, applications, and follow relationships.
It also includes the seed_database function to populate the database with dummy data for development.
-
follows = db.Table(...)(function)- Returns:
follows(Table): Association table linking students to the clubs they follow.
- Returns:
-
class Membership(db.Model)
Represents membership of a student in a club.Attributes:
-student_id: ID of the student.
-club_id: ID of the club.
-joined_at: Timestamp when the membership was created.
-is_president: Boolean indicating if the student is president.
-student: Relationship to theUsermodel.
-club: Relationship to theClubmodel. -
class Application(db.Model)
Represents a student’s application to join a club.Attributes:
-student_id: ID of the student.
-club_id: ID of the club.
-applied_at: Timestamp when the application was submitted.
-status: Application status (pending,denied,accepted).
-student: Relationship to theUsermodel.
-club: Relationship to theClubmodel. -
class User(db.Model)
Represents a user of the application (student or admin).Attributes:
-user_id,first_name,last_name,email,password_hash,user_type,created_at.
-followed_clubs: Clubs the user follows.
-memberships: Memberships the user holds.
-applications: Club applications made by the user.
-created_posts: Posts created by the user. -
class Post(db.Model)
Represents a post or event created by a club or user.Attributes:
-post_id,title,content,post_type,is_pinned,file_urls,post_date.
-event_date,location(for events).
-creator_id,club_id.
-creator: Relationship toUser.
-club: Relationship toClub. -
class Club(db.Model)
Represents a club in the application.Attributes:
-club_id,club_name,description,categories,is_verified.
-followers: Users following the club.
-memberships: Memberships in the club.
-applications: Applications to the club.
-posts: Posts created by the club. -
def seed_database()(function)- Returns:
- None — populates the database with dummy users, clubs, memberships, posts, and events.
- Returns:
-
@app.context_processor
def inject_user()(function)- Returns:
dict(current_user=user): Makes the logged-in user available in all templates via{{ current_user }}.
- Returns:
main.py¶
This file defines the main Flask routes for the School Clubs application, including landing pages, club browsing, post creation and modification, membership management, and application handling.
It also provides utility functions to check user roles (member, president, admin).
-
main = Blueprint("main", __name__)(function)- Parameters:
"main": Name of the blueprint.__name__: Current module name.
- Returns:
main(Blueprint): Blueprint used for main application routes.
- Parameters:
-
@main.route("/")
def landing_page()(function)- Returns:
- Renders the landing page (
landing.html) with posts from followed clubs. - Includes user role flags (
is_member,is_president,is_admin) andfrom_adminreferrer flag.
- Renders the landing page (
- Returns:
-
@main.route("/browse")
@login_required
def browse()(function)- Parameters:
- None (query parameter
typecan be"clubs"or"events").
- None (query parameter
- Returns:
- Renders
browse.htmlwith either clubs or events, depending on thetypeparameter. - Includes user role flags (
is_member,is_president,is_admin).
- Renders
- Parameters:
-
@main.route("/club/<int:club_id>")
@login_required
def club_detail(club_id)(function)- Parameters:
club_id(int): ID of the club to display.
- Returns:
- Renders
club-detail.htmlwith club details, members count, posts, president, pending applications count, user application, and user role flags.
- Renders
- Parameters:
-
@main.route("/post/create", methods=["GET", "POST"])
@login_required
def create_post()(function)- Parameters:
- Form data from
request.formfor title, content, post type, club ID, pinned status, event date, location. - Optional gallery files from
request.files.
- Form data from
- Returns:
- On POST: Creates a new post, commits to the database, flashes success/error messages, redirects to club detail.
- On GET: Renders
modify_post.htmlwith clubs the user can post to, preselected club, and user role flags.
- Parameters:
-
@main.route("/post/<int:post_id>/edit", methods=["GET", "POST"])
@login_required
def modify_post(post_id)(function)- Parameters:
post_id(int): ID of the post to edit.- Form data from
request.formand optional gallery files.
- Returns:
- On POST: Updates the post, handles event and gallery fields, commits to database, flashes messages, redirects to club posts.
- On GET: Renders
modify_post.htmlwith existing post data and user role flags.
- Parameters:
-
@main.route("/post/<int:post_id>/delete", methods=["POST"])
@login_required
def delete_post(post_id)(function)- Parameters:
post_id(int): ID of the post to delete.
- Returns:
- Deletes the post if user is creator or admin, flashes success/error messages, redirects to referrer or landing page.
- Parameters:
-
@main.route("/remove/<int:club_id>/<int:user_id>", methods=["POST"])
@login_required
def remove_member(club_id, user_id)(function)- Parameters:
club_id(int): Club ID.user_id(int): Student ID of member to remove.
- Returns:
- Removes a member if current user is president or admin, flashes messages, redirects to club detail.
- Parameters:
-
@main.route("/promote/<int:club_id>/<int:user_id>", methods=["POST"])
@login_required
def make_president(club_id, user_id)(function)- Parameters:
club_id(int): Club ID.user_id(int): Student ID of new president.
- Returns:
- Promotes the member to president, demotes previous president, commits changes, flashes messages, redirects to club detail.
- Parameters:
-
@main.route("/club/<int:club_id>/posts")
@login_required
def club_posts(club_id)(function)- Parameters:
club_id(int): ID of the club.
- Returns:
- Renders
posts.htmlwith all posts for the given club and user role flags.
- Renders
- Parameters:
-
@main.route("/club/<int:club_id>/<int:post_id>")
@login_required
def post_mod(club_id, post_id)(function)- Parameters:
club_id(int): Club ID.post_id(int): Post ID.
- Returns:
- Renders
posts.htmlwith the given post and user role flags.
- Renders
- Parameters:
-
def is_member(current_user, club_id)(function)- Parameters:
current_user: Logged-in user object.club_id(int): Club ID.
- Returns:
Trueif user is a member, president, or admin of the club; otherwiseFalse.
- Parameters:
-
def is_president(current_user, club_id)(function)- Parameters:
current_user: Logged-in user object.club_id(int): Club ID.
- Returns:
Trueif user is president or admin; otherwiseFalse.
- Parameters:
-
def is_admin(current_user)(function)- Parameters:
current_user: Logged-in user object.
- Returns:
Trueif user is an admin; otherwiseFalse.
- Parameters:
-
@main.route("/clubs/<int:club_id>/follow", methods=["POST"])
@login_required
def follow_club(club_id)(function)- Parameters:
club_id(int): Club ID.
- Returns:
- Adds the club to user’s followed clubs, flashes messages, redirects to club detail.
- Parameters:
-
@main.route("/clubs/<int:club_id>/unfollow", methods=["POST"])
@login_required
def unfollow_club(club_id)(function)- Parameters:
club_id(int): Club ID.
- Returns:
- Removes the club from user’s followed clubs, flashes messages, redirects to club detail.
- Parameters:
-
@main.route("/clubs/<int:club_id>/join", methods=["POST"])
@login_required
def join_club(club_id)(function)- Parameters:
club_id(int): Club ID.
- Returns:
- Creates a new membership application if user is not already a member or has pending application, flashes messages, redirects to club detail.
- Parameters:
-
@main.route("/clubs/<int:club_id>/applications")
@login_required
def club_applications(club_id)(function)- Parameters:
club_id(int): Club ID.
- Returns:
- Renders
club_applications.htmlwith pending applications for the club (presidents/admins only).
- Renders
- Parameters:
-
@main.route("/clubs/<int:club_id>/applications/<int:student_id>/accept", methods=["POST"])
@login_required
def accept_application(club_id, student_id)(function)- Parameters:
club_id(int): Club ID.student_id(int): Student ID of applicant.
- Returns:
- Accepts application, creates membership, flashes messages, redirects to pending applications page.
- Parameters:
-
@main.route("/clubs/<int:club_id>/applications/<int:student_id>/deny", methods=["POST"])
@login_required
def deny_application(club_id, student_id)(function)- Parameters:
club_id(int): Club ID.student_id(int): Student ID of applicant.
- Returns:
- Denies application, updates database, flashes messages, redirects to pending applications page.
- Parameters:
models.py¶
This file defines the database models for the School Clubs application, including users, clubs, posts, memberships, applications, and follow relationships.
It also defines the follows association table linking students to clubs they follow.
-
follows = db.Table(...)(function)- Returns:
follows(Table): Association table linking students to the clubs they follow.- Columns:
student_id: Foreign key tousers.user_id.club_id: Foreign key toclubs.club_id.followed_at: Timestamp of when the follow was created (default: now).
- Returns:
-
class Membership(db.Model)
Represents membership of a student in a club.2.1 Attributes:
-student_id: ID of the student (primary key, foreign key toUser).
-club_id: ID of the club (primary key, foreign key toClub).
-joined_at: Timestamp when membership was created (default: now).
-is_president: Boolean indicating if the student is president.
-student: Relationship to theUsermodel.
-club: Relationship to theClubmodel. -
class Application(db.Model)
Represents a student’s application to join a club.3.1 Attributes:
-student_id: ID of the student (primary key, foreign key toUser).
-club_id: ID of the club (primary key, foreign key toClub).
-applied_at: Timestamp when the application was submitted (default: now).
-status: Application status (pending,denied,accepted).
-student: Relationship to theUsermodel.
-club: Relationship to theClubmodel. -
class User(UserMixin, db.Model)
Represents a user of the application (student or admin).4.1 Attributes:
-user_id,first_name,last_name,email,password_hash,user_type,created_at.
-followed_clubs: Clubs the user follows.
-memberships: Memberships the user holds.
-applications: Club applications made by the user.
-created_posts: Posts created by the user.4.2 Methods:
-get_id(): Required by Flask-Login, returns the user ID as a string. -
class Post(db.Model)
Represents a post or event created by a club or user.5.1 Attributes:
-post_id,title,content,post_type,is_pinned,file_urls,post_date.
-event_date,location(for events).
-creator_id,club_id.
-creator: Relationship toUser.
-club: Relationship toClub. -
class Club(db.Model)
Represents a club in the application.6.1 Attributes:
-club_id,club_name,description,categories,is_verified.
-followers: Users following the club.
-memberships: Memberships in the club.
-applications: Applications to the club.
-posts: Posts created by the club.