Jul 22 - Initial code
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
from datetime import datetime
|
||||
|
||||
from flask import Flask, render_template
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_wtf import CSRFProtect
|
||||
from markupsafe import Markup
|
||||
|
||||
from config import Config
|
||||
|
||||
db = SQLAlchemy()
|
||||
csrf = CSRFProtect()
|
||||
|
||||
|
||||
class Section(db.Model):
|
||||
__tablename__ = "section"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
num = db.Column(db.Integer, nullable=False, unique=True)
|
||||
title = db.Column(db.String(160), nullable=False)
|
||||
subtitle = db.Column(db.String(255))
|
||||
sort_order = db.Column(db.Integer, nullable=False, default=0)
|
||||
topics = db.relationship(
|
||||
"Topic",
|
||||
backref="section",
|
||||
order_by="Topic.sort_order",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
|
||||
|
||||
class Topic(db.Model):
|
||||
__tablename__ = "topic"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
section_id = db.Column(
|
||||
db.Integer, db.ForeignKey("section.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
slug = db.Column(db.String(80), nullable=False, unique=True)
|
||||
title = db.Column(db.String(200), nullable=False)
|
||||
body_html = db.Column(db.Text)
|
||||
link_url = db.Column(db.String(500))
|
||||
link_label = db.Column(db.String(120))
|
||||
media_type = db.Column(db.String(16), nullable=False, default="none")
|
||||
media_url = db.Column(db.String(500))
|
||||
media_caption = db.Column(db.String(255))
|
||||
sort_order = db.Column(db.Integer, nullable=False, default=0)
|
||||
|
||||
@property
|
||||
def body(self):
|
||||
# Content is admin-authored and trusted; render as-is.
|
||||
return Markup(self.body_html or "")
|
||||
|
||||
@property
|
||||
def is_youtube(self):
|
||||
u = (self.media_url or "").lower()
|
||||
return "youtube.com" in u or "youtu.be" in u
|
||||
|
||||
|
||||
class AuditLog(db.Model):
|
||||
__tablename__ = "audit_log"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
actor = db.Column(db.String(80))
|
||||
action = db.Column(db.String(40), nullable=False) # create / update / delete
|
||||
entity = db.Column(db.String(40), nullable=False) # section / topic
|
||||
entity_id = db.Column(db.Integer)
|
||||
detail = db.Column(db.String(255))
|
||||
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
||||
|
||||
|
||||
def log_action(actor, action, entity, entity_id=None, detail=None):
|
||||
"""Record an audit row. MUST be called AFTER db.session.commit() of the
|
||||
change it describes, so a failed transaction never leaves an orphan log."""
|
||||
entry = AuditLog(
|
||||
actor=actor, action=action, entity=entity,
|
||||
entity_id=entity_id, detail=detail,
|
||||
)
|
||||
db.session.add(entry)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(Config)
|
||||
db.init_app(app)
|
||||
csrf.init_app(app)
|
||||
|
||||
# Deferred import avoids a circular import: admin.py imports the models and
|
||||
# log_action defined above, which are ready by the time create_app() runs.
|
||||
from admin import admin_bp
|
||||
app.register_blueprint(admin_bp)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
sections = (
|
||||
Section.query.order_by(Section.sort_order, Section.num).all()
|
||||
)
|
||||
return render_template(
|
||||
"index.html",
|
||||
sections=sections,
|
||||
demo_url=app.config["DEMO_CONTACT_URL"],
|
||||
)
|
||||
|
||||
@app.route("/healthz")
|
||||
def healthz():
|
||||
return {"status": "ok"}
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host="127.0.0.1", port=8000, debug=True)
|
||||
Reference in New Issue
Block a user