diff --git a/README.md b/README.md index 100ec4f..3fdb98f 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,29 @@

- JsWeb Logo + JsWeb Logo

+ +

+ The Blazing-Fast ASGI Lightweight Python Web Framework +

+

- + Build full-stack web apps and APIs with JsWeb. Pure Python, pure speed. +

+

PyPI version License - PyPI version + Downloads

- + Discord @@ -30,112 +37,333 @@

-# JsWeb: The Blazing-Fast ASGI Lightweight Python Web Framework +--- +## πŸ† Contributors -**JsWeb** is a modern, high-performance Python web framework built from the ground up on the **ASGI** standard. It's designed for developers who want the speed of asynchronous programming with the simplicity of a classic framework. + + Contributors + -With built-in, zero-configuration AJAX and a focus on developer experience, JsWeb makes it easy to build fast, dynamic web applications without writing a single line of JavaScript. +--- -## Core Features +## About JsWeb -* **Blazing-Fast ASGI Core:** Built for speed and concurrency, compatible with servers like Uvicorn. -* **Automatic AJAX:** Forms and navigation are automatically handled in the background for a smooth, single-page-application feel with zero configuration. -* **Elegant Routing:** Define routes with a simple decorator syntax. -* **Jinja2 Templating:** Render dynamic HTML with a powerful and familiar templating engine. -* **Built-in Security:** Comes with CSRF protection, password hashing, and cache-control tools out of the box. -* **Full-Featured Forms:** A powerful, easy-to-use form library with validation. -* **SQLAlchemy Integration:** Includes Alembic for easy database migrations. -* **Automatic Admin Panel:** A production-ready admin interface for your models that's generated automatically. -* **Modular Blueprints:** Organize your code into clean, reusable components. -* **Powerful CLI:** A command-line interface for creating projects, running the server, and managing your database. +**JsWeb** is a modern, high-performance Python web framework built from the ground up on the **ASGI** standard. It's designed for developers who want the speed of asynchronous programming with the simplicity of a classic framework. +With built-in, zero-configuration AJAX and a focus on developer experience, JsWeb makes it easy to build fast, dynamic web applications without writing a single line of JavaScript. -### Contributors - - - +### Why Choose JsWeb? -## Command-Line Interface (CLI) +- ⚑ **Lightning Fast** - ASGI-based async framework handles thousands of concurrent connections +- 🎯 **Developer Experience** - Simple, intuitive API inspired by Flask with modern features +- πŸš€ **Full-Stack Ready** - Everything you need: routing, forms, templates, database, admin panel +- πŸ”„ **Zero-Config AJAX** - Automatic SPA-like experience without JavaScript +- πŸ›‘οΈ **Security First** - CSRF protection, secure sessions, password hashing built-in +- πŸ“¦ **Production Ready** - Auto-generated admin panel, API docs, and more -JsWeb comes with a powerful set of command-line tools to streamline your development workflow. +--- -* **`jsweb run`**: Starts the development server. - * `--host
`: Specify the host to run on (e.g., `0.0.0.0`). - * `--port `: Specify the port. - * `--reload`: Enable auto-reloading on code changes. - * `--qr`: Display a QR code for accessing the server on your local network. +## ✨ Core Features -* **`jsweb new `**: Creates a new, production-ready JsWeb project with a clean directory structure and all necessary files. +- **πŸš€ Blazing-Fast ASGI Core** - Built for speed and concurrency, compatible with servers like Uvicorn +- **πŸ”„ Zero-Config AJAX** - Forms and navigation automatically handled for a smooth SPA feel +- **πŸ›£οΈ Elegant Routing** - Simple decorator-based route definition +- **🎨 Jinja2 Templating** - Powerful templating engine with inheritance and macros +- **πŸ›‘οΈ Built-in Security** - CSRF protection, password hashing, and secure session management +- **πŸ“ Full-Featured Forms** - Form validation, file uploads, and field types +- **πŸ—„οΈ SQLAlchemy Integration** - ORM with Alembic migrations included +- **βš™οΈ Automatic Admin Panel** - Production-ready data management interface generated automatically +- **🧩 Modular Blueprints** - Organize code into clean, reusable components +- **πŸ› οΈ Powerful CLI** - Create projects, run server, and manage database from command line +- **πŸ“š Auto API Documentation** - OpenAPI 3.0.3 docs at `/docs`, `/redoc`, and `/openapi.json` +- **πŸ” Hybrid DTO System** - Uses Pydantic v2 internally with clean JsWeb API -* **`jsweb db ...`**: A group of commands for managing your database migrations with Alembic. - * `jsweb db prepare -m "Your message"`: Generates a new database migration script based on changes to your models. - * `jsweb db upgrade`: Applies all pending migrations to the database. - * `jsweb db downgrade`: Reverts the last applied migration. +--- -* **`jsweb create-admin`**: An interactive command to create a new administrator user in the database. +## πŸš€ Quick Start (30 seconds) -## Installation & Setup +### 1. Install JsWeb -Get up and running in under a minute. +```bash +pip install jsweb +``` + +### 2. Create a Project -1. **Install JsWeb:** - ```bash - pip install jsweb - ``` +```bash +jsweb new my_project +cd my_project +``` -2. **Create a new project:** - ```bash - jsweb new my_project - cd my_project - ``` +### 3. Run the Server -3. **(Optional) Set up the database:** - ```bash - jsweb db prepare -m "Initial migration" - jsweb db upgrade - ``` +```bash +jsweb run --reload +``` -4. **Run the development server:** - ```bash - jsweb run --reload - ``` +Visit **http://127.0.0.1:8000** and your app is live! πŸŽ‰ -Your new JsWeb project is now running with auto-reloading enabled! +--- -## Quickstart: A Real-World Example +## πŸ“ Basic Example -Here’s how a simple but structured JsWeb application looks, using **Blueprints** to organize your code. +Here's a simple but complete JsWeb application: -**`views.py`** +**`views.py`** - Define your routes ```python from jsweb import Blueprint, render -# 1. Create a "Blueprint" for a group of related pages. views_bp = Blueprint('views') -# 2. Define a route on the blueprint. @views_bp.route("/") async def home(req): - # The render function automatically finds your templates. - return render(req, "welcome.html", {"user_name": "Guest"}) + return render(req, "welcome.html", {"name": "World"}) + +@views_bp.route("/api/status") +async def status(req): + return {"status": "online", "message": "Hello from JsWeb!"} ``` -**`app.py`** +**`app.py`** - Wire it all together ```python from jsweb import JsWebApp -import config - -# Import the blueprint you just created. from views import views_bp +import config -# 3. Create the main application instance. app = JsWebApp(config=config) - -# 4. Register your blueprint with the app. app.register_blueprint(views_bp) -# The `jsweb run` command will find and run this `app` instance. +# Run with: jsweb run --reload +``` + +That's all you need for a working application! + +--- + +## πŸ“– Installation & Setup + +Get up and running in under a minute. + +### Prerequisites + +- **Python 3.8+** (Python 3.10+ recommended) +- **pip** (Python package manager) +- A text editor or IDE + +### Step 1: Create Virtual Environment + +```bash +# Create and activate virtual environment +python -m venv venv +source venv/bin/activate # On Windows: venv\Scripts\activate +``` + +### Step 2: Install JsWeb + +```bash +pip install jsweb +``` + +### Step 3: Create New Project + +```bash +jsweb new my_awesome_app +cd my_awesome_app +``` + +### Step 4: Setup Database (Optional) + +```bash +jsweb db prepare -m "Initial migration" +jsweb db upgrade +``` + +### Step 5: Run Development Server + +```bash +jsweb run --reload +``` + +Visit `http://127.0.0.1:8000` - your app is running! πŸŽ‰ + +--- + +## πŸ› οΈ Command-Line Interface (CLI) + +JsWeb includes powerful CLI tools to streamline development: + +### `jsweb run` - Start Server + +```bash +jsweb run --reload # Auto-reload on changes +jsweb run --host 0.0.0.0 # Accessible from network +jsweb run --port 5000 # Custom port +jsweb run --reload --qr # QR code for mobile access +``` + +### `jsweb new` - Create Project + +```bash +jsweb new my_project # Create new project with boilerplate +cd my_project ``` -This structure allows you to organize your application into logical components, making it clean and scalable from the very beginning. +### `jsweb db` - Database Management -For more detailed usage, refer to the [official documentation](https://jsweb-framework.site/), individual module docstrings, and examples within the codebase. +```bash +jsweb db prepare -m "Message" # Generate migration +jsweb db upgrade # Apply migrations +jsweb db downgrade # Revert last migration +``` + +### `jsweb create-admin` - Admin User + +```bash +jsweb create-admin # Interactive admin user creation +``` + +--- + +## πŸ“š Documentation + +Complete documentation available at **https://jsweb-framework.site** + +### Core Guides + +- **[Getting Started](https://jsweb-tech.github.io/jsweb/getting-started/)** - Installation and setup +- **[Routing](https://jsweb-tech.github.io/jsweb/routing/)** - URL mapping and HTTP methods +- **[Templating](https://jsweb-tech.github.io/jsweb/templating/)** - Jinja2 templates and filters +- **[Database](https://jsweb-tech.github.io/jsweb/database/)** - Models, queries, and migrations +- **[Forms](https://jsweb-tech.github.io/jsweb/forms/)** - Form handling and validation +- **[Blueprints](https://jsweb-tech.github.io/jsweb/blueprints/)** - Modular application structure +- **[Admin Panel](https://jsweb-tech.github.io/jsweb/admin/)** - Data management interface +- **[Configuration](https://jsweb-tech.github.io/jsweb/configuration/)** - App settings +- **[CLI Reference](https://jsweb-tech.github.io/jsweb/cli/)** - Command-line tools +- **[OpenAPI Guide](https://jsweb-tech.github.io/jsweb/JSWEB_OPENAPI_GUIDE/)** - API documentation + +--- + +## 🌟 Key Concepts + +### Blueprints - Modular Organization + +Organize your application into logical modules: + +```python +from jsweb import Blueprint + +# Create a blueprint +auth_bp = Blueprint('auth', url_prefix='/auth') + +@auth_bp.route('/login', methods=['GET', 'POST']) +async def login(req): + return render(req, 'login.html') + +@auth_bp.route('/logout') +async def logout(req): + return redirect('/') + +# Register in app.py +app.register_blueprint(auth_bp) +``` + +### Forms with Validation + +Built-in form handling with validation: + +```python +from jsweb.forms import Form, StringField +from jsweb.validators import DataRequired, Email + +class LoginForm(Form): + email = StringField("Email", validators=[DataRequired(), Email()]) + password = StringField("Password", validators=[DataRequired()]) + +@app.route("/login", methods=["GET", "POST"]) +async def login(req): + form = LoginForm(await req.form()) + if form.validate(): + # Handle login + pass + return render(req, "login.html", {"form": form}) +``` + +### Database Models + +Define models with SQLAlchemy: + +```python +from jsweb.database import ModelBase, Column, Integer, String + +class User(ModelBase): + __tablename__ = 'users' + id = Column(Integer, primary_key=True) + username = Column(String(80), unique=True, nullable=False) + email = Column(String(120), unique=True, nullable=False) + +# Query the database +user = User.query.get(1) +users = User.query.all() +new_user = User.create(username="john", email="john@example.com") +``` + +### Admin Panel + +Auto-generated admin interface: + +```python +from jsweb.admin import Admin + +admin = Admin(app) +admin.register(User) +admin.register(Post) +admin.register(Category) + +# Access at http://localhost:8000/admin +``` + +--- + +## 🀝 Community & Support + +- **πŸ“– Documentation** - [jsweb-framework.site](https://jsweb-framework.site) +- **πŸ’¬ Discord** - [Join community](https://discord.gg/cqg5wgEVhP) +- **πŸ› Issues** - [Report bugs](https://github.com/Jsweb-Tech/jsweb/issues) +- **πŸ’‘ Questions & Discussions** - [Discord Community](https://discord.gg/cqg5wgEVhP) +- **πŸ”— GitHub** - [Jsweb-Tech/jsweb](https://github.com/Jsweb-Tech/jsweb) + +--- + +## πŸ‘₯ Contributing + +We welcome contributions from the community! Whether you want to fix a bug, add a feature, or improve documentation: + +1. **Fork** the repository +2. **Create** a feature branch +3. **Make** your changes +4. **Test** thoroughly +5. **Submit** a pull request + +See [Contributing Guide](CONTRIBUTING.md) for details. + +--- + +## πŸ“Š Project Status + +- **Status**: Active Development +- **Python**: 3.8+ +- **License**: MIT +- **Latest Version**: Check [PyPI](https://pypi.org/project/jsweb/) + +--- + +## πŸ“„ License + +This project is licensed under the **MIT License** - see [LICENSE](LICENSE) file for details. + +--- + +

