flask site buildout #2

Merged
finn merged 25 commits from mgtut1 into master 2024-08-05 08:41:03 +00:00
10 changed files with 129 additions and 11 deletions
Showing only changes of commit 9db540fc1c - Show all commits

View File

@ -8,6 +8,9 @@ pip install python-dotenv
pip install flask-wtf pip install flask-wtf
pip install flask-sqlalchemy pip install flask-sqlalchemy
pip install flask-migrate pip install flask-migrate
pip install flask-login
pip install email-validator
pip freeze > requirements.txt pip freeze > requirements.txt
``` ```

View File

@ -2,11 +2,14 @@ from flask import Flask
from config import Config from config import Config
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_login import LoginManager
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(Config) app.config.from_object(Config)
db = SQLAlchemy(app) db = SQLAlchemy(app)
migrate = Migrate(app, db) migrate = Migrate(app, db)
login = LoginManager(app)
login.login_view = 'login'
from app import routes, models from app import routes, models

View File

@ -1,6 +1,9 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import DataRequired from wtforms.validators import DataRequired, ValidationError, Email, EqualTo
import sqlalchemy as sa
from app import db
from app.models import User
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()]) username = StringField('Username', validators=[DataRequired()])
@ -8,3 +11,19 @@ class LoginForm(FlaskForm):
remember_me = BooleanField('Remember Me') remember_me = BooleanField('Remember Me')
submit = SubmitField('Sign In') submit = SubmitField('Sign In')
class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
password2 = PasswordField('Repeat Password', validators=[DataRequired()])
submit = SubmitField('Register')
def validate_username(self, username):
user = db.session.scalar(sa.select(User).where(User.username == username.data))
if user is not None:
raise ValidationError('Please use a different username.')
def validate_email(self, email):
user = db.session.scalar(sa.select(User).where(User.email == email.data))
if user is not None:
raise ValidationError('Please use a different email address.')

View File

@ -2,15 +2,21 @@ from datetime import datetime, timezone
from typing import Optional from typing import Optional
import sqlalchemy as sa import sqlalchemy as sa
import sqlalchemy.orm as so import sqlalchemy.orm as so
from app import db from app import db, login
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
class User(db.Model): class User(UserMixin, db.Model):
id: so.Mapped[int] = so.mapped_column(primary_key=True) id: so.Mapped[int] = so.mapped_column(primary_key=True)
username: so.Mapped[str] = so.mapped_column(sa.String(64), index=True, unique=True) username: so.Mapped[str] = so.mapped_column(sa.String(64), index=True, unique=True)
email: so.Mapped[str] = so.mapped_column(sa.String(120), index=True, unique=True) email: so.Mapped[str] = so.mapped_column(sa.String(120), index=True, unique=True)
password_hash: so.Mapped[Optional[str]] = so.mapped_column(sa.String(256)) password_hash: so.Mapped[Optional[str]] = so.mapped_column(sa.String(256))
posts: so.WriteOnlyMapped['Post'] = so.relationship(back_populates='author') posts: so.WriteOnlyMapped['Post'] = so.relationship(back_populates='author')
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)
def __repr__(self): def __repr__(self):
return '<User {}>'.format(self.username) return '<User {}>'.format(self.username)
@ -25,3 +31,6 @@ class Post(db.Model):
return '<Post {}>'.format(self.body) return '<Post {}>'.format(self.body)
@login.user_loader
def load_user(id):
return db.session.get(User, int(id))

View File

@ -1,9 +1,14 @@
from flask import render_template, flash, redirect, url_for from flask import render_template, flash, redirect, url_for, request
from app import app from urllib.parse import urlsplit
from app.forms import LoginForm from app import app, db
from app.forms import LoginForm, RegistrationForm
from flask_login import current_user, login_user, logout_user, login_required
import sqlalchemy as sa
from app.models import User
@app.route('/') @app.route('/')
@app.route('/index') @app.route('/index')
@login_required
def index(): def index():
user = {'username': 'Finnaa'} user = {'username': 'Finnaa'}
@ -18,14 +23,42 @@ def index():
} }
] ]
#return posts; #return posts;
return render_template('index.html', title='Home', user=user, posts=posts) return render_template('index.html', title='Home', posts=posts)
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm() form = LoginForm()
if form.validate_on_submit(): if form.validate_on_submit():
flash('Login requested for user {}, remember_me={}'.format(form.username.data, form.remember_me.data)) user = db.session.scalar(sa.select(User).where(User.username == form.username.data))
return redirect(url_for('index')) if user is None or not user.check_password(form.password.data):
flash('Invalid u or p')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
if not next_page or urlsplit(next_page).netloc != '':
next_page = url_for('index')
return redirect(next_page)
return render_template('login.html', title='Sign In', form=form) return render_template('login.html', title='Sign In', form=form)
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('User has been created.')
return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form)

View File

@ -12,7 +12,11 @@
<div> <div>
blgo: blgo:
<a href="{{ url_for('index') }}">home</a> <a href="{{ url_for('index') }}">home</a>
{% if current_user.is_anonymous %}
<a href="{{ url_for('login') }}">login</a> <a href="{{ url_for('login') }}">login</a>
{% else %}
<a href="{{ url_for('logout') }}">logout</a>
{% endif %}
</div> </div>
<hr> <hr>
@ -20,7 +24,7 @@
{% if messages %} {% if messages %}
<ul> <ul>
{% for message in messages %} {% for message in messages %}
<li>{{ message }}</li> <p class="notice">{{ message }}</p>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}

View File

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h1>Helloo, {{ user.username }}!</h1> <h1>Hello, {{ current_user.username }}!</h1>
{% for post in posts %} {% for post in posts %}
<div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div> <div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
{% endfor %} {% endfor %}

View File

@ -22,4 +22,7 @@
<p>{{ form.submit }}</p> <p>{{ form.submit }}</p>
</form> </form>
<p><a href="{{ url_for('register') }}">Register Here</a></p>
{% endblock %} {% endblock %}

View File

@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block content %}
<h1>Register</h1>
<form action="" method="post">
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.email.label }}<br>
{{ form.email(size=64) }}<br>
{% for error in form.email.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=64) }}<br>
{% for error in form.password.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>
{{ form.password2.label }}<br>
{{ form.password2(size=64) }}<br>
{% for error in form.password2.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit }}</p>
</form>
{% endblock %}

View File

@ -1,11 +1,15 @@
alembic==1.13.2 alembic==1.13.2
blinker==1.8.2 blinker==1.8.2
click==8.1.7 click==8.1.7
dnspython==2.6.1
email_validator==2.2.0
Flask==3.0.3 Flask==3.0.3
Flask-Login==0.6.3
Flask-Migrate==4.0.7 Flask-Migrate==4.0.7
Flask-SQLAlchemy==3.1.1 Flask-SQLAlchemy==3.1.1
Flask-WTF==1.2.1 Flask-WTF==1.2.1
greenlet==3.0.3 greenlet==3.0.3
idna==3.7
itsdangerous==2.2.0 itsdangerous==2.2.0
Jinja2==3.1.4 Jinja2==3.1.4
Mako==1.3.5 Mako==1.3.5