diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md new file mode 100644 index 00000000..42faf8fc --- /dev/null +++ b/IMPLEMENTATION_PLAN.md @@ -0,0 +1,63 @@ +# Implementation Plan - North Pole Wishlist + +## Phase 0: Git Setup +- [x] Check if the current directory is an initialized git repository. +- [x] If it is, create and checkout a new feature branch named `north-pole-wishlist` (derived from the application name). + +## Phase 1: Environment & Project Structure +- [x] Initialize the project structure: create `app/`, `app/templates`, `app/static/css`, `app/static/img` directories. +- [x] Create a virtual environment `venv` and activate it (instructional step for the user/agent). +- [x] Create `requirements.txt` with dependencies: `Flask`, `Flask-SQLAlchemy`, `Flask-WTF`, `email-validator` (if needed for extensive validation, strictly strictly adherence to spec: spec doesn't ask for email, so just core libs). +- [x] Create `run.py` as the entry point. +- [x] Create `app/__init__.py` to initialize the Flask application, database extension, and configuration (Secret Key, DB URI). +- [x] Create a dummy route in `app/routes.py` and register it in `__init__.py` to verify the server runs. + +## Phase 2: Database Layer +- [x] Create `app/models.py`. +- [x] Define the `CategoryEnum` class with the specified values. +- [x] Define the `GiftIdea` model using SQLAlchemy 2.0 style (`Mapped`, `mapped_column`) with fields: `id`, `name`, `description`, `category`, `created_at`. +- [x] Define the `Vote` model with fields: `id`, `gift_id`, `score` (1-5), `created_at`. +- [x] Define the `Comment` model with fields: `id`, `gift_id`, `author_name`, `content`, `created_at`. +- [x] Configure relationships between `GiftIdea`, `Vote`, and `Comment`. +- [x] Create a CLI command or update `run.py` context to initialize the database (`db.create_all()`) and test creating a sample entry. + +## Phase 3: Gift Submission Feature +- [x] Create `app/forms.py` using Flask-WTF. +- [x] Define `GiftForm` with fields: `name`, `description`, `category` (SelectField). Add validators (DataRequired, Length). +- [x] Create `app/templates/base.html` with the Bootstrap 5 CDN links and a basic navigation bar structure. +- [x] Create `app/templates/submit_gift.html` extending `base.html` to render the `GiftForm`. +- [x] Implement `GET /submit` in `app/routes.py` to render the template. +- [x] Implement `POST /submit` in `app/routes.py` to validate form data and save the new `GiftIdea` to the database. Flash a success message upon completion. + +## Phase 4: Feed & Filtering +- [x] Create `app/templates/index.html` extending `base.html`. +- [x] Implement `GET /` in `app/routes.py`. +- [x] Write SQLAlchemy 2.0 queries to fetch `GiftIdea` records. +- [x] Implement the "New Arrivals" sorting logic (default). +- [x] Implement the "Category" filter logic (query parameter `category`). +- [x] Implement the "Ranking" logic: "Top Rated" (Avg Score) and "Most Popular" (Vote Count). +- [x] Render the list of gifts in `index.html` using a basic Bootstrap Card component. + +## Phase 5: Gift Details & Interaction +- [x] Create `app/templates/gift_details.html`. +- [x] Implement `GET /gift/` to fetch and display a specific gift, its average score, and comments. +- [x] Update `app/forms.py` to include `VoteForm` (Integer/Radio 1-5) and `CommentForm` (Author, Content). +- [x] Implement `POST /gift//vote` to handle voting. +- [x] Implement `POST /gift//comment` to handle comments. +- [x] Add the forms to `gift_details.html` and display existing comments chronologically. + +## Phase 6: The "Christmas Aesthetic" (UI/UX) +- [x] Update `app/templates/base.html` to include the Google Fonts (e.g., 'Mountains of Christmas'). +- [x] Create `app/static/css/style.css` and define the color palette variables (`--santa-red`, `--pine-green`, etc.). +- [x] Apply the color palette to the Navbar, Buttons, and Body background. +- [x] Add the Hero Image (Santa on Sleigh) to `index.html`. +- [x] Style the Gift Cards to look like wrapped presents (custom borders/shadows). +- [x] Style the Vote input to use snowflakes (❄️) instead of default radio buttons or stars. + +## Phase 7: Completion & Version Control +- [ ] Verify all application functionality (Submission, Browsing, Filtering, Voting, Commenting). +- [ ] Create a `README.md` file explaining the application functions, how to interact with them, the architecture, file breakdown and how to run and test it locally. +- [ ] Add all changes to the repository (`git add .`). +- [ ] Commit the changes (`git commit -m "Complete implementation of North Pole Wishlist"`). +- [ ] Push the feature branch to the remote repository, creating a branch with the same name in the remote repository, using the Gemini CLI github MCP server. +- [ ] Open a pull request for the feature branch using the Gemini CLI github MCP server, leave it open for review, don't merge it. diff --git a/README.md b/README.md index 75588e2d..cb671741 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,57 @@ -# Spec-Driven Development w Gemini CLI +# North Pole Wishlist 🎅 -This repo has some basic assets to experiment **Spec-Driven Development** using the Gemini CLI. You will act as a developer going from a raw Functional Specification to a deployed Pull Request in a single session. +A community-driven platform to share and curate the best holiday gift ideas. Built with Python (Flask) and love. -## Assets +## Features -* `.gemini/commands/`: Contains configuration files for custom commands (`techspec`, `plan`, `build`). -* `GEMINI.md`: Contains project rules and guidelines. -* `.github/workflows`: Contains CI workflow. -* **No application code**. +- **Gift Submission**: Users can share their gift ideas with descriptions and categories. +- **Vote & Rank**: Community voting system ("Snowflakes") to highlight the best gifts. +- **Comments**: Discuss and review gift ideas. +- **Festive UI**: A fully immersive Christmas-themed interface. +- **Sorting & Filtering**: Browse by category, popularity, rating, or new arrivals. -## Requirements +## Tech Stack -The `GEMINI.md` configuration and custom commands require the following extensions: -* **Google Workspace** -* **Nano Banana** -* **GitHub** +- **Backend**: Python 3, Flask, SQLAlchemy (2.0 style) +- **Frontend**: Bootstrap 5, Jinja2, Custom CSS +- **Database**: SQLite (Development) ---- +## Setup & Running Locally -## Step 1: The Architect Phase (/techspec) +1. **Clone the repository**: + ```bash + git clone + cd north-pole-wishlist + ``` -**Goal:** Transform a Functional Spec (Google Doc) into a Technical Spec (Google Doc). +2. **Create and activate a virtual environment**: + ```bash + python3 -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` -1. **Command:** - ``` - /techspec "Name of your functional specs doc" "Your desired technology stack and requirements" - ``` +3. **Install dependencies**: + ```bash + pip install -r requirements.txt + ``` -2. **What Happens:** - * The agent searches your Drive for the doc. - * It reads the requirements. - * It generates a **Technical Specification** including Data Models, API Routes, and Architecture based on your inputs. - * **Output:** It creates a *new* Google Doc titled "Technical Specification - Application name" and gives you the link. +4. **Run the application**: + ```bash + python run.py + ``` + The application will start at `http://127.0.0.1:5000`. The database will be automatically initialized with a sample gift if it's empty. ---- +## Project Structure -## Step 2: The Planning Phase (/plan) +- `app/`: Main application package. + - `models.py`: Database models (GiftIdea, Vote, Comment). + - `routes.py`: Route handlers and logic. + - `forms.py`: WTForms definitions. + - `templates/`: HTML templates. + - `static/`: CSS, Images, and JS. +- `run.py`: Application entry point. +- `requirements.txt`: Project dependencies. -**Goal:** Break the Technical Spec down into an atomic Implementation Plan. +## License -1. **Command:** - ``` - /plan "Name of your Tech spec doc" - ``` - *(Use the exact name of the doc generated in Step 1)* - -2. **What Happens:** - * The agent reads the Tech Spec. - * It creates a local file `IMPLEMENTATION_PLAN.md`. - * It breaks the project into phases (e.g., Setup, Backend, Frontend, Polish). - * It defines the Git strategy. - ---- - -## Step 3: The Build Phase (/build) - -**Goal:** Execute the plan and write the code. - -1. **Command:** - ``` - /build IMPLEMENTATION_PLAN.md "Name of your Tech spec doc" - ``` - -2. **What Happens (Iterative):** - * **Execution:** The agent iterates through the plan, initializing the project structure and writing the application code. - * **Visuals:** It generates necessary visual assets (images, icons) as defined in the spec. - * **Progress:** It updates `IMPLEMENTATION_PLAN.md` as tasks are completed. - ---- - -## Step 4: Final Delivery - -**Goal:** Push the code and open a Pull Request. - -1. **Action:** - The `/build` command's final phase usually covers this, or you can manually instruct the agent to finalize the project. - -2. **What Happens:** - * The agent runs final checks (linting/formatting). - * It creates a `README.md` for the new application. - * It commits all changes. - * It pushes the feature branch to GitHub. - * It uses the GitHub extension to **Open a Pull Request**. - ---- - -## Summary of Commands - -| Step | Command | Input | Output | -| :--- | :--- | :--- | :--- | -| **1. Spec** | `/techspec` | Functional Doc (Drive) | Tech Spec (Drive) | -| **2. Plan** | `/plan` | Tech Spec (Drive) | `IMPLEMENTATION_PLAN.md` | -| **3. Build** | `/build` | Plan + Tech Spec | Code, Assets, App | \ No newline at end of file +MIT License. diff --git a/__pycache__/run.cpython-313.pyc b/__pycache__/run.cpython-313.pyc new file mode 100644 index 00000000..f12030db Binary files /dev/null and b/__pycache__/run.cpython-313.pyc differ diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 00000000..f8ea31ff --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,10 @@ +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + +app = Flask(__name__) +app.config['SECRET_KEY'] = '5791628bb0b13ce0c676dfde280ba245' +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' + +db = SQLAlchemy(app) + +from app import routes diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 00000000..a50b4012 Binary files /dev/null and b/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/app/__pycache__/forms.cpython-313.pyc b/app/__pycache__/forms.cpython-313.pyc new file mode 100644 index 00000000..468c7a48 Binary files /dev/null and b/app/__pycache__/forms.cpython-313.pyc differ diff --git a/app/__pycache__/models.cpython-313.pyc b/app/__pycache__/models.cpython-313.pyc new file mode 100644 index 00000000..4874e278 Binary files /dev/null and b/app/__pycache__/models.cpython-313.pyc differ diff --git a/app/__pycache__/routes.cpython-313.pyc b/app/__pycache__/routes.cpython-313.pyc new file mode 100644 index 00000000..759502d1 Binary files /dev/null and b/app/__pycache__/routes.cpython-313.pyc differ diff --git a/app/forms.py b/app/forms.py new file mode 100644 index 00000000..51cebf79 --- /dev/null +++ b/app/forms.py @@ -0,0 +1,19 @@ +from flask_wtf import FlaskForm +from wtforms import StringField, TextAreaField, SelectField, SubmitField, RadioField +from wtforms.validators import DataRequired, Length +from app.models import CategoryEnum + +class GiftForm(FlaskForm): + name = StringField('Gift Name', validators=[DataRequired(), Length(max=100)]) + description = TextAreaField('Description', validators=[DataRequired(), Length(max=500)]) + category = SelectField('Category', choices=[(c.value, c.value) for c in CategoryEnum], validators=[DataRequired()]) + submit = SubmitField('Submit Gift Idea') + +class VoteForm(FlaskForm): + score = RadioField('Rating', choices=[('5', '5'), ('4', '4'), ('3', '3'), ('2', '2'), ('1', '1')], validators=[DataRequired()]) + submit = SubmitField('Vote') + +class CommentForm(FlaskForm): + author_name = StringField('Name (Optional)', validators=[Length(max=100)]) + content = TextAreaField('Comment', validators=[DataRequired(), Length(min=10, max=500)]) + submit = SubmitField('Post Comment') diff --git a/app/models.py b/app/models.py new file mode 100644 index 00000000..4fdafb2e --- /dev/null +++ b/app/models.py @@ -0,0 +1,41 @@ +from typing import List, Optional +from datetime import datetime +import sqlalchemy as sa +import sqlalchemy.orm as so +from app import db +from enum import Enum + +class CategoryEnum(Enum): + FOR_KIDS = "For Kids" + FOR_PARENTS = "For Parents" + STOCKING_STUFFERS = "Stocking Stuffers" + DIY_HOMEMADE = "DIY / Homemade" + TECH_GADGETS = "Tech & Gadgets" + DECORATIONS = "Decorations" + +class GiftIdea(db.Model): + id: so.Mapped[int] = so.mapped_column(primary_key=True) + name: so.Mapped[str] = so.mapped_column(sa.String(100), nullable=False) + description: so.Mapped[str] = so.mapped_column(sa.String(500), nullable=False) + category: so.Mapped[str] = so.mapped_column(sa.String(50), nullable=False) + created_at: so.Mapped[datetime] = so.mapped_column(default=sa.func.now()) + + votes: so.Mapped[List['Vote']] = so.relationship(back_populates='gift', cascade='all, delete-orphan') + comments: so.Mapped[List['Comment']] = so.relationship(back_populates='gift', cascade='all, delete-orphan') + +class Vote(db.Model): + id: so.Mapped[int] = so.mapped_column(primary_key=True) + gift_id: so.Mapped[int] = so.mapped_column(sa.ForeignKey('gift_idea.id'), nullable=False) + score: so.Mapped[int] = so.mapped_column(nullable=False) + created_at: so.Mapped[datetime] = so.mapped_column(default=sa.func.now()) + + gift: so.Mapped['GiftIdea'] = so.relationship(back_populates='votes') + +class Comment(db.Model): + id: so.Mapped[int] = so.mapped_column(primary_key=True) + gift_id: so.Mapped[int] = so.mapped_column(sa.ForeignKey('gift_idea.id'), nullable=False) + author_name: so.Mapped[str] = so.mapped_column(sa.String(100), default="Anonymous Elf") + content: so.Mapped[str] = so.mapped_column(sa.String(500), nullable=False) + created_at: so.Mapped[datetime] = so.mapped_column(default=sa.func.now()) + + gift: so.Mapped['GiftIdea'] = so.relationship(back_populates='comments') diff --git a/app/routes.py b/app/routes.py new file mode 100644 index 00000000..adf4c2a6 --- /dev/null +++ b/app/routes.py @@ -0,0 +1,108 @@ +from flask import render_template, url_for, flash, redirect, request, abort +from app import app, db +from app.forms import GiftForm, VoteForm, CommentForm +from app.models import GiftIdea, CategoryEnum, Vote, Comment +import sqlalchemy as sa + +@app.route('/') +def home(): + category_filter = request.args.get('category') + sort_option = request.args.get('sort', 'new') + + query = sa.select(GiftIdea) + + if category_filter: + query = query.where(GiftIdea.category == category_filter) + + if sort_option == 'popular': + query = query.outerjoin(Vote).group_by(GiftIdea.id).order_by(sa.func.count(Vote.id).desc()) + elif sort_option == 'top_rated': + query = query.outerjoin(Vote).group_by(GiftIdea.id).order_by(sa.func.avg(Vote.score).desc(), sa.func.count(Vote.id).desc()) + else: # 'new' + query = query.order_by(GiftIdea.created_at.desc()) + + gifts = db.session.scalars(query).all() + + for gift in gifts: + votes = gift.votes + gift.vote_count = len(votes) + if votes: + gift.avg_score = sum(v.score for v in votes) / len(votes) + else: + gift.avg_score = 0.0 + + return render_template('index.html', + gifts=gifts, + categories=CategoryEnum, + current_category=category_filter, + current_sort=sort_option) + +@app.route('/submit', methods=['GET', 'POST']) +def submit_gift(): + form = GiftForm() + if form.validate_on_submit(): + gift = GiftIdea(name=form.name.data, description=form.description.data, category=form.category.data) + db.session.add(gift) + db.session.commit() + flash('Your gift idea has been submitted!', 'success') + return redirect(url_for('home')) + return render_template('submit_gift.html', title='Submit Gift', form=form) + +@app.route('/gift/') +def gift_details(id): + gift = db.session.get(GiftIdea, id) + if not gift: + abort(404) + + vote_form = VoteForm() + comment_form = CommentForm() + + votes = gift.votes + vote_count = len(votes) + avg_score = sum(v.score for v in votes) / vote_count if vote_count > 0 else 0.0 + + comments = sorted(gift.comments, key=lambda c: c.created_at, reverse=False) # Chronological order + + return render_template('gift_details.html', + gift=gift, + vote_form=vote_form, + comment_form=comment_form, + avg_score=avg_score, + vote_count=vote_count, + comments=comments) + +@app.route('/gift//vote', methods=['POST']) +def cast_vote(id): + gift = db.session.get(GiftIdea, id) + if not gift: + abort(404) + + form = VoteForm() + if form.validate_on_submit(): + vote = Vote(gift_id=gift.id, score=int(form.score.data)) + db.session.add(vote) + db.session.commit() + flash('Vote cast successfully!', 'success') + else: + flash('Error casting vote.', 'danger') + + return redirect(url_for('gift_details', id=id)) + +@app.route('/gift//comment', methods=['POST']) +def post_comment(id): + gift = db.session.get(GiftIdea, id) + if not gift: + abort(404) + + form = CommentForm() + if form.validate_on_submit(): + author = form.author_name.data if form.author_name.data else "Anonymous Elf" + comment = Comment(gift_id=gift.id, author_name=author, content=form.content.data) + db.session.add(comment) + db.session.commit() + flash('Comment posted!', 'success') + else: + for error in form.content.errors: + flash(f'Comment error: {error}', 'danger') + + return redirect(url_for('gift_details', id=id)) diff --git a/app/static/css/style.css b/app/static/css/style.css new file mode 100644 index 00000000..d6598a10 --- /dev/null +++ b/app/static/css/style.css @@ -0,0 +1,104 @@ +:root { + --santa-red: #D42426; + --pine-green: #165B33; + --snow-white: #F8F9FA; + --gold: #F0A500; +} + +body { + background-color: var(--snow-white); + font-family: 'Lato', sans-serif; +} + +h1, h2, h3, h4, h5, h6, .navbar-brand { + font-family: 'Mountains of Christmas', cursive; + font-weight: 700; +} + +.navbar { + background-color: var(--santa-red) !important; +} + +.btn-primary { + background-color: var(--pine-green); + border-color: var(--pine-green); +} + +.btn-primary:hover { + background-color: #0f4224; + border-color: #0f4224; +} + +.btn-success { + background-color: var(--santa-red); + border-color: var(--santa-red); +} + +.btn-success:hover { + background-color: #a81c1e; + border-color: #a81c1e; +} + +/* Hero Section */ +.hero-section { + background-image: url('../img/hero_santa.png'); + background-size: cover; + background-position: center; + height: 400px; + display: flex; + align-items: center; + justify-content: center; + color: white; + text-shadow: 2px 2px 4px rgba(0,0,0,0.7); + margin-bottom: 30px; + border-bottom: 5px solid var(--gold); +} + +.hero-text h1 { + font-size: 4rem; +} + +/* Gift Cards */ +.card { + border: 2px solid var(--santa-red); + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + transition: transform 0.2s; +} + +.card:hover { + transform: translateY(-5px); +} + +.card-title { + color: var(--pine-green); +} + +/* Snowflake Rating */ +.form-check-input[type="radio"] { + display: none; +} + +.form-check-label { + cursor: pointer; + font-size: 1.5rem; + color: #ccc; +} + +.form-check-input:checked + .form-check-label { + color: #87CEEB; /* Ice blue for snowflakes */ +} + +/* Custom Snowflake Logic via CSS is tricky with pure radio buttons without JS or reversed DOM order trick. + For simplicity in this spec, we'll just style the label. + Actually, the plan mentions "Radio buttons styled as snowflakes". + Let's make them look like snowflakes. */ + +.form-check-label::before { + content: '❄️ '; +} + +.form-check-input:checked + .form-check-label::before { + content: '❄️ '; + text-shadow: 0 0 5px #87CEEB; +} diff --git a/app/static/img/hero_santa.png b/app/static/img/hero_santa.png new file mode 100644 index 00000000..5c8e8a85 Binary files /dev/null and b/app/static/img/hero_santa.png differ diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 00000000..226c40ff --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,47 @@ + + + + + + North Pole Wishlist + + + + + + {% block styles %}{% endblock %} + + + + +
+ {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
{{ message }}
+ {% endfor %} + {% endif %} + {% endwith %} + {% block content %}{% endblock %} +
+ + + + diff --git a/app/templates/gift_details.html b/app/templates/gift_details.html new file mode 100644 index 00000000..770a2593 --- /dev/null +++ b/app/templates/gift_details.html @@ -0,0 +1,83 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+
+

{{ gift.name }}

+
{{ gift.category }}
+

{{ gift.description }}

+
+
+
+ Current Rating: + {% if avg_score > 0 %} + {{ "%.1f"|format(avg_score) }} / 5 ({{ vote_count }} votes) + {% else %} + No votes yet + {% endif %} +
+
+
+
+ +
+
+

Comments

+ {% if comments %} + {% for comment in comments %} +
+ {{ comment.author_name }} - {{ comment.created_at.strftime('%Y-%m-%d %H:%M') }} +

{{ comment.content }}

+
+ {% endfor %} + {% else %} +

No comments yet. Be the first to share your thoughts!

+ {% endif %} +
+
+ +
+
+
Leave a Comment
+
+ {{ comment_form.hidden_tag() }} +
+ {{ comment_form.author_name.label(class="form-label") }} + {{ comment_form.author_name(class="form-control", placeholder="Anonymous Elf") }} +
+
+ {{ comment_form.content.label(class="form-label") }} + {{ comment_form.content(class="form-control", rows=3) }} + {% for error in comment_form.content.errors %} +
{{ error }}
+ {% endfor %} +
+ {{ comment_form.submit(class="btn btn-primary") }} +
+
+
+
+ +
+
+
+
Rate this Gift
+
+ {{ vote_form.hidden_tag() }} +
+ {% for subfield in vote_form.score %} +
+ {{ subfield(class="form-check-input") }} + {{ subfield.label(class="form-check-label") }} +
+ {% endfor %} +
+ {{ vote_form.submit(class="btn btn-warning w-100") }} +
+
+
+
+
+{% endblock %} diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 00000000..e3e9e257 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,63 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

North Pole Wishlist

+

Curating the best gifts for the season!

+
+
+ +
+
+
+ + +
+
+
+ +
+ {% for gift in gifts %} +
+
+
+
{{ gift.name }}
+
{{ gift.category }}
+

{{ gift.description|truncate(100) }}

+
+ +
+
+ {% else %} +
+

No gift ideas found. Be the first to submit one!

+
+ {% endfor %} +
+{% endblock %} diff --git a/app/templates/submit_gift.html b/app/templates/submit_gift.html new file mode 100644 index 00000000..822b7ff0 --- /dev/null +++ b/app/templates/submit_gift.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block content %} +
+
+