+ Made with ❀️ by the JsWeb team
+ Join our Discord community β€’ + Sponsor us +

diff --git a/docs/admin.md b/docs/admin.md index 72af3f8..cada536 100644 --- a/docs/admin.md +++ b/docs/admin.md @@ -1,37 +1,274 @@ # Admin Interface -JsWeb includes a built-in admin interface that allows you to manage your application's data. The admin interface is powered by the `jsweb.admin` module and is highly customizable. +JsWeb includes a built-in admin interface that allows you to manage your application's data. The admin interface is automatically generated based on your models and is ready for production use out of the box. + +## Table of Contents + +- [Enabling the Admin Interface](#enabling-the-admin-interface) +- [Creating Admin Users](#creating-admin-users) +- [Accessing the Admin Panel](#accessing-the-admin-panel) +- [Managing Models](#managing-models) +- [Admin Features](#admin-features) +- [Security Considerations](#security-considerations) +- [Customization](#customization) +- [Best Practices](#best-practices) ## Enabling the Admin Interface -To enable the admin interface, you need to create an `Admin` instance and register your models with it. +To enable the admin interface, create an `Admin` instance and register your models with it: ```python # app.py from jsweb import JsWebApp from jsweb.admin import Admin -from .models import User, Post +from .models import User, Post, Category import config app = JsWebApp(config=config) + +# Create admin instance admin = Admin(app) +# Register your models admin.register(User) admin.register(Post) +admin.register(Category) ``` -This will create an admin interface at the `/admin` URL. +This will create an admin interface accessible at `/admin`. + +!!! tip "Auto-Generated Admin" + The admin interface is completely auto-generated based on your models. No additional configuration needed! -## Creating an Admin User +## Creating Admin Users -To access the admin interface, you need to create an admin user. You can do this using the `jsweb create-admin` command. +Before you can access the admin interface, you need to create an admin user: ```bash jsweb create-admin ``` -This will prompt you to enter a username, email, and password for the new admin user. +This command will prompt you to enter: +- **Username**: Your admin username +- **Email**: Your admin email address +- **Password**: A secure password + +### Creating Admin Users Programmatically + +```python +from .models import User + +# Create admin user with code +admin_user = User.create( + username="admin", + email="admin@example.com", + password="secure_password", + is_admin=True +) +``` + +!!! warning "Security" + Always use strong passwords. Never hardcode credentials in your application. + +## Accessing the Admin Panel + +Once an admin user is created, you can access the admin panel: + +1. Navigate to `http://your-domain.com/admin` +2. Log in with your admin credentials +3. Manage your application's data + +## Managing Models + +### Viewing Records + +The admin interface displays all records in a table format: +- **List View**: See all records with key fields +- **Search**: Find specific records +- **Filtering**: Filter records by field values +- **Sorting**: Sort by any column + +### Creating Records + +Click the "Add" or "Create" button to add a new record. A form will appear with all model fields. + +### Editing Records + +Click on any record in the list to edit its details. The edit form displays: +- All model fields +- Field values +- Validation errors (if any) + +### Deleting Records + +Click the delete button to remove a record. A confirmation will appear before deletion. + +## Admin Features + +### Automatic Field Detection + +The admin interface automatically detects your model fields and displays appropriate widgets: + +| Field Type | Widget | +|-----------|--------| +| String | Text input | +| Integer | Number input | +| Float | Decimal input | +| Date | Date picker | +| DateTime | Date & time picker | +| Boolean | Checkbox | +| Text | Textarea | +| Foreign Key | Dropdown selector | + +### Search & Filter + +The admin interface provides: +- **Full-text search** across all fields +- **Field filtering** by specific values +- **Boolean filtering** for true/false fields +- **Relationship filtering** for foreign keys + +### Bulk Actions + +Perform operations on multiple records: +- Select multiple records +- Apply actions (delete, export, etc.) + +## Security Considerations + +!!! warning "Admin Access Control" + The admin interface should only be accessible to trusted administrators. Consider: + + - Using strong passwords + - Enabling 2FA (if available) + - Restricting IP access with a proxy/firewall + - Using HTTPS in production + +!!! danger "Data Protection" + Be careful when deleting records. Deletions are usually permanent. Consider implementing: + + ```python + # Soft delete approach + class Post(ModelBase): + __tablename__ = 'posts' + title = Column(String(200)) + deleted_at = Column(DateTime, nullable=True) + + @property + def is_deleted(self): + return self.deleted_at is not None + ``` + +!!! note "Audit Logging" + Consider adding audit logs to track admin actions: + + ```python + class AuditLog(ModelBase): + __tablename__ = 'audit_logs' + admin_id = Column(Integer, ForeignKey('user.id')) + action = Column(String(100)) + model_name = Column(String(100)) + record_id = Column(Integer) + timestamp = Column(DateTime, default=datetime.utcnow) + ``` + +## Customization + +### Limiting Model Fields + +```python +# Currently not available in base Admin class +# Feature for future versions +``` + +### Custom Model Lists + +```python +# Custom template override support +# Feature for future versions +``` + +!!! info "Future Customization" + More customization options are planned for future versions, such as: + - Custom column lists + - Custom filters + - Inline editing + - Bulk operations + +## Best Practices + +!!! tip "Model Organization" + Keep your models organized in `models.py`: + + ```python + # models.py + from jsweb.database import ModelBase, Column, Integer, String + + class User(ModelBase): + __tablename__ = 'users' + # ... fields ... + + class Post(ModelBase): + __tablename__ = 'posts' + # ... fields ... + + # app.py + from models import User, Post + admin.register(User) + admin.register(Post) + ``` + +!!! warning "Register Models Early" + Register admin models right after creating the Admin instance: + + ```python + admin = Admin(app) + + # Register all models + admin.register(User) + admin.register(Post) + admin.register(Category) + + # Then define routes + @app.route("/") + async def home(req): + ... + ``` + +!!! success "Use Descriptive Names" + Use clear, descriptive model names for the admin interface: + + ```python + # Good + class BlogPost(ModelBase): + __tablename__ = 'blog_posts' + + # Less clear + class P(ModelBase): + __tablename__ = 'p' + ``` + +!!! tip "Validation in Models" + Add validation to your models for better data quality: + + ```python + class User(ModelBase): + __tablename__ = 'users' + username = Column(String(80), unique=True, nullable=False) + email = Column(String(120), unique=True, nullable=False, index=True) + + def __repr__(self): + return f"" + ``` -## Customizing the Admin Interface +!!! note "Admin vs Public Interface" + Keep admin interface separate from public-facing pages: + + ``` + routes/ + β”œβ”€β”€ public_routes.py # Public pages + β”œβ”€β”€ auth_routes.py # Login/Register + β”œβ”€β”€ api_routes.py # API endpoints + └── admin_routes.py # Admin-only routes (if needed) + ``` -The admin interface is automatically generated based on your models. At the moment, there are no specific customization options like `list_display` or `search_fields` available directly in the `Admin` class. However, you can extend the `Admin` class or modify the admin templates to customize the interface. diff --git a/docs/blueprints.md b/docs/blueprints.md index c0894c0..095b79a 100644 --- a/docs/blueprints.md +++ b/docs/blueprints.md @@ -1,10 +1,28 @@ # Blueprints -Blueprints are a way to organize your JsWeb application into smaller, reusable components. A blueprint is a collection of routes that can be registered with your main application. This is a great way to structure larger applications, as it allows you to group related functionality together. +Blueprints are a way to organize your JsWeb application into smaller, reusable components. A blueprint is a collection of routes that can be registered with your main application. This is an essential pattern for structuring larger applications. + +## Table of Contents + +- [What are Blueprints?](#what-are-blueprints) +- [Creating a Blueprint](#creating-a-blueprint) +- [Registering Blueprints](#registering-blueprints) +- [URL Prefixes](#url-prefixes) +- [Static Files & Templates](#static-files--templates) +- [Nested Blueprints](#nested-blueprints) +- [Best Practices](#best-practices) + +## What are Blueprints? + +Blueprints allow you to: +- **Organize code** into logical modules (auth, admin, API, etc.) +- **Reuse components** across multiple applications +- **Scale applications** as they grow +- **Maintain separation of concerns** with clear folder structure ## Creating a Blueprint -To create a blueprint, you first need to import the `Blueprint` class from the `jsweb` library. +To create a blueprint, import the `Blueprint` class and define routes on it: **`views.py`** ```python @@ -18,11 +36,15 @@ views_bp = Blueprint('views') async def home(req): # The render function automatically finds your templates. return render(req, "welcome.html", {"user_name": "Guest"}) + +@views_bp.route("/about") +async def about(req): + return render(req, "about.html") ``` -## Registering a Blueprint +## Registering Blueprints -Once you've created a blueprint, you need to register it with your main application instance. +Once you've created a blueprint, register it with your main application: **`app.py`** ```python @@ -39,26 +61,211 @@ app = JsWebApp(config=config) app.register_blueprint(views_bp) ``` +!!! tip "Multiple Blueprints" + You can register multiple blueprints with your application. Each blueprint maintains its own namespace of routes. + ## URL Prefixes -You can add a URL prefix to all the routes in a blueprint. +Add a URL prefix to all routes in a blueprint: ```python +# auth/routes.py auth_bp = Blueprint('auth', url_prefix='/auth') -@auth_bp.route('/login') -def login(): - ... +@auth_bp.route('/login', methods=['GET', 'POST']) +async def login(req): + return render(req, "auth/login.html") + +@auth_bp.route('/logout') +async def logout(req): + # Handle logout + return redirect('/') + +@auth_bp.route('/register', methods=['GET', 'POST']) +async def register(req): + return render(req, "auth/register.html") +``` + +When you register this blueprint, the routes will be available at: +- `/auth/login` +- `/auth/logout` +- `/auth/register` + +### Registering with Prefix + +You can also specify a prefix when registering: + +```python +# app.py +from auth.routes import auth_bp + +app = JsWebApp(config=config) +app.register_blueprint(auth_bp, url_prefix='/api/v1') ``` -When you register this blueprint, the `login` route will be available at `/auth/login`. +This will make routes available at `/api/v1/auth/login`, etc. + +## Static Files & Templates + +Blueprints can have their own static files and templates. -## Static Files +### Project Structure -Blueprints can also have their own static files. To do this, you need to specify the `static_folder` when you create the blueprint. +``` +myapp/ +β”œβ”€β”€ app.py +β”œβ”€β”€ views/ +β”‚ β”œβ”€β”€ routes.py +β”‚ β”œβ”€β”€ templates/ +β”‚ β”‚ └── views/ +β”‚ β”‚ β”œβ”€β”€ home.html +β”‚ β”‚ └── about.html +β”‚ └── static/ +β”‚ └── views/ +β”‚ └── views.css +β”œβ”€β”€ admin/ +β”‚ β”œβ”€β”€ routes.py +β”‚ β”œβ”€β”€ templates/ +β”‚ β”‚ └── admin/ +β”‚ β”‚ └── dashboard.html +β”‚ └── static/ +β”‚ └── admin/ +β”‚ └── admin.css +└── config.py +``` + +### Creating Blueprints with Static & Templates ```python -admin_bp = Blueprint('admin', static_folder='static') +# admin/routes.py +from jsweb import Blueprint, render + +admin_bp = Blueprint( + 'admin', + url_prefix='/admin', + static_folder='static', + static_url_path='/static', + template_folder='templates' +) + +@admin_bp.route('/dashboard') +async def dashboard(req): + # Uses admin/templates/dashboard.html + return render(req, 'dashboard.html') ``` -This will create a `/admin/static` endpoint that serves files from the `static` folder in your blueprint's directory. +!!! tip "Template Organization" + Within `template_folder`, create subdirectories matching your blueprint name for clarity. + +## Nested Blueprints + +Organize related functionality with nested blueprints: + +```python +# users/routes.py +from jsweb import Blueprint, render + +users_bp = Blueprint('users', url_prefix='/users') + +@users_bp.route('/') +async def user_list(req): + return render(req, "users/list.html") + +@users_bp.route('/') +async def user_detail(req, user_id): + return render(req, "users/detail.html", {"user_id": user_id}) + +@users_bp.route('//edit', methods=['GET', 'POST']) +async def user_edit(req, user_id): + return render(req, "users/edit.html", {"user_id": user_id}) + +# api/routes.py +from jsweb import Blueprint +from users.routes import users_bp + +api_bp = Blueprint('api', url_prefix='/api/v1') +api_bp.register_blueprint(users_bp) + +# app.py +from api.routes import api_bp + +app.register_blueprint(api_bp) +``` + +Routes will be available at: +- `/api/v1/users/` +- `/api/v1/users/` +- `/api/v1/users//edit` + +## Best Practices + +!!! tip "Blueprint Organization" + Organize your blueprints logically: + + ``` + myapp/ + β”œβ”€β”€ app.py + β”œβ”€β”€ config.py + β”œβ”€β”€ views/ # Public views + β”œβ”€β”€ auth/ # Authentication + β”œβ”€β”€ admin/ # Admin panel + β”œβ”€β”€ api/ # API endpoints + └── core/ # Shared utilities + ``` + +!!! warning "Avoid Circular Imports" + Be careful not to create circular imports when importing blueprints. Structure your imports carefully: + + ```python + # Good: Import blueprint in app.py + from auth.routes import auth_bp + app.register_blueprint(auth_bp) + + # Avoid: Don't import app in blueprint module + # from app import app # Bad! + ``` + +!!! info "Blueprint Naming" + Use clear, descriptive blueprint names: + + ```python + auth_bp = Blueprint('auth') # βœ“ Good + bp = Blueprint('bp') # βœ— Not descriptive + ``` + +!!! tip "Shared Templates & Static" + Create a `core` or `base` blueprint for shared templates and assets: + + ```python + # core/routes.py + core_bp = Blueprint('core', template_folder='templates') + + # Place shared templates in core/templates/ + # - base.html + # - macros.html + # - components/ + ``` + +!!! note "Blueprint Methods" + Blueprints support the same decorators as the app: + + ```python + @bp.route('/path') + @bp.before_request + @bp.after_request + @bp.errorhandler(404) + @bp.filter('custom_filter') + ``` + +!!! success "Testing Blueprints" + Blueprints are easier to test in isolation: + + ```python + # test_auth.py + from auth.routes import auth_bp + + # Create test app with just auth blueprint + test_app = JsWebApp(config=test_config) + test_app.register_blueprint(auth_bp) + ``` + diff --git a/docs/cli.md b/docs/cli.md index 98016cc..c0ff3e8 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -1,10 +1,32 @@ # Command-Line Interface -JsWeb comes with a powerful command-line interface (CLI) that helps you manage your application. You can use the CLI to create new projects, run the development server, manage database migrations, and more. +JsWeb comes with a powerful command-line interface (CLI) that helps you manage your application. You can use it to create new projects, run the development server, manage database migrations, and more. -## `jsweb run` +## Table of Contents -This command starts the development server. +- [Available Commands](#available-commands) +- [jsweb run](#jsweb-run) +- [jsweb new](#jsweb-new) +- [jsweb db](#jsweb-db) +- [jsweb create-admin](#jsweb-create-admin) +- [Tips & Tricks](#tips--tricks) + +## Available Commands + +Here's a quick overview of all CLI commands: + +| Command | Purpose | +|---------|---------| +| `jsweb run` | Start the development server | +| `jsweb new` | Create a new JsWeb project | +| `jsweb db prepare` | Generate a new migration | +| `jsweb db upgrade` | Apply migrations to database | +| `jsweb db downgrade` | Revert the last migration | +| `jsweb create-admin` | Create a new admin user | + +## jsweb run + +Starts the development server with optional configuration. ```bash jsweb run @@ -12,55 +34,273 @@ jsweb run ### Options -* `--host`: The host to bind to. Defaults to `127.0.0.1`. -* `--port`: The port to listen on. Defaults to `8000`. -* `--reload`: Enable auto-reloading. The server will restart whenever you make changes to your code. -* `--qr`: Display a QR code for accessing the server on your local network. +| Option | Description | Default | +|--------|-------------|---------| +| `--host` | The host to bind to | `127.0.0.1` | +| `--port` | The port to listen on | `8000` | +| `--reload` | Enable auto-reloading on code changes | Disabled | +| `--qr` | Display QR code for network access | Disabled | + +### Examples + +```bash +# Basic server +jsweb run + +# With auto-reload (recommended for development) +jsweb run --reload + +# On different port +jsweb run --port 5000 + +# Accessible from other machines +jsweb run --host 0.0.0.0 --reload + +# With QR code for mobile access +jsweb run --reload --qr + +# All options combined +jsweb run --host 0.0.0.0 --port 3000 --reload --qr +``` + +!!! tip "Development" + Always use `--reload` during development to automatically restart the server when you save changes. + +!!! warning "Production" + The development server is NOT suitable for production. Use a proper ASGI server like: + - Uvicorn + - Hypercorn + - Daphne + + ```bash + # Production example with Uvicorn + pip install uvicorn + uvicorn app:app --host 0.0.0.0 --port 8000 + ``` -## `jsweb new` +## jsweb new -This command creates a new JsWeb project with a standard directory structure. +Creates a new JsWeb project with a production-ready directory structure. ```bash -jsweb new myproject +jsweb new my_project ``` -This will create a `myproject` directory with all the necessary files to get you started. +### What Gets Created + +``` +my_project/ +β”œβ”€β”€ alembic/ # Database migrations +β”‚ β”œβ”€β”€ versions/ +β”‚ └── env.py +β”œβ”€β”€ static/ # Static files (CSS, JS, images) +β”‚ └── global.css +β”œβ”€β”€ templates/ # HTML templates +β”‚ β”œβ”€β”€ login.html +β”‚ β”œβ”€β”€ profile.html +β”‚ β”œβ”€β”€ register.html +β”‚ └── starter_template.html +β”œβ”€β”€ alembic.ini # Alembic configuration +β”œβ”€β”€ app.py # Main application +β”œβ”€β”€ auth.py # Authentication +β”œβ”€β”€ config.py # Configuration +β”œβ”€β”€ forms.py # Form definitions +β”œβ”€β”€ models.py # Database models +└── views.py # Route handlers +``` + +### Quick Start After Creating + +```bash +cd my_project +pip install jsweb +jsweb run --reload +``` + +## jsweb db + +Database management commands for migrations using Alembic. + +### jsweb db prepare + +Generate a new migration based on model changes. + +```bash +jsweb db prepare -m "Create user table" +``` + +**Options:** + +| Option | Description | Required | +|--------|-------------|----------| +| `-m`, `--message` | Description of the migration | Yes | -## `jsweb db` +**Examples:** -This command is used to manage your database migrations with Alembic. +```bash +# Simple migration +jsweb db prepare -m "Add email field to users" + +# Detailed messages are helpful +jsweb db prepare -m "Create products table with inventory tracking" + +# Structural changes +jsweb db prepare -m "Add foreign key relationships between posts and users" +``` + +!!! tip "Descriptive Messages" + Use clear, descriptive messages that explain what the migration does. This helps when reviewing migration history. -### `jsweb db prepare` +### jsweb db upgrade -This command generates a new migration script based on the changes to your models. +Apply all pending migrations to the database. ```bash -jsweb db prepare -m "A short message describing the changes" +jsweb db upgrade ``` -### `jsweb db upgrade` +**When to use:** + +- After creating a new project (to initialize database) +- After pulling code with new migrations +- Before deploying to production -This command applies all pending migrations to the database. +**Examples:** ```bash +# Apply all pending migrations +jsweb db upgrade + +# Deploy workflow +git pull jsweb db upgrade +jsweb run --reload ``` -### `jsweb db downgrade` +!!! warning "Backup Before Upgrade" + Always backup your database before running migrations in production! + + ```bash + # SQLite backup + cp app.db app.db.backup + jsweb db upgrade + ``` -This command reverts the last applied migration. +### jsweb db downgrade + +Revert the last applied migration (one step back). ```bash jsweb db downgrade ``` -## `jsweb create-admin` +**Use cases:** + +- Accidentally applied the wrong migration +- Need to test migration rollback +- Rolling back changes -This command creates a new admin user for the built-in admin interface. +**Examples:** + +```bash +# Revert last migration +jsweb db downgrade + +# Check status after downgrade +jsweb db upgrade # See current migrations +``` + +!!! danger "Production Downgrades" + Be very careful downgrading in production. Data loss may occur depending on the migration. + +## jsweb create-admin + +Create a new administrator user for the admin interface. ```bash jsweb create-admin ``` -You will be prompted to enter a username, email, and password for the new admin user. +### Interactive Prompts + +The command will ask for: +1. **Username**: Unique admin username +2. **Email**: Admin email address +3. **Password**: Secure password (input hidden) + +```bash +$ jsweb create-admin +Username: admin +Email: admin@example.com +Password: β€’β€’β€’β€’β€’β€’β€’β€’ +Password (confirm): β€’β€’β€’β€’β€’β€’β€’β€’ + +Admin user 'admin' created successfully! +``` + +!!! tip "Multiple Admins" + You can create multiple admin users by running this command multiple times. + +!!! warning "Strong Passwords" + Always use strong, unique passwords for admin accounts. Consider using a password manager. + +## Tips & Tricks + +!!! success "Command Shortcuts" + Create shell aliases for common commands: + + ```bash + # In ~/.bash_profile or ~/.zshrc + alias jsweb-dev="jsweb run --reload --qr" + alias jsweb-migrate="jsweb db prepare" + alias jsweb-upgrade="jsweb db upgrade" + ``` + +!!! tip "Working with Migrations" + Good migration workflow: + + ```bash + # Make changes to models + # ... + + # Create migration + jsweb db prepare -m "Descriptive message" + + # Test migration + jsweb db upgrade + + # Commit changes + git add -A + git commit -m "Add migration: descriptive message" + ``` + +!!! info "Environment Variables" + Configure server via environment variables: + + ```bash + # Using environment variables + export JSWEB_HOST=0.0.0.0 + export JSWEB_PORT=5000 + jsweb run --reload + ``` + +!!! note "Port Already in Use" + If port 8000 is already in use: + + ```bash + # Use different port + jsweb run --port 8001 + + # Or find and kill process + lsof -i :8000 # Find process + kill -9 # Kill process + ``` + +!!! tip "QR Code for Mobile Testing" + Use `--qr` flag to quickly test on mobile devices: + + ```bash + jsweb run --reload --qr + # Then scan the QR code with your phone + ``` + diff --git a/docs/configuration.md b/docs/configuration.md index 4228166..0e80d87 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,8 +1,20 @@ # Configuration -JsWeb applications are configured using a `config.py` file in your project's root directory. This file contains all the settings that your application needs to run, such as the database connection string, secret key, and other options. +JsWeb applications are configured using a `config.py` file in your project's root directory. This file contains all the settings your application needs to run, such as the database connection string, secret key, security settings, and other options. -A new project created with `jsweb new` will have a default `config.py` file that looks like this: +## Table of Contents + +- [Config File Structure](#config-file-structure) +- [Core Configuration Options](#core-configuration-options) +- [Database Configuration](#database-configuration) +- [Security Settings](#security-settings) +- [Development vs Production](#development-vs-production) +- [Environment Variables](#environment-variables) +- [Best Practices](#best-practices) + +## Config File Structure + +A new project created with `jsweb new` will have a default `config.py` file: ```python import os @@ -27,26 +39,332 @@ TEMPLATE_FOLDER = "templates" ## Core Configuration Options -Here are some of the most important configuration options: +Here are the most important configuration options: + +| Setting | Type | Description | +|---------|------|-------------| +| `SECRET_KEY` | String | Secret key for session signing and CSRF protection | +| `DEBUG` | Boolean | Enable/disable debug mode | +| `TESTING` | Boolean | Enable/disable testing mode | +| `SQLALCHEMY_DATABASE_URI` | String | Database connection string | +| `SQLALCHEMY_TRACK_MODIFICATIONS` | Boolean | Track SQLAlchemy object modifications | +| `STATIC_URL` | String | URL prefix for static files | +| `STATIC_DIR` | String | Directory path for static files | +| `TEMPLATE_FOLDER` | String | Directory for templates | +| `MAX_CONTENT_LENGTH` | Integer | Maximum upload file size in bytes | + +## Database Configuration + +### SQLite (Development) + +Perfect for local development and small projects: + +```python +import os + +BASE_DIR = os.path.abspath(os.path.dirname(__file__)) + +SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(BASE_DIR, "app.db") +SQLALCHEMY_TRACK_MODIFICATIONS = False +``` + +### PostgreSQL (Production) + +Recommended for production applications: + +```python +import os + +SQLALCHEMY_DATABASE_URI = ( + f"postgresql://{os.getenv('DB_USER')}:" + f"{os.getenv('DB_PASSWORD')}@" + f"{os.getenv('DB_HOST')}:" + f"{os.getenv('DB_PORT')}/" + f"{os.getenv('DB_NAME')}" +) +SQLALCHEMY_TRACK_MODIFICATIONS = False + +# Connection pooling +SQLALCHEMY_ENGINE_OPTIONS = { + "pool_size": 10, + "pool_recycle": 3600, + "pool_pre_ping": True, +} +``` + +### MySQL + +```python +SQLALCHEMY_DATABASE_URI = ( + f"mysql://{os.getenv('DB_USER')}:" + f"{os.getenv('DB_PASSWORD')}@" + f"{os.getenv('DB_HOST')}/" + f"{os.getenv('DB_NAME')}" +) +``` + +### MariaDB + +```python +SQLALCHEMY_DATABASE_URI = ( + f"mariadb://{os.getenv('DB_USER')}:" + f"{os.getenv('DB_PASSWORD')}@" + f"{os.getenv('DB_HOST')}/" + f"{os.getenv('DB_NAME')}" +) +``` + +!!! note "Connection Strings" + Always use environment variables for sensitive database credentials. Never hardcode passwords in your config file. + +## Security Settings + +### Secret Key + +The `SECRET_KEY` is used for signing sessions and protecting against CSRF attacks: + +```python +import os +import secrets + +# Generate a secure secret key +SECRET_KEY = os.getenv('SECRET_KEY', secrets.token_hex(32)) +``` + +!!! danger "Secret Key Security" + - Never commit your actual secret key to version control + - Use a different key in production + - Generate a new key with `secrets.token_hex(32)` + - Store it in environment variables or a `.env` file + +### HTTPS & Session Security + +```python +# Enable HTTPS only cookies in production +SESSION_COOKIE_SECURE = not DEBUG +SESSION_COOKIE_HTTPONLY = True +SESSION_COOKIE_SAMESITE = "Lax" + +# CSRF Protection +WTF_CSRF_ENABLED = True +WTF_CSRF_SSL_STRICT = not DEBUG +``` + +## Development vs Production + +### Development Configuration + +```python +# config_dev.py +import os + +DEBUG = True +TESTING = False + +BASE_DIR = os.path.abspath(os.path.dirname(__file__)) + +SECRET_KEY = "dev-secret-key-not-secure" + +SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(BASE_DIR, "dev.db") +SQLALCHEMY_TRACK_MODIFICATIONS = True # Helpful for development -| Setting | Description | -| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `SECRET_KEY` | A secret key that is used for session signing and other security features. This should be a long, random string. | -| `SQLALCHEMY_DATABASE_URI` | The connection string for your database. This tells SQLAlchemy where to find your database. | -| `SQLALCHEMY_TRACK_MODIFICATIONS` | If set to `True`, SQLAlchemy will track modifications of objects and emit signals. The default is `False`. | -| `STATIC_URL` | The URL prefix for your static files. | -| `STATIC_DIR` | The directory where your static files are located. | -| `TEMPLATE_FOLDER` | The directory where your templates are located. | +STATIC_URL = "/static" +STATIC_DIR = os.path.join(BASE_DIR, "static") +TEMPLATE_FOLDER = "templates" + +# File uploads +UPLOAD_FOLDER = os.path.join(BASE_DIR, "uploads") +MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16 MB +``` + +### Production Configuration + +```python +# config_prod.py +import os + +DEBUG = False +TESTING = False + +BASE_DIR = os.path.abspath(os.path.dirname(__file__)) + +SECRET_KEY = os.getenv('SECRET_KEY') + +SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL') +SQLALCHEMY_TRACK_MODIFICATIONS = False + +SQLALCHEMY_ENGINE_OPTIONS = { + "pool_size": 10, + "pool_recycle": 3600, + "pool_pre_ping": True, +} + +STATIC_URL = "/static" +STATIC_DIR = os.path.join(BASE_DIR, "static") +TEMPLATE_FOLDER = "templates" + +# Security settings +SESSION_COOKIE_SECURE = True +SESSION_COOKIE_HTTPONLY = True +SESSION_COOKIE_SAMESITE = "Strict" + +# File uploads +UPLOAD_FOLDER = "/var/uploads" +MAX_CONTENT_LENGTH = 50 * 1024 * 1024 # 50 MB + +# Cache configuration +CACHE_TYPE = "simple" +CACHE_DEFAULT_TIMEOUT = 300 +``` + +### Using Environment-Based Config + +```python +# config.py +import os + +env = os.getenv('FLASK_ENV', 'development') + +if env == 'production': + from config_prod import * +else: + from config_dev import * +``` + +## Environment Variables + +Create a `.env` file for development: + +```bash +# .env +FLASK_ENV=development +SECRET_KEY=your-super-secret-key-here +DATABASE_URL=sqlite:///app.db +DEBUG=True +``` + +Load environment variables in your app: + +```python +# app.py +import os +from dotenv import load_dotenv + +load_dotenv() + +import config +from jsweb import JsWebApp + +app = JsWebApp(config=config) +``` + +!!! warning ".env File Safety" + Add `.env` to `.gitignore` to prevent committing sensitive data: + + ```bash + # .gitignore + .env + .env.local + *.db + __pycache__/ + ``` ## Accessing Configuration -The configuration is available in your application through the `app.config` object. For example, you can access the `SECRET_KEY` like this: +Access your configuration in your application: ```python +# app.py from jsweb import JsWebApp import config app = JsWebApp(config=config) +# Access config secret_key = app.config.SECRET_KEY +debug = app.config.DEBUG +db_uri = app.config.SQLALCHEMY_DATABASE_URI +``` + +### In Routes + +```python +@app.route("/config-info") +async def config_info(req): + return { + "debug": app.config.DEBUG, + "max_upload_size": app.config.MAX_CONTENT_LENGTH, + } ``` + +## Best Practices + +!!! warning "Never Hardcode Secrets" + Always use environment variables for sensitive data: + + ```python + # Bad + SECRET_KEY = "my-secret-key" + DB_PASSWORD = "password123" + + # Good + SECRET_KEY = os.getenv('SECRET_KEY') + DB_PASSWORD = os.getenv('DB_PASSWORD') + ``` + +!!! tip "Config Organization" + Separate configs by environment: + + ``` + config/ + β”œβ”€β”€ __init__.py + β”œβ”€β”€ base.py # Common settings + β”œβ”€β”€ development.py # Dev-only settings + β”œβ”€β”€ production.py # Production settings + └── testing.py # Test settings + ``` + +!!! info "Configuration Inheritance" + Use a base config class to reduce duplication: + + ```python + # config/base.py + class Config: + SECRET_KEY = os.getenv('SECRET_KEY') + DEBUG = False + + # config/development.py + class DevConfig(Config): + DEBUG = True + SQLALCHEMY_DATABASE_URI = "sqlite:///dev.db" + + # config/production.py + class ProdConfig(Config): + SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URL') + ``` + +!!! note "Validation" + Validate your configuration on startup: + + ```python + # app.py + def validate_config(app): + required = ['SECRET_KEY', 'SQLALCHEMY_DATABASE_URI'] + for key in required: + if not hasattr(app.config, key): + raise ValueError(f"Missing configuration: {key}") + + validate_config(app) + ``` + +!!! success "Local Override" + Create a `config.local.py` for local overrides: + + ```python + # config.py + try: + from config.local import * + except ImportError: + pass + ``` + diff --git a/docs/database.md b/docs/database.md index 62fc7c5..dfbf9d0 100644 --- a/docs/database.md +++ b/docs/database.md @@ -2,89 +2,332 @@ JsWeb provides a simple and powerful way to work with databases using [SQLAlchemy](https://www.sqlalchemy.org/), a popular SQL toolkit and Object-Relational Mapper (ORM). +## Table of Contents + +- [Configuration](#configuration) +- [Defining Models](#defining-models) +- [Model Fields](#model-fields) +- [Querying the Database](#querying-the-database) +- [Database Migrations](#database-migrations) +- [Relationships](#relationships) +- [Advanced Queries](#advanced-queries) +- [Best Practices](#best-practices) + ## Configuration To get started, you need to configure your database connection in your `config.py` file. ```python # config.py -SQLALCHEMY_DATABASE_URI = "sqlite:///app.db" +import os + +BASE_DIR = os.path.abspath(os.path.dirname(__file__)) + +SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(BASE_DIR, "app.db") SQLALCHEMY_TRACK_MODIFICATIONS = False ``` +### Supported Databases + +| Database | Connection String Example | +|----------|---------------------------| +| SQLite | `sqlite:///app.db` | +| PostgreSQL | `postgresql://user:password@localhost/dbname` | +| MySQL | `mysql://user:password@localhost/dbname` | +| MariaDB | `mariadb://user:password@localhost/dbname` | + +!!! tip "SQLite for Development" + SQLite is perfect for development and small projects. For production, use PostgreSQL or MySQL. + ## Defining Models Models are Python classes that represent the tables in your database. You can define your models in the `models.py` file by inheriting from `jsweb.database.ModelBase`. ```python # models.py -from jsweb.database import ModelBase, Column, Integer, String +from jsweb.database import ModelBase, Column, Integer, String, DateTime, Text +from datetime import datetime class User(ModelBase): __tablename__ = 'users' + id = Column(Integer, primary_key=True) username = Column(String(80), unique=True, nullable=False) email = Column(String(120), unique=True, nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) def __repr__(self): return f"" ``` +## Model Fields + +Here are the common column types available: + +| Type | Description | Example | +|------|-------------|---------| +| `Integer` | Integer numbers | `Column(Integer)` | +| `String(length)` | Short text | `Column(String(100))` | +| `Text` | Long text | `Column(Text)` | +| `Boolean` | True/False values | `Column(Boolean)` | +| `DateTime` | Date and time | `Column(DateTime, default=datetime.utcnow)` | +| `Date` | Date only | `Column(Date)` | +| `Float` | Decimal numbers | `Column(Float)` | +| `JSON` | JSON data | `Column(JSON)` | +| `Enum` | Multiple choices | `Column(Enum('admin', 'user'))` | + +### Column Options + +```python +class Product(ModelBase): + __tablename__ = 'products' + + # Primary key + id = Column(Integer, primary_key=True) + + # Unique constraint + sku = Column(String(50), unique=True, nullable=False) + + # With default value + active = Column(Boolean, default=True) + + # Not null + name = Column(String(100), nullable=False) + + # Indexed for faster queries + category = Column(String(50), index=True) +``` + +## Querying the Database + +### Get All Records + +```python +from .models import User + +@app.route("/users") +async def user_list(req): + users = User.query.all() + return render(req, "users.html", {"users": users}) +``` + +### Get a Single Record by ID + +```python +@app.route("/user/") +async def user_detail(req, user_id): + user = User.query.get(user_id) + if user is None: + return "User not found", 404 + return render(req, "user.html", {"user": user}) +``` + +### Filter Records + +```python +# Get user by username +user = User.query.filter_by(username="alice").first() + +# Get all admin users +admins = User.query.filter_by(role="admin").all() + +# Using filter() for more complex conditions +from sqlalchemy import or_ + +users = User.query.filter( + or_(User.username == "alice", User.email == "alice@example.com") +).all() +``` + +!!! tip "first() vs all()" + - `first()` returns the first result or `None` + - `all()` returns a list of all results + - `get(id)` returns the record with that primary key or `None` + +### Creating Records + +```python +# Method 1: Using create() +new_user = User.create(username="john", email="john@example.com") + +# Method 2: Creating and saving manually +user = User(username="jane", email="jane@example.com") +db.session.add(user) +db.session.commit() +``` + +### Updating Records + +```python +user = User.query.get(1) +user.email = "newemail@example.com" +db.session.commit() +``` + +### Deleting Records + +```python +user = User.query.get(1) +db.session.delete(user) +db.session.commit() +``` + ## Database Migrations JsWeb uses [Alembic](https://alembic.sqlalchemy.org/) to handle database migrations. Alembic allows you to track changes to your database schema and apply them in a systematic way. ### Generating a Migration -To generate a new migration script, you can use the `jsweb db prepare` command. +After modifying your models, generate a new migration: ```bash jsweb db prepare -m "Create user table" ``` -This will create a new migration file in the `alembic/versions` directory. +This creates a migration file in `alembic/versions/` that captures your schema changes. -### Applying a Migration +### Applying Migrations -To apply the migration to your database, you can use the `jsweb db upgrade` command. +Apply pending migrations to your database: ```bash jsweb db upgrade ``` -This will run the migration script and create the `user` table in your database. +### Reverting Migrations -## Querying the Database +Undo the last migration: -You can use SQLAlchemy to query the database in your views. +```bash +jsweb db downgrade +``` + +!!! warning "Production Migrations" + Always test migrations in a development environment before running them in production. + +## Relationships + +Create relationships between models for more complex data structures. + +### One-to-Many Relationship ```python -from .models import User +from sqlalchemy import ForeignKey +from sqlalchemy.orm import relationship -@app.route("/users") -async def user_list(req): - users = User.query.all() - return render(req, "users.html", {"users": users}) +class Author(ModelBase): + __tablename__ = 'authors' + + id = Column(Integer, primary_key=True) + name = Column(String(100), nullable=False) + + # Relationship: one author has many books + books = relationship("Book", back_populates="author") + +class Book(ModelBase): + __tablename__ = 'books' + + id = Column(Integer, primary_key=True) + title = Column(String(200), nullable=False) + author_id = Column(Integer, ForeignKey('authors.id'), nullable=False) + + # Relationship: many books belong to one author + author = relationship("Author", back_populates="books") ``` -### Creating a New User +### Accessing Related Data -The `ModelBase` class provides a `create` method that creates and saves a new model instance in a single step. +```python +# Get author and their books +author = Author.query.get(1) +print(author.books) # List of all books by this author + +# Get book and its author +book = Book.query.get(1) +print(book.author.name) # Name of the book's author +``` + +## Advanced Queries + +### Counting Records ```python -from .models import User +total_users = User.query.count() +admin_count = User.query.filter_by(role="admin").count() +``` -new_user = User.create(username="john", email="john@example.com") +### Ordering Results + +```python +# Order by name (ascending) +users = User.query.order_by(User.username).all() + +# Order by created_at (descending) +from sqlalchemy import desc +users = User.query.order_by(desc(User.created_at)).all() ``` -### Getting a User by ID +### Pagination ```python -user = User.query.get(1) +page = 1 +per_page = 10 +users = User.query.paginate(page=page, per_page=per_page) ``` -### Filtering Users +### Limiting Results ```python -users = User.query.filter_by(username="john").all() +# Get first 5 users +users = User.query.limit(5).all() + +# Skip 10, then get 5 +users = User.query.offset(10).limit(5).all() ``` + +## Best Practices + +!!! tip "Always Use Transactions" + Use transactions for operations that modify multiple records: + + ```python + try: + user = User.create(username="alice", email="alice@example.com") + db.session.commit() + except Exception as e: + db.session.rollback() + raise + ``` + +!!! warning "N+1 Query Problem" + Avoid fetching related data in loops. Use eager loading: + + ```python + # Bad: N queries + books = Book.query.all() + for book in books: + print(book.author.name) # Separate query for each book + + # Good: 2 queries + from sqlalchemy.orm import joinedload + books = Book.query.options(joinedload(Book.author)).all() + ``` + +!!! info "Indexes for Performance" + Add indexes to frequently queried columns: + + ```python + class User(ModelBase): + __tablename__ = 'users' + email = Column(String(120), unique=True, index=True) + username = Column(String(80), index=True) + ``` + +!!! note "Migration Naming" + Use descriptive migration messages: + + ```bash + jsweb db prepare -m "Add email verification status to users" + jsweb db prepare -m "Create products and categories tables" + ``` + diff --git a/docs/forms.md b/docs/forms.md index ef4b63b..40491b7 100644 --- a/docs/forms.md +++ b/docs/forms.md @@ -2,13 +2,25 @@ JsWeb provides a powerful and easy-to-use form library that simplifies the process of handling user input and validation. +## Table of Contents + +- [Creating a Form](#creating-a-form) +- [Form Fields](#form-fields) +- [Validators](#validators) +- [Rendering Forms](#rendering-forms) +- [Form Validation](#form-validation) +- [File Upload](#file-upload) +- [CSRF Protection](#csrf-protection) +- [Custom Validators](#custom-validators) +- [Best Practices](#best-practices) + ## Creating a Form -To create a form, you can define a class that inherits from `jsweb.forms.Form`. You can then add fields to the form as class attributes. +To create a form, define a class that inherits from `jsweb.forms.Form`. Add fields as class attributes: ```python from jsweb.forms import Form, StringField, PasswordField, HiddenField -from jsweb.validators import DataRequired, Length, Email +from jsweb.validators import DataRequired, Length class LoginForm(Form): username = StringField("Username", validators=[DataRequired(), Length(min=4, max=25)]) @@ -18,40 +30,135 @@ class LoginForm(Form): ## Form Fields -JsWeb provides a variety of form fields, including: - -* `StringField` -* `PasswordField` -* `BooleanField` -* `IntegerField` -* `FloatField` -* `DateField` -* `TimeField` -* `DateTimeField` -* `FileField` -* `SelectField` -* `RadioField` -* `TextAreaField` -* `HiddenField` +JsWeb provides a variety of form fields for different input types: + +### Text Fields + +| Field | Description | Example | +|-------|-------------|---------| +| `StringField` | Single-line text input | `StringField("Name")` | +| `PasswordField` | Password input (hidden) | `PasswordField("Password")` | +| `TextAreaField` | Multi-line text input | `TextAreaField("Comments")` | +| `EmailField` | Email input | `EmailField("Email")` | +| `URLField` | URL input | `URLField("Website")` | +| `SearchField` | Search input | `SearchField("Search")` | + +### Number & Boolean Fields + +| Field | Description | Example | +|-------|-------------|---------| +| `IntegerField` | Integer input | `IntegerField("Age")` | +| `FloatField` | Decimal number input | `FloatField("Price")` | +| `DecimalField` | High-precision decimal | `DecimalField("Amount")` | +| `BooleanField` | Checkbox | `BooleanField("Agree to terms")` | + +### Date & Time Fields + +| Field | Description | Example | +|-------|-------------|---------| +| `DateField` | Date picker | `DateField("Birth Date")` | +| `TimeField` | Time picker | `TimeField("Start Time")` | +| `DateTimeField` | Date & time picker | `DateTimeField("Appointment")` | + +### Selection Fields + +| Field | Description | Example | +|-------|-------------|---------| +| `SelectField` | Dropdown list | `SelectField("Country", choices=[...])` | +| `RadioField` | Radio button group | `RadioField("Gender", choices=[...])` | +| `FileField` | File upload | `FileField("Upload")` | +| `HiddenField` | Hidden field (CSRF token) | `HiddenField()` | + +### Complete Example + +```python +from jsweb.forms import Form, StringField, IntegerField, SelectField, DateField, BooleanField +from jsweb.validators import DataRequired, Email, NumberRange + +class UserRegistrationForm(Form): + username = StringField("Username", validators=[DataRequired()]) + email = StringField("Email", validators=[DataRequired(), Email()]) + age = IntegerField("Age", validators=[NumberRange(min=18, max=120)]) + country = SelectField("Country", choices=[ + ("us", "United States"), + ("uk", "United Kingdom"), + ("ca", "Canada") + ]) + birth_date = DateField("Birth Date") + newsletter = BooleanField("Subscribe to newsletter") + csrf_token = HiddenField() +``` ## Validators -Validators are used to check that the data submitted by the user is valid. You can pass a list of validators to a field's `validators` argument. +Validators check that submitted data is valid. Pass a list of validators to the `validators` argument: + +```python +from jsweb.validators import ( + DataRequired, Length, Email, EqualTo, NumberRange, + FileRequired, FileAllowed, URL, Regexp +) + +class Form(Form): + username = StringField("Username", validators=[ + DataRequired(message="Username is required"), + Length(min=4, max=25, message="Username must be 4-25 characters") + ]) +``` + +### Built-in Validators -Some of the built-in validators include: +| Validator | Purpose | Example | +|-----------|---------|---------| +| `DataRequired` | Field cannot be empty | `DataRequired()` | +| `Length` | String length validation | `Length(min=3, max=50)` | +| `Email` | Valid email format | `Email()` | +| `URL` | Valid URL format | `URL()` | +| `EqualTo` | Compare with another field | `EqualTo('password')` | +| `NumberRange` | Number within range | `NumberRange(min=0, max=100)` | +| `FileRequired` | File must be uploaded | `FileRequired()` | +| `FileAllowed` | Allowed file extensions | `FileAllowed(['pdf', 'doc'])` | +| `FileSize` | File size limit | `FileSize(max_size=5_000_000)` | +| `Regexp` | Match regex pattern | `Regexp(r'^[A-Z]')` | +| `Optional` | Field is optional | `Optional()` | -* `DataRequired`: Checks that the field is not empty. -* `Length`: Checks that the length of the input is within a certain range. -* `Email`: Checks that the input is a valid email address. -* `EqualTo`: Checks that the input is equal to the input of another field. -* `NumberRange`: Checks that the input is within a certain number range. -* `FileRequired`: Checks that a file has been uploaded. -* `FileAllowed`: Checks that the uploaded file has an allowed extension. -* `FileSize`: Checks that the uploaded file is within a certain size range. +### Validator Examples -## Rendering a Form +```python +class SignUpForm(Form): + username = StringField("Username", validators=[ + DataRequired(), + Length(min=4, max=20) + ]) + + email = StringField("Email", validators=[ + DataRequired(), + Email(message="Invalid email address") + ]) + + password = PasswordField("Password", validators=[ + DataRequired(), + Length(min=8) + ]) + + confirm_password = PasswordField("Confirm Password", validators=[ + DataRequired(), + EqualTo('password', message="Passwords must match") + ]) + + age = IntegerField("Age", validators=[ + NumberRange(min=18, message="Must be at least 18") + ]) + + website = StringField("Website (optional)", validators=[URL()]) +``` + +!!! tip "Custom Messages" + Provide custom validation error messages to improve user experience. -You can render a form in your templates by passing the form object to the `render` function. +## Rendering Forms + +Use the `render` function to pass the form to your template: ```python from jsweb.response import render @@ -62,46 +169,255 @@ async def login(req): form = LoginForm(await req.form(), await req.files()) if req.method == "POST" and form.validate(): # Handle the login - ... + username = form.username.data + # ... authentication logic ... return render(req, "login.html", {"form": form}) ``` -In your template, you can then render the form fields individually. +## Form Validation + +### Checking if Form is Valid + +```python +if form.validate(): + # Process form data + data = form.data # All form data as dictionary + username = form.username.data +else: + # Form has errors + errors = form.errors # Dictionary of field errors +``` + +### Accessing Field Data + +```python +# Get specific field value +username = form.username.data + +# Get all form data +all_data = form.data # Returns dict + +# Check if field has errors +if form.username.errors: + print(form.username.errors) # List of error messages +``` + +### Rendering in Template ```html
{{ form.csrf_token() }} -
+ +
{{ form.username.label }} {{ form.username() }} {% if form.username.errors %} -
    +
    {% for error in form.username.errors %} -
  • {{ error }}
  • +

    {{ error }}

    {% endfor %} -
