Database Migration and SMS Verification Implementation in Flask

Database Models

Create models.py in the ihome driectory:

# -*- coding:utf-8 -*-

from datetime import datetime
from . import db


class BaseModel(object):
    """Base model class providing created_at and updated_at timestamps"""

    created_at = db.Column(db.DateTime, default=datetime.now)
    updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)


class User(BaseModel, db.Model):
    """User model"""

    __tablename__ = "tb_user_profile"

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(32), unique=True, nullable=False)
    password_hash = db.Column(db.String(128), nullable=False)
    phone = db.Column(db.String(11), unique=True, nullable=False)
    real_name = db.Column(db.String(32))
    id_number = db.Column(db.String(20))
    avatar_url = db.Column(db.String(128))

    houses = db.relationship("House", backref="owner")
    bookings = db.relationship("Order", backref="customer")


class District(BaseModel, db.Model):
    """District/Area model"""

    __tablename__ = "tb_district_info"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), nullable=False)

    houses = db.relationship("House", backref="district")


# Many-to-many association table for house amenities
house_amenities = db.Table(
    "tb_house_amenities",
    db.Column("house_id", db.Integer, db.ForeignKey("tb_house_info.id"), primary_key=True),
    db.Column("amenity_id", db.Integer, db.ForeignKey("tb_amenity_info.id"), primary_key=True)
)


class House(BaseModel, db.Model):
    """House listing model"""

    __tablename__ = "tb_house_info"

    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(64), nullable=False)
    price = db.Column(db.Integer, default=0)  # Price in cents
    address = db.Column(db.String(512), default="")
    room_count = db.Column(db.Integer, default=1)
    area_size = db.Column(db.Integer, default=0)
    floor_plan = db.Column(db.String(32), default="")
    max_guests = db.Column(db.Integer, default=1)
    bed_config = db.Column(db.String(64), default="")
    deposit = db.Column(db.Integer, default=0)
    min_nights = db.Column(db.Integer, default=1)
    max_nights = db.Column(db.Integer, default=0)  # 0 means unlimited
    booking_count = db.Column(db.Integer, default=0)
    cover_image = db.Column(db.String(256), default="")

    owner_id = db.Column(db.Integer, db.ForeignKey("tb_user_profile.id"), nullable=False)
    district_id = db.Column(db.Integer, db.ForeignKey("tb_district_info.id"), nullable=False)

    amenities = db.relationship("Amenity", secondary=house_amenities)
    images = db.relationship("HouseImage")
    bookings = db.relationship("Order", backref="property")


class Amenity(BaseModel, db.Model):
    """Amenity/Facility model"""

    __tablename__ = "tb_amenity_info"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), nullable=False)


class HouseImage(BaseModel, db.Model):
    """House image gallery model"""

    __tablename__ = "tb_house_image"

    id = db.Column(db.Integer, primary_key=True)
    url = db.Column(db.String(256), nullable=False)

    house_id = db.Column(db.Integer, db.ForeignKey("tb_house_info.id"), nullable=False)


class Order(BaseModel, db.Model):
    """Booking order model"""

    __tablename__ = "tb_order_info"

    id = db.Column(db.Integer, primary_key=True)

    user_id = db.Column(db.Integer, db.ForeignKey("tb_user_profile.id"), nullable=False)
    house_id = db.Column(db.Integer, db.ForeignKey("tb_house_info.id"), nullable=False)

    check_in = db.Column(db.DateTime, nullable=False)
    check_out = db.Column(db.DateTime, nullable=False)
    nights = db.Column(db.Integer, nullable=False)
    price_per_night = db.Column(db.Integer, nullable=False)
    total_amount = db.Column(db.Integer, nullable=False)
    status = db.Column(
        db.Enum(
            "PENDING",
            "UNPAID",
            "PAID",
            "REVIEW_PENDING",
            "COMPLETED",
            "CANCELLED",
            "REJECTED"
        ),
        default="PENDING",
        index=True
    )
    notes = db.Column(db.Text)

Import the models in api_1_0/index.py:

from ihome import models

Datbaase Migration

Initialize the migration directory:

flask db init

Generate migration script:

flask db migrate -m "Initial migration"

Apply changes to database:

flask db upgrade

Serving Static Files

Place all required static files in the static directory.

Create a custom URL converter in utils/converters.py:

# coding:utf8
from werkzeug.routing import BaseConverter

class RegexConverter(BaseConverter):
    def __init__(self, url_map, *args):
        super(RegexConverter, self).__init__(url_map)
        self.pattern = args[0]

Register the converter in ihome/__init__.py:

app.url_map.converters['regex'] = RegexConverter

Create web_html.py to serve static files and set CSRF token:

# coding:utf8
from flask import Blueprint, current_app, make_response
from flask_wtf.csrf import generate_csrf

html_blueprint = Blueprint('html', __name__)

@html_blueprint.route('/<regex(r".*"):path>') 
def serve_html(path):
    if not path:
        path = 'index.html'

    if path != 'favicon.ico':
        path = 'html/' + path

    csrf_token = generate_csrf()
    response = make_response(current_app.send_static_file(path))
    response.set_cookie('csrf_token', csrf_token)
    return response

Register the blueprint in ihome/__init__.py:

from web_html import html_blueprint
app.register_blueprint(html_blueprint)

Access the application at:

http://127.0.0.1:5000/login

Image Captcha Verification

Place the captcha package in ihome/utils/captcha/.

Create the captcha endpoint in api_1_0/verify_code.py:

# coding=utf-8

import logging
from . import api_blueprint
from ihome.utils.captcha.captcha import captcha
from ihome import redis_conn
from ihome import http_code as HTTP
from flask import jsonify, make_response


@api_blueprint.route('/captchas/<captcha_id>')
def generate_captcha(captcha_id):
    # Generate captcha text and image
    captcha_key, captcha_text, captcha_image = captcha.generate_captcha()

    try:
        redis_conn.setex(f'captcha:{captcha_id}', 180, captcha_text)
    except Exception as e:
        logging.error(f"Redis error: {e}")
        return jsonify({
            'error_code': HTTP.DBERR,
            'error_msg': 'Failed to store captcha'
        }), 500

    response = make_response(captcha_image)
    response.headers['Content-Type'] = 'image/jpeg'

    return response

Test in browser:

http://127.0.0.1:5000/captchas/uuid-here

Frontend Implementation

// Generate unique identifier for captcha
var captchaId = generateUUID();
// Build URL and set to image source
var imageUrl = "/captchas/" + captchaId;
document.querySelector(".captcha-img").src = imageUrl;

SMS Verification

Integrate the cloud SMS provider SDK and run the template SMS example script.

Key Patterns Summary

Many-to-Many Table Definition

# Association table for many-to-many relationships
amenity_association = db.Table(
    "tb_house_amenities",
    db.Column("house_id", db.Integer, db.ForeignKey("tb_house.id"), primary_key=True),
    db.Column("amenity_id", db.Integer, db.ForeignKey("tb_amenity.id"), primary_key=True)
)

Static File Serving

app.send_static_file(filename) searches the static folder for the given file.

CSRF Token Generation

from flask_wtf.csrf import generate_csrf
csrf_token = generate_csrf()

Browser Automatic Requests

Browsers automatically request favicon.ico on every page load.

Redis Storage with Expiry

redis_conn.setex('captcha:123', 180, 'abc123')

Image Response Headers

from flask import make_response
response = make_response(image_data)
response.headers['Content-Type'] = 'image/jpeg'

Tags: Flask database migration Redis csrf CAPTCHA

Posted on Mon, 29 Jun 2026 16:21:58 +0000 by gezeala