diff --git a/app/forms.py b/app/forms.py index 0189742..5b9e1e3 100644 --- a/app/forms.py +++ b/app/forms.py @@ -4,6 +4,8 @@ from wtforms.validators import ValidationError, DataRequired, Email, EqualTo import sqlalchemy as sa from app import db from app.models import User +from wtforms import TextAreaField +from wtforms.validators import Length class LoginForm(FlaskForm): @@ -32,3 +34,8 @@ class RegistrationForm(FlaskForm): User.email == email.data)) 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') \ No newline at end of file diff --git a/app/models.py b/app/models.py index 1448a0e..4c3071f 100644 --- a/app/models.py +++ b/app/models.py @@ -5,6 +5,7 @@ import sqlalchemy.orm as so from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash from app import db, login +from hashlib import md5 class User(UserMixin, db.Model): @@ -17,6 +18,9 @@ class User(UserMixin, db.Model): posts: so.WriteOnlyMapped['Post'] = so.relationship( back_populates='author') + about_me: so.Mapped[Optional[str]] = so.mapped_column(sa.String(140)) + last_seen: so.Mapped[Optional[datetime]] = so.mapped_column( + default=lambda: datetime.now(timezone.utc)) def __repr__(self): return ''.format(self.username) @@ -27,6 +31,10 @@ 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 f'https://www.gravatar.com/avatar/{digest}?d=identicon&s={size}' + @login.user_loader def load_user(id): diff --git a/app/routes.py b/app/routes.py index 2395191..26acf7f 100644 --- a/app/routes.py +++ b/app/routes.py @@ -4,8 +4,15 @@ from flask_login import login_user, logout_user, current_user, login_required import sqlalchemy as sa from app import app, db from app.forms import LoginForm, RegistrationForm +from app.forms import EditProfileForm from app.models import User +from datetime import datetime, timezone +@app.before_request +def before_request(): + if current_user.is_authenticated: + current_user.last_seen = datetime.now(timezone.utc) + db.session.commit() @app.route('/') @app.route('/index') @@ -62,3 +69,29 @@ 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 = db.first_or_404(sa.select(User).where(User.username == username)) + 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) \ No newline at end of file diff --git a/app/templates/_post.html b/app/templates/_post.html new file mode 100644 index 0000000..7e33b44 --- /dev/null +++ b/app/templates/_post.html @@ -0,0 +1,6 @@ + + + + + +
{{ post.author.username }} says:
{{ post.body }}
diff --git a/app/templates/base.html b/app/templates/base.html index 496ea6d..efc35fa 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -9,13 +9,14 @@
- Microblog: - Home - {% if current_user.is_anonymous %} - Login - {% else %} - Logout - {% endif %} + Microblog: + Home + {% if current_user.is_anonymous %} + Login + {% else %} + Profile + Logout + {% endif %}

{% with messages = get_flashed_messages() %} diff --git a/app/templates/edit_profile.html b/app/templates/edit_profile.html new file mode 100644 index 0000000..e2471ac --- /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..877703a --- /dev/null +++ b/app/templates/user.html @@ -0,0 +1,21 @@ +{% 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/98b772b4e7aa_new_fields_in_user_model.py b/migrations/versions/98b772b4e7aa_new_fields_in_user_model.py new file mode 100644 index 0000000..9171e7b --- /dev/null +++ b/migrations/versions/98b772b4e7aa_new_fields_in_user_model.py @@ -0,0 +1,34 @@ +"""new fields in user model + +Revision ID: 98b772b4e7aa +Revises: d436510df492 +Create Date: 2025-09-27 06:51:46.106905 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '98b772b4e7aa' +down_revision = 'd436510df492' +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 ###