Submit a Gift Idea

+
+ {{ form.hidden_tag() }} +
+ {{ form.name.label(class="form-label") }} + {{ form.name(class="form-control") }} + {% for error in form.name.errors %} +
{{ error }}
+ {% endfor %} +
+
+ {{ form.description.label(class="form-label") }} + {{ form.description(class="form-control", rows=5) }} + {% for error in form.description.errors %} +
{{ error }}
+ {% endfor %} +
+
+ {{ form.category.label(class="form-label") }} + {{ form.category(class="form-select") }} + {% for error in form.category.errors %} +
{{ error }}
+ {% endfor %} +
+
+ {{ form.submit(class="btn btn-success") }} +
+
+
+
+{% endblock %} diff --git a/instance/site.db b/instance/site.db new file mode 100644 index 00000000..578f987d Binary files /dev/null and b/instance/site.db differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..eb384a6b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +Flask +Flask-SQLAlchemy +Flask-WTF +email-validator diff --git a/run.py b/run.py new file mode 100644 index 00000000..947cf879 --- /dev/null +++ b/run.py @@ -0,0 +1,18 @@ +from app import app, db +from app.models import GiftIdea, CategoryEnum + +if __name__ == '__main__': + with app.app_context(): + db.create_all() + # Create a sample entry if DB is empty + if not db.session.get(GiftIdea, 1): + sample_gift = GiftIdea( + name="Sample Holiday Mug", + description="A festive mug for hot cocoa.", + category=CategoryEnum.DECORATIONS.value + ) + db.session.add(sample_gift) + db.session.commit() + print("Database initialized and sample gift added.") + + app.run(debug=True)