diff options
author | Matt Arnold | 2025-04-09 18:18:37 -0400 |
---|---|---|
committer | Matt Arnold | 2025-04-09 18:18:37 -0400 |
commit | 4ac2861d28676bc886cf80b43a087f1f0044696a (patch) | |
tree | 48dd9e5e62860b137c4363f29d921646ff6f9889 | |
parent | d1745a9c1e46d43af005ac966cf4170192b76f97 (diff) |
Threaded blogging works
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | app.py | 92 | ||||
-rw-r--r-- | forms.py | 16 | ||||
-rw-r--r-- | models.py | 39 | ||||
-rw-r--r-- | requirements.txt | 2 | ||||
-rw-r--r-- | templates/login.html | 24 | ||||
-rw-r--r-- | templates/post.html | 24 |
7 files changed, 175 insertions, 25 deletions
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/<int:post_id>') +@app.route("/logout") +@login_required +def logout(): + flash("Goodbye!") + logout_user() + return redirect(url_for("login")) + + +@app.route("/post/<int:post_id>") +@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 %} +<h1>Welcome to VibeEngine</h1> +<form action="" method="post" novalidate> + {{ form.hidden_tag() }} + <p> + {{ form.username.label }}<br> + {{ form.username(size=32) }}<br> + {% for error in form.username.errors %} + <span style="color: peru;">[{{ error }}]</span> + {% endfor %} + </p> + <p> + {{ form.password.label }}<br> + {{ form.password(size=32) }}<br> + {% for error in form.password.errors %} + <span style="color: peru;">[{{ error }}]</span> + {% endfor %} + </p> + <p>{{ form.remember_me() }} {{ form.remember_me.label }}</p> + <p>{{ form.submit() }}</p> +</form> +{% 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 %} - <h2>{{ post.title }}</h2> - <p>{{ post.created_at.strftime('%Y-%m-%d %H:%M') }}</p> - <div>{{ post.content|markdown|safe }}</div> +{% if post.parent != 0 %} +<a href="{{url_for('post', post_id=post.parent)}}"> Previous</a> +<hr> +{% endif %} +<h2>{{ post.title }}</h2> +<p>{{ post.created_at.strftime('%Y-%m-%d %H:%M') }}</p> +<p> Authour: {{post.authour.name}}</p> +<div>{{ post.content|markdown|safe }}</div> +<div class="post-actions"> + <a href="{{ url_for('create', reply=post.id)}}"> Reply</a> ~ <a href="{{ url_for('index') }}">Back to posts</a> -{% endblock %} +</div> +<hr> +<h3> Replies to this</h3> +<div class="post-replies"> + <ul> + <li> {% for reply in replies %} <a href="{{ url_for('post', post_id=reply.id) }}">{{ reply.title }}</a> - {{ + reply.created_at.strftime('%Y-%m-%d') }}</li> {% endfor %} + </ul> +</div> +{% endblock %} \ No newline at end of file |