From 4ac2861d28676bc886cf80b43a087f1f0044696a Mon Sep 17 00:00:00 2001 From: Matt Arnold Date: Wed, 9 Apr 2025 18:18:37 -0400 Subject: Threaded blogging works --- .gitignore | 3 +- app.py | 92 +++++++++++++++++++++++++++++++++++++++++++--------- forms.py | 16 +++++++-- models.py | 39 ++++++++++++++++++++-- requirements.txt | 2 ++ templates/login.html | 24 ++++++++++++++ templates/post.html | 24 +++++++++++--- 7 files changed, 175 insertions(+), 25 deletions(-) create mode 100644 templates/login.html diff --git a/.gitignore b/.gitignore index c2014f5..1d2d98a 100644 --- a/.gitignore +++ b/.gitignore @@ -115,4 +115,5 @@ cython_debug/ # Local configuration files config.py -*.db \ No newline at end of file +*.db +AI_HOLDING/* \ No newline at end of file diff --git a/app.py b/app.py index 219b160..137a6ca 100644 --- a/app.py +++ b/app.py @@ -1,46 +1,108 @@ -from flask import Flask, render_template, request, redirect, url_for +from urllib.parse import urlparse as url_parse + + +from flask import Flask, render_template, request, redirect, url_for, flash from markdown import markdown, Markdown from config import config -from models import Post, db -from forms import PostForm +from models import Post, db, get_replies, Faccet +from models import User as NewUser +from forms import PostForm, LoginForm +from flask_login import ( + login_user, + logout_user, + login_required, + current_user, + LoginManager, +) import os + app = Flask(__name__) -app.config.from_object('config') +app.config.from_object("config") SECRET_KEY = os.urandom(32) -app.config['SECRET_KEY'] = SECRET_KEY +app.config["SECRET_KEY"] = SECRET_KEY app.jinja_options = app.jinja_options.copy() app.jinja_env.add_extension(Markdown) app.jinja_env.filters["markdown"] = markdown +login = LoginManager(app) +login.login_view = "login" + + +@login.user_loader +def load_user(uid): + return NewUser.get(NewUser.id == uid) + + +@app.route("/login", methods=["GET", "POST"]) +def login(): + form = LoginForm() + if form.validate_on_submit(): # noqa + user = NewUser.get(NewUser.username == form.username.data) + if user is None or not user.check_password(form.password.data): + flash("Invalid username or password") + return redirect(url_for("login")) + login_user(user, remember=form.remember_me.data) + next_page = request.args.get("next") + if not next_page or url_parse(next_page).netloc != "": + next_page = url_for("index") + return redirect(next_page) + + return render_template("login.html", form=form) + @app.before_request def before_request(): db.connect() + @app.after_request def after_request(response): db.close() return response -@app.route('/') + +@app.route("/") +@login_required def index(): - posts = Post.select().order_by(Post.created_at.desc()) - return render_template('index.html', posts=posts) + posts = Post.select().where(Post.parent == 0).order_by(Post.created_at.desc()) + return render_template("index.html", posts=posts) + -@app.route('/post/') +@app.route("/logout") +@login_required +def logout(): + flash("Goodbye!") + logout_user() + return redirect(url_for("login")) + + +@app.route("/post/") +@login_required def post(post_id): post = Post.get(Post.id == post_id) - return render_template('post.html', post=post) + replies = get_replies(post_id) + return render_template("post.html", post=post, replies=replies) -@app.route('/create', methods=['GET', 'POST']) + +@app.route("/create", methods=["GET", "POST"]) +@login_required def create(): form = PostForm() + replyto = request.args.get("reply", 0) + userctx = NewUser.get(NewUser.username == current_user.username) + asfaccet = Faccet.get(Faccet.name == userctx.default_faccet) if form.validate_on_submit(): - Post.create(title=form.title.data, content=form.content.data) - return redirect(url_for('index')) - return render_template('create.html', form=form) + Post.create( + title=form.title.data, + content=form.content.data, + authour=asfaccet, + parent=replyto, + ) + return redirect(url_for("index")) + return render_template("create.html", form=form) + -if __name__ == '__main__': +if __name__ == "__main__": app.run(debug=True, port=5052) diff --git a/forms.py b/forms.py index d8aa8c0..be0171a 100644 --- a/forms.py +++ b/forms.py @@ -1,8 +1,18 @@ from flask_wtf import FlaskForm -from wtforms import StringField, TextAreaField, SubmitField +from wtforms import StringField, TextAreaField, SubmitField, PasswordField, BooleanField from wtforms.validators import DataRequired + class PostForm(FlaskForm): - title = StringField('Title', validators=[DataRequired()]) - content = TextAreaField('Content', validators=[DataRequired()]) + title = StringField("Title", validators=[DataRequired()]) + content = TextAreaField("Content", validators=[DataRequired()]) submit = SubmitField("Toot!") + + +class LoginForm(FlaskForm): + """Login Form""" + + username = StringField("Username", validators=[DataRequired()]) + password = PasswordField("Password", validators=[DataRequired()]) + remember_me = BooleanField("Remember Me") + submit = SubmitField("Sign In") diff --git a/models.py b/models.py index 75af58b..26934d9 100644 --- a/models.py +++ b/models.py @@ -1,15 +1,50 @@ -from peewee import Model, CharField, TextField, DateTimeField, SqliteDatabase +from peewee import Model, CharField, TextField, DateTimeField, SqliteDatabase, BlobField +from peewee import IntegerField, ForeignKeyField +from flask_login import UserMixin +from werkzeug.security import check_password_hash, generate_password_hash from datetime import datetime from config import dbloc + # from app import db db = SqliteDatabase(dbloc) + + class BaseModel(Model): class Meta: database = db + +class User(UserMixin, BaseModel): + id = IntegerField(primary_key=True) + username = CharField(max_length=64, index=True, unique=True) + default_faccet = TextField(null=False) + password_hash = CharField(max_length=128) + + def set_password(self, password): + self.password_hash = generate_password_hash(password) + + def check_password(self, password): + return check_password_hash(self.password_hash, password) + + +class Faccet(BaseModel): + user_belongs = ForeignKeyField(User, backref="parts") + name = TextField(unique=True) + picture = BlobField() + bio = TextField() + + class Post(BaseModel): + id = IntegerField(primary_key=True) + authour = ForeignKeyField(Faccet, backref="creator") + parent = IntegerField(default=0) title = CharField() content = TextField() created_at = DateTimeField(default=datetime.now) -db.create_tables([Post]) + +def get_replies(post_id): + return Post.select().where(Post.parent == post_id).order_by(Post.created_at.desc()) + + +db.create_tables([User, Post, Faccet]) diff --git a/requirements.txt b/requirements.txt index 125950d..70cfdae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,12 @@ blinker==1.9.0 click==8.1.8 Flask==3.1.0 +Flask-Login==0.6.3 Flask-WTF==1.2.2 itsdangerous==2.2.0 Jinja2==3.1.6 MarkupSafe==3.0.2 +mistletoe==1.4.0 peewee==3.17.9 Werkzeug==3.1.3 WTForms==3.2.1 diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..3a46511 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} + +{% block content %} +

