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