From 4dd7d111905213922979aa841989a1726544cd98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Monta=C3=B1ana?= Date: Sat, 3 Jun 2023 17:53:32 +0200 Subject: [PATCH] Chapter 6 --- app.db | Bin 32768 -> 32768 bytes app/forms.py | 22 ++++++++- app/models.py | 9 ++++ app/routes.py | 44 +++++++++++++++++- app/templates/_post.html | 10 ++++ app/templates/base.html | 1 + app/templates/edit_profile.html | 23 +++++++++ app/templates/user.html | 29 ++++++++++++ .../680d311746e8_new_fields_in_user_model.py | 34 ++++++++++++++ pyproject.toml | 16 +++++++ 10 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 app/templates/_post.html create mode 100644 app/templates/edit_profile.html create mode 100644 app/templates/user.html create mode 100644 migrations/versions/680d311746e8_new_fields_in_user_model.py create mode 100644 pyproject.toml diff --git a/app.db b/app.db index c9af415a66de3f116f047e5be285f0ca7f2ed93a..c0257ca4134944509fe59ae025f66218442df6ff 100644 GIT binary patch delta 293 zcmZo@U}|V!njkHx!N9=40mLxCF;T}@T7y9^?G-OyHUm5NeFlDB{+-i!&g|)7LR5Qo-9bQb8j#MUzW&^BSK0j4b>=7#2-zoVS^m|G5CS znT0`$v7w>4iCOCA|MFk?C6ELxc!6$Z;ycX1U(I)Tv!K9sKG8-~CJqKs?Z!w)$E^H} zJP>v$o;=ZBZnBnrph;qJN*WL=04W6!8$yCuMg~U4x&~&t2F418rdFm#Rwm|prUnLP Lh9;Y_^bJT0S$l0$5GG3#vrQQ7&-ZzUFGI# H`&I`4-Ipr( diff --git a/app/forms.py b/app/forms.py index 7ec3fc3..87b0da3 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,6 +1,18 @@ from flask_wtf import FlaskForm -from wtforms import StringField, PasswordField, BooleanField, SubmitField -from wtforms.validators import DataRequired, EqualTo, Email, ValidationError +from wtforms import ( + StringField, + PasswordField, + BooleanField, + SubmitField, + TextAreaField, +) +from wtforms.validators import ( + DataRequired, + EqualTo, + Email, + ValidationError, + Length, +) from app.models import User @@ -29,3 +41,9 @@ class RegistrationForm(FlaskForm): user = User.query.filter_by(email=email.data).first() if user is not None: 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") diff --git a/app/models.py b/app/models.py index 5a9a6fc..8b253ae 100644 --- a/app/models.py +++ b/app/models.py @@ -1,4 +1,5 @@ from datetime import datetime +from hashlib import md5 from werkzeug.security import generate_password_hash, check_password_hash from flask_login import UserMixin from app import db, login @@ -15,6 +16,8 @@ class User(UserMixin, db.Model): email = db.Column(db.String(120), index=True, unique=True) password_hash = db.Column(db.String(128)) 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): return "".format(self.username) @@ -25,6 +28,12 @@ class User(UserMixin, db.Model): def check_password(self, 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): id = db.Column(db.Integer, primary_key=True) diff --git a/app/routes.py b/app/routes.py index 667b5d4..1ef9c86 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,18 +1,29 @@ +from datetime import datetime from flask import render_template, flash, redirect, url_for, request from flask_login import current_user, login_user, logout_user, login_required from werkzeug.urls import url_parse from app import app, db -from app.forms import LoginForm, RegistrationForm +from app.forms import LoginForm, RegistrationForm, EditProfileForm 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("/index") @login_required def index(): posts = [ {"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) @@ -54,3 +65,32 @@ def register(): flash("Congratulations, you are now a registered user!") return redirect(url_for("login")) return render_template("register.html", title="Register", form=form) + + +@app.route("/user/") +@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 + ) diff --git a/app/templates/_post.html b/app/templates/_post.html new file mode 100644 index 0000000..c764a55 --- /dev/null +++ b/app/templates/_post.html @@ -0,0 +1,10 @@ + + + + + +
+ + + {{ post.author.username }} says:
{{ post.body }} +
diff --git a/app/templates/base.html b/app/templates/base.html index dcd5803..4b5bb70 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -13,6 +13,7 @@ {% if current_user.is_anonymous %} Login {% else %} + Profile Logout {% endif %} diff --git a/app/templates/edit_profile.html b/app/templates/edit_profile.html new file mode 100644 index 0000000..966ed4c --- /dev/null +++ b/app/templates/edit_profile.html @@ -0,0 +1,23 @@ +{% extends 'base.html' %} + +{% block content %} +

Edit Profile

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

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

+

+ {{ form.about_me.label }}
+ {{ form.about_me(cols = 50, rows = 4) }}
+ {% for error in form.about_me.errors %} + [{{ error }}] + {% endfor %} +

+

{{ form.submit() }}

+
+{% endblock %} diff --git a/app/templates/user.html b/app/templates/user.html new file mode 100644 index 0000000..29b529a --- /dev/null +++ b/app/templates/user.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block content %} + + + + + +
+ + +

User: {{ user.username }}

+ {% if user.about_me %} +

{{ user.about_me }}

+ {% endif %} + {% if user.last_seen %} +

Last seen on: {{ user.last_seen }}

+ {% endif %} +
+ {% if user == current_user %} +

+ Edit your profile +

+ {% endif %} +
+ {% for post in posts %} + {% include '_post.html' %} + {% endfor %} +{% endblock %} diff --git a/migrations/versions/680d311746e8_new_fields_in_user_model.py b/migrations/versions/680d311746e8_new_fields_in_user_model.py new file mode 100644 index 0000000..aebd31b --- /dev/null +++ b/migrations/versions/680d311746e8_new_fields_in_user_model.py @@ -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 ### diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9bd6669 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.black] +line-length = 79 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | _build + | buck-out + | build + | dist +)/ +''' \ No newline at end of file