Welcome to VibeEngine

+
+ {{ form.hidden_tag() }} +

+ {{ form.username.label }}
+ {{ form.username(size=32) }}
+ {% for error in form.username.errors %} + [{{ error }}] + {% endfor %} +

+

+ {{ form.password.label }}
+ {{ form.password(size=32) }}
+ {% for error in form.password.errors %} + [{{ error }}] + {% endfor %} +

+

{{ form.remember_me() }} {{ form.remember_me.label }}

+

{{ form.submit() }}

+
+{% endblock %} \ No newline at end of file diff --git a/templates/post.html b/templates/post.html index 8272d37..d759da4 100644 --- a/templates/post.html +++ b/templates/post.html @@ -1,8 +1,24 @@ {% extends 'base.html' %} {% block content %} -

{{ post.title }}

-

{{ post.created_at.strftime('%Y-%m-%d %H:%M') }}

-
{{ post.content|markdown|safe }}
+{% if post.parent != 0 %} + Previous +
+{% endif %} +

{{ post.title }}

+

{{ post.created_at.strftime('%Y-%m-%d %H:%M') }}

+

Authour: {{post.authour.name}}

+
{{ post.content|markdown|safe }}
+
+ Reply ~ Back to posts -{% endblock %} +
+
+

Replies to this

+
+
    +
  • {% for reply in replies %} {{ reply.title }} - {{ + reply.created_at.strftime('%Y-%m-%d') }}
  • {% endfor %} +
+
+{% endblock %} \ No newline at end of file -- cgit 1.4.1-2-gfad0