Chapter 6

This commit is contained in:
2023-06-03 17:53:32 +02:00
parent 428e022bd4
commit 4dd7d11190
10 changed files with 184 additions and 4 deletions

BIN
app.db

Binary file not shown.

View File

@@ -1,6 +1,18 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms import (
from wtforms.validators import DataRequired, EqualTo, Email, ValidationError StringField,
PasswordField,
BooleanField,
SubmitField,
TextAreaField,
)
from wtforms.validators import (
DataRequired,
EqualTo,
Email,
ValidationError,
Length,
)
from app.models import User from app.models import User
@@ -29,3 +41,9 @@ class RegistrationForm(FlaskForm):
user = User.query.filter_by(email=email.data).first() user = User.query.filter_by(email=email.data).first()
if user is not None: if user is not None:
raise ValidationError("Please use a different email address.") raise ValidationError("Please use a different email address.")
class EditProfileForm(FlaskForm):
username = StringField("Username", validators=[DataRequired()])
about_me = TextAreaField("About me", validators=[Length(min=0, max=140)])
submit = SubmitField("Submit")

View File

@@ -1,4 +1,5 @@
from datetime import datetime from datetime import datetime
from hashlib import md5
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin from flask_login import UserMixin
from app import db, login from app import db, login
@@ -15,6 +16,8 @@ class User(UserMixin, db.Model):
email = db.Column(db.String(120), index=True, unique=True) email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128)) password_hash = db.Column(db.String(128))
posts = db.relationship("Post", backref="author", lazy="dynamic") posts = db.relationship("Post", backref="author", lazy="dynamic")
about_me = db.Column(db.String(140))
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
def __repr__(self): def __repr__(self):
return "<User {}>".format(self.username) return "<User {}>".format(self.username)
@@ -25,6 +28,12 @@ class User(UserMixin, db.Model):
def check_password(self, password): def check_password(self, password):
return check_password_hash(self.password_hash, password) return check_password_hash(self.password_hash, password)
def avatar(self, size):
digest = md5(self.email.lower().encode("utf-8")).hexdigest()
return "https://www.gravatar.com/avatar/{}?d=identicon&s={}".format(
digest, size
)
class Post(db.Model): class Post(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)

View File

@@ -1,18 +1,29 @@
from datetime import datetime
from flask import render_template, flash, redirect, url_for, request from flask import render_template, flash, redirect, url_for, request
from flask_login import current_user, login_user, logout_user, login_required from flask_login import current_user, login_user, logout_user, login_required
from werkzeug.urls import url_parse from werkzeug.urls import url_parse
from app import app, db from app import app, db
from app.forms import LoginForm, RegistrationForm from app.forms import LoginForm, RegistrationForm, EditProfileForm
from app.models import User from app.models import User
@app.before_request
def before_request():
if current_user.is_authenticated:
current_user.last_seen = datetime.utcnow()
db.session.commit()
@app.route("/") @app.route("/")
@app.route("/index") @app.route("/index")
@login_required @login_required
def index(): def index():
posts = [ posts = [
{"author": {"username": "John"}, "body": "Beautiful day in Portland!"}, {"author": {"username": "John"}, "body": "Beautiful day in Portland!"},
{"author": {"username": "Susan"}, "body": "The Avengers movie was so cool!"}, {
"author": {"username": "Susan"},
"body": "The Avengers movie was so cool!",
},
] ]
return render_template("index.html", title="Home", posts=posts) return render_template("index.html", title="Home", posts=posts)
@@ -54,3 +65,32 @@ def register():
flash("Congratulations, you are now a registered user!") flash("Congratulations, you are now a registered user!")
return redirect(url_for("login")) return redirect(url_for("login"))
return render_template("register.html", title="Register", form=form) return render_template("register.html", title="Register", form=form)
@app.route("/user/<username>")
@login_required
def user(username):
user = User.query.filter_by(username=username).first_or_404()
posts = [
{"author": user, "body": "Test post #1"},
{"author": user, "body": "Test post #2"},
]
return render_template("user.html", user=user, posts=posts)
@app.route("/edit_profile", methods=["GET", "POST"])
@login_required
def edit_profile():
form = EditProfileForm()
if form.validate_on_submit():
current_user.username = form.username.data
current_user.about_me = form.about_me.data
db.session.commit()
flash("Your changes have been saved.")
return redirect(url_for("edit_profile"))
elif request.method == "GET":
form.username.data = current_user.username
form.about_me.data = current_user.about_me
return render_template(
"edit_profile.html", title="Edit Profile", form=form
)

10
app/templates/_post.html Normal file
View File

@@ -0,0 +1,10 @@
<table>
<tr valign="top">
<td>
<img src="{{ post.author.avatar(36) }}" />
</td>
<td>
{{ post.author.username }} says:<br />{{ post.body }}
</td>
</tr>
</table>

View File

@@ -13,6 +13,7 @@
{% if current_user.is_anonymous %} {% if current_user.is_anonymous %}
<a href="{{ url_for('login') }}">Login</a> <a href="{{ url_for('login') }}">Login</a>
{% else %} {% else %}
<a href="{{ url_for('user', username = current_user.username) }}">Profile</a>
<a href="{{ url_for('logout') }}">Logout</a> <a href="{{ url_for('logout') }}">Logout</a>
{% endif %} {% endif %}
</div> </div>

View File

@@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% block content %}
<h1>Edit Profile</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.about_me.label }}<br />
{{ form.about_me(cols = 50, rows = 4) }}<br />
{% for error in form.about_me.errors %}
<span style="color: red;">[{{ error }}]</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}

29
app/templates/user.html Normal file
View File

@@ -0,0 +1,29 @@
{% extends 'base.html' %}
{% block content %}
<table>
<tr valign="top">
<td>
<img src="{{ user.avatar(128) }}" />
</td>
<td>
<h1>User: {{ user.username }}</h1>
{% if user.about_me %}
<p>{{ user.about_me }}</p>
{% endif %}
{% if user.last_seen %}
<p>Last seen on: {{ user.last_seen }}</p>
{% endif %}
</td>
</tr>
</table>
{% if user == current_user %}
<p>
<a href="{{ url_for('edit_profile') }}">Edit your profile</a>
</p>
{% endif %}
<hr />
{% for post in posts %}
{% include '_post.html' %}
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,34 @@
"""new fields in user model
Revision ID: 680d311746e8
Revises: d56801750ac8
Create Date: 2023-06-03 17:39:48.123676
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '680d311746e8'
down_revision = 'd56801750ac8'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.add_column(sa.Column('about_me', sa.String(length=140), nullable=True))
batch_op.add_column(sa.Column('last_seen', sa.DateTime(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.drop_column('last_seen')
batch_op.drop_column('about_me')
# ### end Alembic commands ###

16
pyproject.toml Normal file
View File

@@ -0,0 +1,16 @@
[tool.black]
line-length = 79
include = '\.pyi?$'
exclude = '''
/(
\.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
)/
'''