Страница профиля, аватары

This commit is contained in:
Toy Rik 2025-09-27 07:34:53 +03:00
parent 1d09487935
commit df75b30da1
8 changed files with 140 additions and 7 deletions

View File

@ -4,6 +4,8 @@ from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
import sqlalchemy as sa import sqlalchemy as sa
from app import db from app import db
from app.models import User from app.models import User
from wtforms import TextAreaField
from wtforms.validators import Length
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
@ -32,3 +34,8 @@ class RegistrationForm(FlaskForm):
User.email == email.data)) User.email == email.data))
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

@ -5,6 +5,7 @@ import sqlalchemy.orm as so
from flask_login import UserMixin from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from app import db, login from app import db, login
from hashlib import md5
class User(UserMixin, db.Model): class User(UserMixin, db.Model):
@ -17,6 +18,9 @@ class User(UserMixin, db.Model):
posts: so.WriteOnlyMapped['Post'] = so.relationship( posts: so.WriteOnlyMapped['Post'] = so.relationship(
back_populates='author') 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): def __repr__(self):
return '<User {}>'.format(self.username) return '<User {}>'.format(self.username)
@ -27,6 +31,10 @@ 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 f'https://www.gravatar.com/avatar/{digest}?d=identicon&s={size}'
@login.user_loader @login.user_loader
def load_user(id): def load_user(id):

View File

@ -4,8 +4,15 @@ from flask_login import login_user, logout_user, current_user, login_required
import sqlalchemy as sa import sqlalchemy as sa
from app import app, db from app import app, db
from app.forms import LoginForm, RegistrationForm from app.forms import LoginForm, RegistrationForm
from app.forms import EditProfileForm
from app.models import User 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('/')
@app.route('/index') @app.route('/index')
@ -62,3 +69,29 @@ 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 = 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)

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

@ -0,0 +1,6 @@
<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

@ -9,13 +9,14 @@
</head> </head>
<body> <body>
<div> <div>
Microblog: Microblog:
<a href="{{ url_for('index') }}">Home</a> <a href="{{ url_for('index') }}">Home</a>
{% 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('logout') }}">Logout</a> <a href="{{ url_for('user', username=current_user.username) }}">Profile</a>
{% endif %} <a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div> </div>
<hr> <hr>
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages() %}

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 %}

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

@ -0,0 +1,21 @@
{% 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: 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 ###