Implement comprehensive client notes system allowing users to add internal notes about clients that are never visible to clients themselves. Notes support importance flagging, full CRUD operations, and proper access controls. Key Changes: - Add ClientNote model with user/client relationships - Create Alembic migration (025) for client_notes table - Implement full REST API with 9 endpoints - Add client_notes blueprint with CRUD routes - Create UI templates (edit page + notes section on client view) - Add importance toggle with AJAX functionality - Implement permission system (users edit own, admins edit all) Features: - Internal-only notes with rich text support - Mark notes as important for quick identification - Author tracking with timestamps - Cascade delete when client is removed - Mobile-responsive design - i18n support for all user-facing text Testing: - 24 comprehensive model tests - 23 route/integration tests - Full coverage of CRUD operations and permissions Documentation: - Complete feature guide in docs/CLIENT_NOTES_FEATURE.md - API documentation with examples - Troubleshooting section - Updated main docs index Database: - Migration revision 025 (depends on 024) - Fixed PostgreSQL boolean default value issue - 4 indexes for query performance - CASCADE delete constraint on client_id This feature addresses the need for teams to track important information about clients internally without exposing sensitive notes to client-facing interfaces or documents.
Database Migrations with Flask-Migrate
This directory contains the database migration system for TimeTracker, now standardized on Flask-Migrate with proper versioning.
Overview
The migration system has been updated from custom Python scripts to use Flask-Migrate, which provides:
- Standardized migrations using Alembic
- Version tracking for all database changes
- Rollback capabilities to previous versions
- Automatic schema detection from SQLAlchemy models
- Cross-database compatibility (PostgreSQL, SQLite)
Quick Start
1. Initialize Migrations (First Time Only)
flask db init
2. Create Your First Migration
flask db migrate -m "Initial database schema"
3. Apply Migrations
flask db upgrade
Migration Commands
Basic Commands
flask db init- Initialize migrations directoryflask db migrate -m "Description"- Create a new migrationflask db upgrade- Apply pending migrationsflask db downgrade- Rollback last migrationflask db current- Show current migration versionflask db history- Show migration history
Advanced Commands
flask db show <revision>- Show specific migration detailsflask db stamp <revision>- Mark database as being at specific revisionflask db heads- Show current heads (for branched migrations)
Migration Workflow
1. Make Model Changes
Edit your SQLAlchemy models in app/models/:
# Example: Add a new column
class User(db.Model):
# ... existing fields ...
phone_number = db.Column(db.String(20), nullable=True)
2. Generate Migration
flask db migrate -m "Add phone number to users"
3. Review Generated Migration
Check the generated migration file in migrations/versions/:
def upgrade():
op.add_column('users', sa.Column('phone_number', sa.String(length=20), nullable=True))
def downgrade():
op.drop_column('users', 'phone_number')
4. Apply Migration
flask db upgrade
5. Verify Changes
Check the migration status:
flask db current
Migration Files Structure
migrations/
├── versions/ # Migration files
│ ├── 001_initial_schema.py
│ ├── 002_add_phone_number.py
│ └── ...
├── env.py # Migration environment
├── script.py.mako # Migration template
├── alembic.ini # Alembic configuration
└── README.md # This file
Transition from Old System
If you're migrating from the old custom migration system:
1. Backup Your Database
# PostgreSQL
pg_dump --format=custom --dbname="$DATABASE_URL" --file=backup_$(date +%Y%m%d_%H%M%S).dump
# SQLite
cp instance/timetracker.db backup_timetracker_$(date +%Y%m%d_%H%M%S).db
2. Use Migration Management Script
python migrations/manage_migrations.py
3. Or Manual Migration
# Initialize Flask-Migrate
flask db init
# Create initial migration (captures current schema)
flask db migrate -m "Initial schema from existing database"
# Apply migration
flask db upgrade
Best Practices
1. Migration Naming
Use descriptive names for migrations:
flask db migrate -m "Add user profile fields"
flask db migrate -m "Create project categories table"
flask db migrate -m "Add invoice payment tracking"
2. Testing Migrations
Always test migrations on a copy of your production data:
# Test upgrade
flask db upgrade
# Test downgrade
flask db downgrade
# Verify data integrity
3. Backup Before Migrations
# Always backup before major migrations
flask db backup # Custom command
# or
pg_dump --format=custom --dbname="$DATABASE_URL" --file=pre_migration_backup.dump
4. Review Generated Code
Always review auto-generated migrations before applying:
- Check the
upgrade()function - Verify the
downgrade()function - Ensure data types and constraints are correct
Troubleshooting
Common Issues
1. Migration Already Applied
# Check current status
flask db current
# If migration is already applied, stamp the database
flask db stamp <revision>
2. Migration Conflicts
# Show migration heads
flask db heads
# Merge branches if needed
flask db merge -m "Merge migration branches" <revision1> <revision2>
3. Database Out of Sync
# Check migration history
flask db history
# Reset to specific revision
flask db stamp <revision>
4. Model Import Errors
Ensure all models are imported in your application:
# In app/__init__.py or similar
from app.models import User, Project, TimeEntry, Task, Settings, Invoice, Client
Getting Help
- Check the migration status:
flask db current - Review migration history:
flask db history - Check Alembic logs for detailed error messages
- Verify database connection and permissions
Advanced Features
Custom Migration Operations
You can add custom operations in your migrations:
def upgrade():
# Custom data migration
op.execute("UPDATE users SET role = 'user' WHERE role IS NULL")
# Custom table operations
op.create_index('custom_idx', 'table_name', ['column_name'])
Data Migrations
For complex data migrations, use the op.execute() method:
def upgrade():
# Migrate existing data
op.execute("""
INSERT INTO new_table (id, name)
SELECT id, name FROM old_table
""")
Conditional Migrations
Handle different database types:
def upgrade():
# PostgreSQL-specific operations
if op.get_bind().dialect.name == 'postgresql':
op.execute('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"')
Environment Variables
Ensure these environment variables are set:
export FLASK_APP=app.py
export DATABASE_URL="postgresql://user:pass@localhost/dbname"
# or
export DATABASE_URL="sqlite:///instance/timetracker.db"
CI/CD Integration
For automated deployments, include migration steps:
# Example GitHub Actions step
- name: Run database migrations
run: |
flask db upgrade
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
Support
For migration-related issues:
- Check this README
- Review Flask-Migrate documentation: https://flask-migrate.readthedocs.io/
- Check Alembic documentation: https://alembic.sqlalchemy.org/
- Review generated migration files for errors