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'