+
{% endif %}
-
+ +
{{ form.password.label }} {{ form.password() }} {% if form.password.errors %} -
    +
    {% for error in form.password.errors %} -
  • {{ error }}
  • +

    {{ error }}

    {% endfor %} -
+
{% endif %}
+
``` +### Styling with CSS + +```css +.errors { + color: #d32f2f; + margin-top: 0.5rem; +} + +.form-group { + margin-bottom: 1rem; +} + +input.error { + border-color: #d32f2f; +} +``` + +## File Upload + +Handle file uploads with `FileField`: + +```python +from jsweb.forms import Form, FileField, StringField +from jsweb.validators import FileRequired, FileAllowed + +class ProfileForm(Form): + bio = StringField("Bio") + avatar = FileField("Profile Picture", validators=[ + FileRequired(), + FileAllowed(['jpg', 'jpeg', 'png', 'gif']) + ]) +``` + +### Processing File Uploads + +```python +import os +from werkzeug.utils import secure_filename + +UPLOAD_FOLDER = 'uploads' +ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif'} + +@app.route("/upload", methods=["GET", "POST"]) +async def upload_profile(req): + form = ProfileForm(await req.form(), await req.files()) + + if req.method == "POST" and form.validate(): + file = form.avatar.data + + # Secure the filename + filename = secure_filename(file.filename) + filepath = os.path.join(UPLOAD_FOLDER, filename) + + # Save the file + file.save(filepath) + + return "File uploaded successfully!" + + return render(req, "upload.html", {"form": form}) +``` + +!!! warning "Security" + Always validate file uploads using `FileAllowed` and `secure_filename()` to prevent security vulnerabilities. + ## CSRF Protection -JsWeb provides built-in CSRF protection. For traditional forms, you can include a hidden `csrf_token` field in your form, as shown in the example above. +JsWeb provides built-in CSRF protection. Include a hidden `csrf_token` field in your forms: + +```python +class LoginForm(Form): + username = StringField("Username", validators=[DataRequired()]) + password = PasswordField("Password", validators=[DataRequired()]) + csrf_token = HiddenField() # Required for CSRF protection +``` + +### In Templates + +```html +
+ {{ form.csrf_token() }} + +
+``` + +!!! info "CSRF Token Validation" + JsWeb automatically validates CSRF tokens on POST requests. Always include the `csrf_token` field in your forms. + +!!! note "Future API Header Support" + Future versions of JsWeb will support sending the CSRF token in the `X-CSRF-Token` HTTP header for SPAs and API-first applications. + +## Custom Validators + +Create custom validators for specific validation logic: + +```python +from jsweb.validators import ValidationError + +def validate_username_available(form, field): + """Check if username is not already taken""" + from .models import User + if User.query.filter_by(username=field.data).first(): + raise ValidationError('Username already taken') + +def validate_no_special_chars(form, field): + """Check that field contains no special characters""" + import re + if not re.match(r'^[a-zA-Z0-9_]+$', field.data): + raise ValidationError('Only alphanumeric and underscore allowed') + +class SignUpForm(Form): + username = StringField("Username", validators=[ + DataRequired(), + validate_username_available + ]) + + bio = TextAreaField("Bio", validators=[ + validate_no_special_chars + ]) +``` + +!!! tip "Validation Order" + Built-in validators run first, then custom validators. Place custom validators last in the list. + +## Best Practices + +!!! warning "Always Validate Input" + Never trust user input. Always validate on the server side, even if you have client-side validation: + + ```python + if form.validate(): + # Safe to use form.data + ``` + +!!! tip "Use Specific Field Types" + Use the most specific field type for better semantics: + + ```python + # Good + email = EmailField("Email", validators=[Email()]) + age = IntegerField("Age") + + # Less good + email = StringField("Email") + age = StringField("Age") + ``` + +!!! info "Security: Password Fields" + Always use `PasswordField` for passwords, never `StringField`: + + ```python + # Good + password = PasswordField("Password") + + # Bad + password = StringField("Password") # Not hidden! + ``` + +!!! note "Flash Messages" + Provide user feedback after form submission: + + ```python + if form.validate(): + # Process form + return redirect("/success") + else: + # Show validation errors + return render(req, "form.html", {"form": form}) + ``` + +!!! tip "Organize Large Forms" + Break large forms into multiple steps or use field groups: + + ```python + class CheckoutForm(Form): + # Billing information + billing_address = StringField("Address") + + # Shipping information + shipping_address = StringField("Address") + + # Payment information + card_number = StringField("Card Number") + ``` -> **Note for the Next Version:** -> -> For SPAs and API-first applications, the next version of JsWeb will also support sending the CSRF token in the `X-CSRF-Token` HTTP header. This is the recommended approach for modern web applications. diff --git a/docs/getting-started.md b/docs/getting-started.md index b47a109..b7142f6 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1,63 +1,154 @@ # Getting Started with JsWeb -Welcome to JsWeb! This guide will walk you through the process of setting up your development environment, creating a new project, and running your first JsWeb application. +Welcome to JsWeb! This comprehensive guide will walk you through setting up your development environment, creating your first project, and running your application. + +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Installation](#installation) +- [Creating a New Project](#creating-a-new-project) +- [Understanding the Project Structure](#understanding-the-project-structure) +- [Running the Development Server](#running-the-development-server) +- [Next Steps](#next-steps) +- [Tips & Tricks](#tips--tricks) + +## Prerequisites + +Before you begin, ensure you have the following installed: + +!!! note "System Requirements" + - **Python 3.8+** (Python 3.10+ is recommended for best performance) + - **pip** (Python package manager) + - A terminal or command prompt + - A text editor or IDE (VS Code, PyCharm, etc.) ## Installation -JsWeb is available on PyPI and can be installed with `pip`. It's recommended to use a virtual environment to manage your project's dependencies. +JsWeb is available on PyPI and can be installed with `pip`. We recommend using a virtual environment to manage your project's dependencies. + +### Step 1: Create and Activate a Virtual Environment ```bash -# Create and activate a virtual environment +# Create a new virtual environment python -m venv venv -source venv/bin/activate # On Windows, use `venv\Scripts\activate` -# Install JsWeb +# Activate the virtual environment +# On macOS/Linux: +source venv/bin/activate + +# On Windows: +venv\Scripts\activate +``` + +!!! tip "Virtual Environments" + Virtual environments isolate your project dependencies from your system Python. This is considered a best practice and prevents dependency conflicts. + +### Step 2: Install JsWeb + +```bash pip install jsweb ``` +Verify the installation: + +```bash +jsweb --version +``` + ## Creating a New Project -Once JsWeb is installed, you can use the `jsweb` command-line interface (CLI) to generate a new project. This will create a directory with a standard structure, including all the necessary files to get you started. +Once JsWeb is installed, use the CLI to generate a new project with a standard structure and all necessary files. ```bash jsweb new myproject cd myproject ``` -This will create a `myproject` directory with the following structure: +!!! info "Project Creation" + The `jsweb new` command creates a production-ready project structure with all boilerplate code you need to start building. + +## Understanding the Project Structure + +After running `jsweb new myproject`, your project will look like this: ``` myproject/ -β”œβ”€β”€ alembic/ -β”œβ”€β”€ static/ -β”œβ”€β”€ templates/ -β”œβ”€β”€ alembic.ini -β”œβ”€β”€ app.py -β”œβ”€β”€ auth.py -β”œβ”€β”€ config.py -β”œβ”€β”€ forms.py -β”œβ”€β”€ models.py -└── views.py +β”œβ”€β”€ alembic/ # Database migrations +β”‚ β”œβ”€β”€ versions/ # Migration files +β”‚ └── env.py +β”œβ”€β”€ static/ # Static files (CSS, JS, images) +β”‚ └── global.css +β”œβ”€β”€ templates/ # HTML templates +β”‚ β”œβ”€β”€ login.html +β”‚ β”œβ”€β”€ profile.html +β”‚ └── register.html +β”œβ”€β”€ alembic.ini # Alembic configuration +β”œβ”€β”€ app.py # Main application file +β”œβ”€β”€ auth.py # Authentication logic +β”œβ”€β”€ config.py # Configuration settings +β”œβ”€β”€ forms.py # Form definitions +β”œβ”€β”€ models.py # Database models +└── views.py # Route handlers ``` +### Key Files Explained + +| File | Purpose | +|---------------|-------------------------------------------------------------| +| `app.py` | Creates and configures your JsWeb application instance | +| `config.py` | Stores application configuration (database, secret key, etc) | +| `models.py` | Define your database models/tables | +| `views.py` | Define your routes and request handlers | +| `forms.py` | Define your HTML forms and validation rules | +| `auth.py` | Handle user authentication and authorization | +| `templates/` | Store your HTML templates | +| `static/` | Store CSS, JavaScript, and image files | + ## Running the Development Server -To run your new JsWeb application, use the `jsweb run` command from within your project directory. +To run your new JsWeb application: ```bash jsweb run --reload ``` -The `--reload` flag enables auto-reloading, which means the server will automatically restart whenever you make changes to your code. This is incredibly useful during development. +### Command Options + +| Option | Description | +|--------|-------------| +| `--reload` | Auto-restart server when code changes (highly recommended) | +| `--host` | Host to run on (default: `127.0.0.1`) | +| `--port` | Port to run on (default: `8000`) | +| `--qr` | Display QR code for network access | + +!!! success "Server Started" + You should see output like `Application startup complete`. Your app is now running at **http://127.0.0.1:8000** + +Visit `http://127.0.0.1:8000` in your browser to see your application in action! + +## Next Steps + +Now that you have a running JsWeb project, here are the topics you should explore: + +1. **[Routing](routing.md)** - Learn how to define routes and handle different URLs +2. **[Templating](templating.md)** - Use Jinja2 templates to render dynamic HTML +3. **[Database](database.md)** - Set up models and manage your database +4. **[Forms](forms.md)** - Create forms with validation +5. **[Blueprints](blueprints.md)** - Organize your code into modules +6. **[Admin Interface](admin.md)** - Use the built-in admin panel +7. **[Configuration](configuration.md)** - Configure your application + +## Tips & Tricks + +!!! tip "Hot Reload During Development" + Always use `jsweb run --reload` during development. This saves you from manually restarting the server every time you change code. -You can now access your application by navigating to `http://127.0.0.1:8000` in your web browser. +!!! tip "QR Code Access" + Use `jsweb run --reload --qr` to generate a QR code for accessing your app from other devices on your network. -## What's Next? +!!! warning "Production" + The development server is not suitable for production. Use a proper ASGI server like Uvicorn, Gunicorn with an ASGI worker, or Daphne for production deployments. -Now that you have a running JsWeb project, you're ready to start building your application. Here are a few topics you might want to explore next: +!!! info "Database Setup" + If you're using a database, run `jsweb db upgrade` to apply migrations before starting the server. -* **Routing**: Learn how to define routes to handle different URLs in your application. -* **Templating**: Discover how to use Jinja2 templates to render dynamic HTML. -* **Database**: Set up a database and use SQLAlchemy to interact with your data. -* **Forms**: Create forms to handle user input and validation. -* **Admin**: Explore the built-in admin interface for managing your application's data. diff --git a/docs/index.md b/docs/index.md index e0f49da..a5cec69 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,11 +11,15 @@ hide:

- JsWeb Logo + JsWeb Logo

-

- The Blazing-Fast, Modern ASGI Python Web Framework. +

+ The Blazing-Fast, Modern ASGI Python Web Framework +

+ +

+ Build full-stack web apps and APIs with zero JavaScript. Pure Python, pure speed.

@@ -28,81 +32,190 @@ hide: License + + Downloads + +

+ +

+ + Discord + + + Documentation + + + GitHub + +

+ +--- + +

+ πŸ’ Sponsors πŸ’ +

+ +

+ As of now we don't have any sponsors +

+ +

+ Love JsWeb? Help us keep the project alive and growing! We're looking for sponsors to support ongoing development. +

+ +

+ + πŸ’– Sponsor on GitHub + + + πŸ’³ Donate on PayPal +

--- -**JsWeb** is a blazing-fast, lightweight ASGI Python web framework that combines traditional MVC architecture and modern API-first development into a single, unified technology. -Build full-stack web apps and APIs together without switching frameworks. +## ✨ Why JsWeb? + +### Modern Architecture +Built on **ASGI** for blazing-fast performance and true async support. Handle thousands of concurrent connections effortlessly. -## ✨ Key Features +### Zero Configuration +- **Automatic AJAX**: Forms and navigation work like a Single Page Applicationβ€”without writing JavaScript +- **Built-in Admin Panel**: Production-ready interface generated automatically from your models +- **Automatic API Docs**: OpenAPI 3.0.3 documentation generated at `/docs` and `/redoc` -* **πŸš€ Blazing Fast**: Built on ASGI, JsWeb is designed for high concurrency and performance. -* **πŸ”„ Zero-Config AJAX**: Forms and navigation are automatically handled in the background, giving your app a Single Page Application (SPA) feel without writing JavaScript. -* **πŸ›‘οΈ Built-in Security**: CSRF protection, secure session management, and password hashing are enabled by default. -* **πŸ—„οΈ Database Ready**: Integrated SQLAlchemy support with Alembic migrations makes database management a breeze. -* **βš™οΈ Admin Interface**: A production-ready admin panel is generated automatically for your models. -* **🧩 Modular Design**: Use Blueprints to organize your application into reusable components. -* **🎨 Jinja2 Templating**: Powerful and familiar templating engine for rendering dynamic HTML. -* **πŸ› οΈ Powerful CLI**: A comprehensive command-line tool for scaffolding, running, and managing your project. +### Developer First +- **Simple Routing**: Elegant decorator-based route definition +- **Powerful CLI**: Create projects, manage migrations, and run your server with simple commands +- **Modular Blueprints**: Organize code into reusable, maintainable components +- **Full-Featured Forms**: Validation, CSRF protection, and file uploads built-in -## ⚑ Quick Start +### Production Ready +- **Security Built-in**: CSRF protection, secure sessions, password hashing +- **Database Support**: SQLAlchemy with Alembic migrations for any SQL database +- **Jinja2 Templates**: Powerful templating for dynamic HTML rendering +- **Modular Design**: Scale from single-file apps to enterprise applications -Get up and running in seconds. +--- + +## πŸš€ Quick Start (30 seconds) -### 1. Install JsWeb +### 1. Install ```bash pip install jsweb ``` -### 2. Create a Project +### 2. Create Project ```bash -jsweb new my_awesome_app -cd my_awesome_app +jsweb new my_project +cd my_project ``` -### 3. Run the Server +### 3. Run ```bash jsweb run --reload ``` -Visit `http://127.0.0.1:8000` and you'll see your new app running! +That's it! Visit **http://127.0.0.1:8000** and your app is live! πŸŽ‰ -## πŸ“ Example Code +--- -Here is a simple example of a JsWeb application: +## πŸ’‘ See It In Action +**`views.py`** - Define your routes ```python -from jsweb import JsWebApp, render -import config +from jsweb import Blueprint, render -app = JsWebApp(config=config) +views_bp = Blueprint('views') -@app.route("/") +@views_bp.route("/") async def home(req): - return render(req, "index.html", {"message": "Hello from JsWeb!"}) + return render(req, "welcome.html", {"user_name": "Guest"}) + +@views_bp.route("/api/status") +async def status(req): + return {"status": "online", "message": "Hello from JsWeb!"} +``` + +**`app.py`** - Wire it all together +```python +from jsweb import JsWebApp +from views import views_bp +import config -@app.route("/api/data") -async def get_data(req): - return {"status": "success", "data": [1, 2, 3]} +app = JsWebApp(config=config) +app.register_blueprint(views_bp) ``` -## πŸ“š Documentation +That's all you need for a working full-stack application! + +--- + +## πŸ“Š Key Features at a Glance + +| Feature | Benefit | +|---------|---------| +| πŸš€ **ASGI Framework** | Lightning-fast async I/O, handles thousands of requests | +| πŸ”„ **Zero-Config AJAX** | Forms and navigation work like SPAs, no JavaScript needed | +| πŸ—„οΈ **SQLAlchemy + Alembic** | Powerful ORM with seamless migrations | +| πŸ›‘οΈ **Security Built-in** | CSRF, secure sessions, password hashing by default | +| βš™οΈ **Auto Admin Panel** | Production-ready data management interface | +| 🧩 **Blueprints** | Organize code into modular, reusable components | +| 🎨 **Jinja2 Templates** | Powerful template engine with inheritance and macros | +| πŸ“š **Auto API Docs** | OpenAPI documentation generated automatically | +| πŸ› οΈ **Powerful CLI** | Project scaffolding, server, migrations all in one | +| πŸ“± **Responsive** | Works beautifully on all devices | + +--- + +## πŸ“– Next Steps + +Ready to build your next amazing project? -This documentation is organized into two main sections: +

+ + πŸ“š Get Started Guide + + + πŸ”§ View Source Code + + + πŸ’¬ Join Community + +

+ +--- + +## πŸ“š Complete Documentation + +Explore detailed guides and references: -* **[User Guide](getting-started.md)**: Step-by-step guides to help you learn how to use JsWeb, from installation to deployment. -* **[API Reference](cli.md)**: Detailed reference documentation for the JsWeb API and CLI. +- **[Getting Started](getting-started.md)** - Installation, setup, and your first app +- **[Routing](routing.md)** - URL mapping and HTTP methods +- **[Database](database.md)** - Models, queries, and migrations +- **[Templating](templating.md)** - Jinja2 templates and filters +- **[Forms](forms.md)** - Form handling and validation +- **[Blueprints](blueprints.md)** - Modular application architecture +- **[Admin Panel](admin.md)** - Data management interface +- **[CLI Reference](cli.md)** - Command-line tools -## 🀝 Contributing +--- + +## 🀝 Community & Support -JsWeb is an open-source project, and we welcome contributions from the community! Whether you want to fix a bug, add a feature, or improve the documentation, your help is appreciated. +- **GitHub**: [Jsweb-Tech/jsweb](https://github.com/Jsweb-Tech/jsweb) +- **Discord**: [Join our community](https://discord.gg/cqg5wgEVhP) +- **Issues**: [Report bugs or request features](https://github.com/Jsweb-Tech/jsweb/issues) +- **Discussions**: [Ask questions and share ideas](https://github.com/Jsweb-Tech/jsweb/discussions) -Check out our [GitHub repository](https://github.com/Jsweb-Tech/jsweb) to get started. +--- ## πŸ“„ License -This project is licensed under the terms of the MIT license. +JsWeb is licensed under the **MIT License** - free for personal and commercial use. + +--- + diff --git a/docs/routing.md b/docs/routing.md index 50c8b42..cd59578 100644 --- a/docs/routing.md +++ b/docs/routing.md @@ -1,6 +1,16 @@ # Routing -Routing is the process of mapping URLs to the functions that handle them. In JsWeb, you can define routes using the `@app.route()` decorator. +Routing is the process of mapping URLs to the functions that handle them. In JsWeb, defining routes is simple and elegant using decorators. + +## Table of Contents + +- [Basic Routing](#basic-routing) +- [HTTP Methods](#http-methods) +- [URL Parameters](#url-parameters) +- [Type Converters](#type-converters) +- [Route Organization with Blueprints](#route-organization-with-blueprints) +- [Advanced Patterns](#advanced-patterns) +- [Best Practices](#best-practices) ## Basic Routing @@ -17,7 +27,10 @@ async def home(req): return "Hello, World!" ``` -In this example, we're mapping the root URL (`/`) to the `home` function. When a user visits the root of our site, the `home` function will be called, and the string "Hello, World!" will be returned to the user's browser. +In this example, we're mapping the root URL (`/`) to the `home` function. When a user visits the root of your site, the `home` function will be called, and the string "Hello, World!" will be returned to the user's browser. + +!!! note "Async Functions" + JsWeb routes are always async functions. This allows your application to handle multiple concurrent requests efficiently. ## HTTP Methods @@ -33,9 +46,23 @@ async def submit(req): return "Please submit the form." ``` +### Common HTTP Methods + +| Method | Purpose | +|--------|---------| +| `GET` | Retrieve data from the server | +| `POST` | Submit data to the server | +| `PUT` | Update existing resource | +| `DELETE` | Delete a resource | +| `PATCH` | Partially update a resource | +| `HEAD` | Like GET but without the response body | + +!!! tip "RESTful Routes" + Use different HTTP methods to create RESTful APIs. For example, `GET /items` lists items, `POST /items` creates a new item, `PUT /items/1` updates item 1, and `DELETE /items/1` deletes item 1. + ## URL Parameters -You can add variable sections to a URL by marking them with ``. The variable is then passed as a keyword argument to your function. +You can add variable sections to a URL by marking them with angle brackets (``). The variable is then passed as a keyword argument to your function. ```python @app.route("/user/") @@ -43,7 +70,14 @@ async def profile(req, username): return f"Hello, {username}!" ``` -You can also specify a type for the variable, like ``. +### Example + +- URL: `/user/alice` β†’ `username` = `"alice"` +- URL: `/user/bob` β†’ `username` = `"bob"` + +## Type Converters + +You can specify a type for the URL variable to ensure it matches a specific pattern. This helps with data validation and routing specificity. ```python @app.route("/user/id/") @@ -51,10 +85,152 @@ async def profile_by_id(req, user_id): return f"Hello, user number {user_id}!" ``` -JsWeb supports the following converters: +### Supported Type Converters + +| Converter | Description | Example | +|-----------|-------------|---------| +| `string` | Matches any text without slashes (default) | `/user/john` | +| `int` | Matches positive integers | `/user/id/123` | +| `float` | Matches floating-point numbers | `/distance/5.5` | +| `path` | Like string but accepts slashes | `/files/documents/notes.txt` | +| `uuid` | Matches UUID format | `/item/550e8400-e29b-41d4-a716-446655440000` | + +### Example Routes + +```python +# String (default) +@app.route("/search/") +async def search(req, query): + return f"Searching for: {query}" + +# Integer +@app.route("/post/") +async def get_post(req, post_id): + return f"Viewing post {post_id}" + +# Float +@app.route("/temperature/") +async def convert_temp(req, celsius): + fahrenheit = (celsius * 9/5) + 32 + return f"{celsius}Β°C is {fahrenheit}Β°F" + +# Path (allows slashes) +@app.route("/files/") +async def serve_file(req, filepath): + return f"Serving: {filepath}" + +# UUID +@app.route("/item/") +async def get_item(req, item_id): + return f"Item: {item_id}" +``` + +!!! tip "Multiple Parameters" + You can have multiple parameters in a single route: + + ```python + @app.route("/post//comment/") + async def get_comment(req, post_id, comment_id): + return f"Post {post_id}, Comment {comment_id}" + ``` + +## Route Organization with Blueprints + +For larger applications, use Blueprints to organize routes into logical groups: + +```python +from jsweb import Blueprint + +# Create a blueprint for user-related routes +user_bp = Blueprint('users', url_prefix='/users') + +@user_bp.route('/') +async def user_list(req): + return "All users" + +@user_bp.route('/') +async def user_detail(req, user_id): + return f"User {user_id}" + +# Register in app.py +from views import user_bp +app.register_blueprint(user_bp) +``` + +The routes will be available at `/users/` and `/users/`. + +See [Blueprints](blueprints.md) for more details. + +## Advanced Patterns + +### Optional Parameters + +```python +@app.route("/api/items") +@app.route("/api/items/") +async def get_items(req, item_id=None): + if item_id: + return f"Item {item_id}" + return "All items" +``` + +### Query Parameters + +```python +@app.route("/search") +async def search(req): + query = req.query_params.get('q', '') + page = req.query_params.get('page', '1') + return f"Searching for: {query} (page {page})" +``` + +Usage: `/search?q=python&page=2` + +### Dynamic Responses + +```python +@app.route("/api/users/", methods=["GET", "PUT", "DELETE"]) +async def manage_user(req, user_id): + if req.method == "GET": + return {"user_id": user_id, "action": "retrieve"} + elif req.method == "PUT": + return {"user_id": user_id, "action": "update"} + elif req.method == "DELETE": + return {"user_id": user_id, "action": "delete"} +``` + +## Best Practices + +!!! warning "Route Specificity" + Define more specific routes before more general ones. For example, put `/users/me` before `/users/`. + +!!! tip "Naming Conventions" + Use lowercase route paths and clear, descriptive function names: + + ```python + @app.route("/api/users") # βœ“ Good + @app.route("/api/Users") # βœ— Avoid + ``` + +!!! info "Documentation" + Add docstrings to your route handlers: + + ```python + @app.route("/api/items") + async def list_items(req): + """Retrieve all items from the database""" + return {"items": []} + ``` + +!!! note "Error Handling" + Always handle potential errors in your routes: + + ```python + @app.route("/user/") + async def get_user(req, user_id): + user = User.query.get(user_id) + if user is None: + return {"error": "User not found"}, 404 + return user.to_dict() + ``` -* `string` (default) -* `int` -* `float` -* `path` -* `uuid` diff --git a/docs/templating.md b/docs/templating.md index a578829..9ca2baa 100644 --- a/docs/templating.md +++ b/docs/templating.md @@ -2,9 +2,20 @@ JsWeb uses the [Jinja2](https://jinja.palletsprojects.com/) templating engine to render dynamic HTML. Jinja2 is a powerful and flexible templating language that allows you to embed logic and variables directly into your HTML files. +## Table of Contents + +- [Rendering Templates](#rendering-templates) +- [Template Variables](#template-variables) +- [Control Structures](#control-structures) +- [Template Filters](#template-filters) +- [Template Inheritance](#template-inheritance) +- [Macros](#macros) +- [Custom Filters](#custom-filters) +- [Best Practices](#best-practices) + ## Rendering Templates -To render a template, you can use the `render` function from the `jsweb.response` module. +To render a template, use the `render` function from the `jsweb.response` module: ```python from jsweb.response import render @@ -14,7 +25,10 @@ async def home(req): return render(req, "index.html", {"name": "World"}) ``` -In this example, we're rendering the `index.html` template and passing a context dictionary with a `name` variable. The `render` function will look for the `index.html` template in the `templates` folder of your project. +In this example, we're rendering the `index.html` template and passing a context dictionary with a `name` variable. The `render` function automatically looks for templates in the `templates` folder of your project. + +!!! tip "Template Location" + Templates must be in a `templates` folder in your project root. JsWeb automatically discovers and serves them. ## Template Variables @@ -23,25 +37,44 @@ You can use variables in your templates by enclosing them in double curly braces ```html

Hello, {{ name }}!

+

Welcome, {{ user.username }}!

+

Items count: {{ items | length }}

``` -When you render this template, `{{ name }}` will be replaced with the value of the `name` variable from the context dictionary. +### Variable Examples -## Template Logic +```python +# Python +context = { + "name": "Alice", + "user": {"username": "alice", "email": "alice@example.com"}, + "items": [1, 2, 3, 4, 5], + "count": 42 +} +``` -Jinja2 also supports control structures like `if` statements and `for` loops. +```html + +{{ name }} +{{ user.username }} +{{ count + 10 }} +``` -### `if` Statements +## Control Structures + +### if Statements ```html {% if user %}

Hello, {{ user.name }}!

+{% elif guest %} +

Welcome, guest!

{% else %}

Please log in.

{% endif %} ``` -### `for` Loops +### for Loops ```html
    @@ -51,50 +84,278 @@ Jinja2 also supports control structures like `if` statements and `for` loops.
``` +### Loop Variables + +Jinja2 provides special variables inside loops: + +```html +
    + {% for item in items %} +
  • + {{ loop.index }}: {{ item }} + {% if loop.first %}(first item){% endif %} + {% if loop.last %}(last item){% endif %} +
  • + {% endfor %} +
+``` + +| Variable | Description | +|----------|-------------| +| `loop.index` | Current iteration (1-indexed) | +| `loop.index0` | Current iteration (0-indexed) | +| `loop.revindex` | Iterations remaining (descending) | +| `loop.first` | True if first iteration | +| `loop.last` | True if last iteration | +| `loop.length` | Total number of items | + +### Filtering in Loops + +```html +{% for user in users if user.active %} +

{{ user.name }}

+{% endfor %} +``` + +## Template Filters + +Filters modify variables. Use the pipe (`|`) syntax: + +```html +{{ "hello" | upper }} +{{ "HELLO" | lower }} +{{ "hello world" | title }} +{{ items | length }} +{{ price | round(2) }} +{{ date | strftime('%Y-%m-%d') }} +``` + +### Common Filters + +| Filter | Description | Example | +|--------|-------------|---------| +| `upper` | Convert to uppercase | `{{ text \| upper }}` | +| `lower` | Convert to lowercase | `{{ text \| lower }}` | +| `title` | Capitalize first letter of each word | `{{ text \| title }}` | +| `capitalize` | Capitalize first letter | `{{ text \| capitalize }}` | +| `length` | Get length | `{{ items \| length }}` | +| `reverse` | Reverse a list | `{{ items \| reverse }}` | +| `sort` | Sort a list | `{{ items \| sort }}` | +| `join` | Join list items | `{{ items \| join(', ') }}` | +| `default` | Provide default value | `{{ value \| default('N/A') }}` | +| `abs` | Absolute value | `{{ -5 \| abs }}` | +| `round` | Round number | `{{ 3.14159 \| round(2) }}` | +| `int` | Convert to integer | `{{ "42" \| int }}` | +| `string` | Convert to string | `{{ 42 \| string }}` | + +### Chaining Filters + +```html +{{ "hello world" | upper | replace('WORLD', 'JSWEB') }} + +``` + ## Template Inheritance -Template inheritance allows you to build a base template with common elements and then extend it in other templates. +Template inheritance allows you to build a base template with common elements and extend it in other templates. This reduces code duplication. -### `base.html` +### Base Template (base.html) ```html - {% block title %}{% endblock %} + + + {% block title %}My Site{% endblock %} + + {% block extra_css %}{% endblock %} + +
{% block content %}{% endblock %}
+ +
+

© 2024 My Site. All rights reserved.

+
+ + {% block extra_js %}{% endblock %} ``` -### `index.html` +### Child Template (index.html) ```html {% extends "base.html" %} -{% block title %}Home{% endblock %} +{% block title %}Home - My Site{% endblock %} {% block content %}

Welcome to my site!

+

This is the home page.

+{% endblock %} +``` + +### Another Child Template (about.html) + +```html +{% extends "base.html" %} + +{% block title %}About - My Site{% endblock %} + +{% block content %} +

About Us

+

Learn more about our site here.

{% endblock %} ``` +!!! tip "Block Naming" + Use descriptive block names like `{% block title %}`, `{% block content %}`, `{% block extra_css %}` to make your templates clear and organized. + +## Macros + +Macros are reusable template functions. They help eliminate code duplication for complex HTML patterns. + +```html +{# templates/macros.html #} + +{% macro render_user(user) %} +
+

{{ user.name }}

+

Email: {{ user.email }}

+

Status: {% if user.active %}Active{% else %}Inactive{% endif %}

+
+{% endmacro %} + +{% macro render_pagination(current_page, total_pages) %} + +{% endmacro %} +``` + +### Using Macros + +```html +{% from 'macros.html' import render_user, render_pagination %} + +
+ {% for user in users %} + {{ render_user(user) }} + {% endfor %} +
+ +{{ render_pagination(current_page, total_pages) }} +``` + ## Custom Filters -You can add your own custom filters to the Jinja2 environment using the `@app.filter()` decorator. +Create custom filters to extend Jinja2's functionality: ```python -@app.filter("uppercase") -def uppercase_filter(s): - return s.upper() +# app.py +@app.filter("markdown") +def markdown_filter(text): + import markdown + return markdown.markdown(text) + +@app.filter("slugify") +def slugify_filter(text): + return text.lower().replace(' ', '-') + +@app.filter("currency") +def currency_filter(value): + return f"${value:.2f}" + +@app.filter("ordinal") +def ordinal_filter(n): + if n % 10 == 1 and n % 100 != 11: + return f"{n}st" + elif n % 10 == 2 and n % 100 != 12: + return f"{n}nd" + elif n % 10 == 3 and n % 100 != 13: + return f"{n}rd" + else: + return f"{n}th" ``` -You can then use this filter in your templates: +### Using Custom Filters ```html -{{ "hello" | uppercase }} +{{ "Hello World" | slugify }} +{{ 19.99 | currency }} +{{ 21 | ordinal }} +{{ "# Heading" | markdown }} ``` + +!!! tip "Filter Naming" + Use lowercase, descriptive names for your filters. Always test them before using in production. + +## Best Practices + +!!! warning "Security: XSS Prevention" + By default, Jinja2 does NOT auto-escape HTML. Be careful with user input: + + ```html + {# Good: escapes HTML #} + {{ user_input | escape }} + + {# Or use auto-escaping in Python #} + from markupsafe import escape + escaped = escape(user_input) + ``` + +!!! tip "Keep Logic Simple" + Keep complex logic in Python, not templates: + + ```html + {# Bad: Complex logic in template #} + {% if user and user.is_active and user.role == 'admin' and user.permissions.can_edit %} + + {# Good: Simple check in template #} + {% if can_edit %} + ``` + +!!! info "Performance" + Cache templates in production. JsWeb automatically handles this, but ensure `DEBUG=False` in production config. + +!!! tip "Organization" + Organize templates in subdirectories: + + ``` + templates/ + β”œβ”€β”€ base.html + β”œβ”€β”€ auth/ + β”‚ β”œβ”€β”€ login.html + β”‚ └── register.html + β”œβ”€β”€ admin/ + β”‚ └── dashboard.html + └── macros.html + ``` + +!!! note "Comments" + Use Jinja2 comments that won't appear in output: + + ```html + {# This is a comment #} + {# It won't appear in the rendered HTML #} + ``` + diff --git a/mkdocs.yml b/mkdocs.yml index 78156ed..2d426fb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,13 +29,13 @@ theme: - navigation.tabs - navigation.tabs.sticky - navigation.sections - - toc.integrate - navigation.top - search.suggest - search.highlight - content.tabs.link - content.code.annotation - content.code.copy + - toc.follow extra_css: - stylesheets/extra.css diff --git a/overrides/main.html b/overrides/main.html index a908ad8..58dd461 100644 --- a/overrides/main.html +++ b/overrides/main.html @@ -6,7 +6,7 @@ diff --git a/overrides/stylesheets/extra.css b/overrides/stylesheets/extra.css index b61cb67..8e79924 100644 --- a/overrides/stylesheets/extra.css +++ b/overrides/stylesheets/extra.css @@ -25,7 +25,7 @@ visibility: visible !important; display: block !important; width: auto !important; - font-size: 1.5rem !important; /* Increased font size */ + font-size: 1.2rem !important; font-weight: bold !important; }