Profile page

This commit is contained in:
Inga Kirschnick 2022-11-30 14:36:42 +01:00
parent 102772d1a7
commit a009bbe1f9
11 changed files with 345 additions and 134 deletions

View File

@ -5,7 +5,7 @@ from app import db, hashids
from app.decorators import admin_required from app.decorators import admin_required
from app.models import Role, User, UserSettingJobStatusMailNotificationLevel from app.models import Role, User, UserSettingJobStatusMailNotificationLevel
from app.settings.forms import ( from app.settings.forms import (
EditGeneralSettingsForm, EditProfileSettingsForm,
EditNotificationSettingsForm EditNotificationSettingsForm
) )
from . import bp from . import bp
@ -51,10 +51,10 @@ def edit_user(user_id):
data={'confirmed': user.confirmed, 'role': user.role.hashid}, data={'confirmed': user.confirmed, 'role': user.role.hashid},
prefix='admin-edit-user-form' prefix='admin-edit-user-form'
) )
edit_general_settings_form = EditGeneralSettingsForm( edit_profile_settings_form = EditProfileSettingsForm(
user, user,
data=user.to_json_serializeable(), data=user.to_json_serializeable(),
prefix='edit-general-settings-form' prefix='edit-profile-settings-form'
) )
edit_notification_settings_form = EditNotificationSettingsForm( edit_notification_settings_form = EditNotificationSettingsForm(
data=user.to_json_serializeable(), data=user.to_json_serializeable(),
@ -68,10 +68,10 @@ def edit_user(user_id):
db.session.commit() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.edit_user', user_id=user.id)) return redirect(url_for('.edit_user', user_id=user.id))
if (edit_general_settings_form.submit.data if (edit_profile_settings_form.submit.data
and edit_general_settings_form.validate()): and edit_profile_settings_form.validate()):
user.email = edit_general_settings_form.email.data user.email = edit_profile_settings_form.email.data
user.username = edit_general_settings_form.username.data user.username = edit_profile_settings_form.username.data
db.session.commit() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.edit_user', user_id=user.id)) return redirect(url_for('.edit_user', user_id=user.id))
@ -87,7 +87,7 @@ def edit_user(user_id):
return render_template( return render_template(
'admin/edit_user.html.j2', 'admin/edit_user.html.j2',
admin_edit_user_form=admin_edit_user_form, admin_edit_user_form=admin_edit_user_form,
edit_general_settings_form=edit_general_settings_form, edit_profile_settings_form=edit_profile_settings_form,
edit_notification_settings_form=edit_notification_settings_form, edit_notification_settings_form=edit_notification_settings_form,
title='Edit user', title='Edit user',
user=user user=user

View File

@ -262,6 +262,11 @@ class User(HashidMixin, UserMixin, db.Model):
default=UserSettingJobStatusMailNotificationLevel.END default=UserSettingJobStatusMailNotificationLevel.END
) )
last_seen = db.Column(db.DateTime()) last_seen = db.Column(db.DateTime())
full_name = db.Column(db.String(64))
about_me = db.Column(db.String(256))
location = db.Column(db.String(64))
website = db.Column(db.String(128))
organization = db.Column(db.String(128))
# Backrefs: role: Role # Backrefs: role: Role
# Relationships # Relationships
tesseract_ocr_pipeline_models = db.relationship( tesseract_ocr_pipeline_models = db.relationship(

87
app/profile/forms.py Normal file
View File

@ -0,0 +1,87 @@
from flask_wtf import FlaskForm
from wtforms import (
FileField,
StringField,
SubmitField,
TextAreaField,
ValidationError
)
from wtforms.validators import (
InputRequired,
Email,
Length,
Regexp
)
from app.models import User
from app.auth import USERNAME_REGEX
class EditProfileSettingsForm(FlaskForm):
user_avatar = FileField(
'Image File'
)
email = StringField(
'E-Mail',
validators=[InputRequired(), Length(max=254), Email()]
)
username = StringField(
'Username',
validators=[
InputRequired(),
Length(max=64),
Regexp(
USERNAME_REGEX,
message=(
'Usernames must have only letters, numbers, dots or '
'underscores'
)
)
]
)
full_name = StringField(
'Full name',
validators=[Length(max=128)]
)
about_me = TextAreaField(
'About me',
validators=[
Length(max=254)
]
)
website = StringField(
'Website',
validators=[
Length(max=254)
]
)
organization = StringField(
'Organization',
validators=[
Length(max=128)
]
)
location = StringField(
'Location',
validators=[
Length(max=128)
]
)
submit = SubmitField()
def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs)
self.user = user
def validate_email(self, field):
if (field.data != self.user.email
and User.query.filter_by(email=field.data).first()):
raise ValidationError('Email already registered')
def validate_username(self, field):
if (field.data != self.user.username
and User.query.filter_by(username=field.data).first()):
raise ValidationError('Username already in use')
def validate_image_file(self, field):
if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'):
raise ValidationError('only .jpg, .png and .jpeg!')

View File

@ -1,24 +1,61 @@
from flask import render_template, url_for from flask import flash, redirect, render_template, url_for
from flask_login import current_user, login_required from flask_login import current_user, login_required
from app import db from app import db
from app.models import User from app.models import User
from . import bp from . import bp
from .forms import (
EditProfileSettingsForm
)
@bp.route('') @bp.route('')
@login_required @login_required
def profile(): def profile():
user_image = 'static/images/user_avatar.png' user_image = 'static/images/user_avatar.png'
user_name = current_user.username user_name = current_user.username
last_seen = current_user.last_seen last_seen = f'{current_user.last_seen.strftime("%Y-%m-%d %H:%M")}'
member_since = current_user.member_since location = 'Bielefeld'
about_me = '''Lorem ipsum dolor sit amet, consetetur sadipscing elitr,
sed diam nonumy eirmod tempor invidunt ut labore et dolore
magna aliquyam erat, sed diam voluptua. At vero eos et accusam
et justo duo dolores et ea rebum. Stet clita kasd gubergren,
no sea takimat'''
full_name = 'Inga Kirschnick'
email = current_user.email email = current_user.email
role = current_user.role website = 'https://nopaque.uni-bielefeld.de'
organization = 'Universität Bielefeld'
member_since = f'{current_user.member_since.strftime("%Y-%m-%d")}'
return render_template('profile/profile_page.html.j2', return render_template('profile/profile_page.html.j2',
user_image=user_image, user_image=user_image,
user_name=user_name, user_name=user_name,
last_seen=last_seen, last_seen=last_seen,
member_since=member_since, location=location,
about_me=about_me,
full_name=full_name,
email=email, email=email,
role=role) website=website,
organization=organization,
member_since=member_since)
@bp.route('/edit')
@login_required
def edit_profile():
edit_profile_settings_form = EditProfileSettingsForm(
current_user,
data=current_user.to_json_serializeable(),
prefix='edit-profile-settings-form'
)
if (edit_profile_settings_form.submit.data
and edit_profile_settings_form.validate()):
current_user.email = edit_profile_settings_form.email.data
current_user.username = edit_profile_settings_form.username.data
current_user.about_me = edit_profile_settings_form.about_me.data
current_user.location = edit_profile_settings_form.location.data
current_user.organization = edit_profile_settings_form.organization.data
current_user.website = edit_profile_settings_form.website.data
current_user.full_name = edit_profile_settings_form.full_name.data
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.profile.edit_profile'))
return render_template('profile/edit_profile.html.j2',
edit_profile_settings_form=edit_profile_settings_form,
title='Edit Profile')

View File

@ -48,7 +48,7 @@ class ChangePasswordForm(FlaskForm):
raise ValidationError('Invalid password') raise ValidationError('Invalid password')
class EditGeneralSettingsForm(FlaskForm): class EditProfileSettingsForm(FlaskForm):
user_avatar = FileField( user_avatar = FileField(
'Image File' 'Image File'
) )
@ -74,7 +74,7 @@ class EditGeneralSettingsForm(FlaskForm):
'Full name', 'Full name',
validators=[Length(max=128)] validators=[Length(max=128)]
) )
bio = TextAreaField( about_me = TextAreaField(
'About me', 'About me',
validators=[ validators=[
Length(max=254) Length(max=254)
@ -101,10 +101,6 @@ class EditGeneralSettingsForm(FlaskForm):
submit = SubmitField() submit = SubmitField()
def validate_image_file(self, field):
if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'):
raise ValidationError('only .jpg, .png and .jpeg!')
def __init__(self, user, *args, **kwargs): def __init__(self, user, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.user = user self.user = user
@ -119,6 +115,10 @@ class EditGeneralSettingsForm(FlaskForm):
and User.query.filter_by(username=field.data).first()): and User.query.filter_by(username=field.data).first()):
raise ValidationError('Username already in use') raise ValidationError('Username already in use')
def validate_image_file(self, field):
if not field.data.filename.lower().endswith('.jpg' or '.png' or '.jpeg'):
raise ValidationError('only .jpg, .png and .jpeg!')
class EditNotificationSettingsForm(FlaskForm): class EditNotificationSettingsForm(FlaskForm):
job_status_mail_notification_level = SelectField( job_status_mail_notification_level = SelectField(

View File

@ -5,7 +5,6 @@ from app.models import UserSettingJobStatusMailNotificationLevel
from . import bp from . import bp
from .forms import ( from .forms import (
ChangePasswordForm, ChangePasswordForm,
EditGeneralSettingsForm,
EditNotificationSettingsForm, EditNotificationSettingsForm,
EditPrivacySettingsForm EditPrivacySettingsForm
) )
@ -18,11 +17,6 @@ def settings():
current_user, current_user,
prefix='change-password-form' prefix='change-password-form'
) )
edit_general_settings_form = EditGeneralSettingsForm(
current_user,
data=current_user.to_json_serializeable(),
prefix='edit-general-settings-form'
)
edit_notification_settings_form = EditNotificationSettingsForm( edit_notification_settings_form = EditNotificationSettingsForm(
data=current_user.to_json_serializeable(), data=current_user.to_json_serializeable(),
prefix='edit-notification-settings-form' prefix='edit-notification-settings-form'
@ -36,13 +30,7 @@ def settings():
db.session.commit() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.index')) return redirect(url_for('.index'))
if (edit_general_settings_form.submit.data
and edit_general_settings_form.validate()):
current_user.email = edit_general_settings_form.email.data
current_user.username = edit_general_settings_form.username.data
db.session.commit()
flash('Your changes have been saved')
return redirect(url_for('.settings'))
if (edit_notification_settings_form.submit.data if (edit_notification_settings_form.submit.data
and edit_notification_settings_form.validate()): and edit_notification_settings_form.validate()):
current_user.setting_job_status_mail_notification_level = ( current_user.setting_job_status_mail_notification_level = (
@ -53,13 +41,10 @@ def settings():
db.session.commit() db.session.commit()
flash('Your changes have been saved') flash('Your changes have been saved')
return redirect(url_for('.settings')) return redirect(url_for('.settings'))
user_image = 'static/images/user_avatar.png'
return render_template( return render_template(
'settings/settings.html.j2', 'settings/settings.html.j2',
change_password_form=change_password_form, change_password_form=change_password_form,
edit_general_settings_form=edit_general_settings_form,
edit_notification_settings_form=edit_notification_settings_form, edit_notification_settings_form=edit_notification_settings_form,
edit_privacy_settings_form=edit_privacy_settings_form, edit_privacy_settings_form=edit_privacy_settings_form,
user_image=user_image, title='Settings'
title='Profile & Settings'
) )

View File

@ -2,9 +2,17 @@
<li> <li>
<div class="user-view"> <div class="user-view">
<div class="background primary-color"></div> <div class="background primary-color"></div>
<div class="row">
<div class="col s2">
<a href="{{ url_for('profile.profile') }}">
<i class="material-icons" style="color:white; font-size:3em; margin-top: 25px; margin-left:-15px;">account_circle</i></div>
</a>
<div class="col s10">
<span class="white-text name">{{ current_user.username }}</span> <span class="white-text name">{{ current_user.username }}</span>
<span class="white-text email">{{ current_user.email }}</span> <span class="white-text email">{{ current_user.email }}</span>
</div> </div>
</div>
</div>
</li> </li>
<li><a href="{{ url_for('main.index') }}">nopaque</a></li> <li><a href="{{ url_for('main.index') }}">nopaque</a></li>
<li><a href="{{ url_for('main.news') }}"><i class="material-icons left">email</i>News</a></li> <li><a href="{{ url_for('main.news') }}"><i class="material-icons left">email</i>News</a></li>

View File

@ -0,0 +1,43 @@
{% extends "base.html.j2" %}
{% import "materialize/wtf.html.j2" as wtf %}
{% block page_content %}
<div class="container">
<div class="row">
<div class="col s12">
<h1 id="title">{{ title }}</h1>
</div>
<div class="col s12">
<form method="POST">
{{ edit_profile_settings_form.hidden_tag() }}
<div class="card">
<div class="card-content">
<div class="row">
<div class="col s1"></div>
<div class="col s3">
<img src="{{ url_for('static', filename='images/user_avatar.png') }}" alt="user-image" class="circle responsive-img">
{{wtf.render_field(edit_profile_settings_form.user_avatar, accept='image/*', class='file-path validate')}}
</div>
<div class="col s1"></div>
<div class="col s7">
{{ wtf.render_field(edit_profile_settings_form.username, material_icon='person') }}
{{ wtf.render_field(edit_profile_settings_form.email, material_icon='email') }}
{{ wtf.render_field(edit_profile_settings_form.full_name, material_icon='badge') }}
{{ wtf.render_field(edit_profile_settings_form.about_me, material_icon='description') }}
{{ wtf.render_field(edit_profile_settings_form.website, material_icon='laptop') }}
{{ wtf.render_field(edit_profile_settings_form.organization, material_icon='business') }}
{{ wtf.render_field(edit_profile_settings_form.location, material_icon='location_on') }}
</div>
</div>
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(edit_profile_settings_form.submit, material_icon='send') }}
</div>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock page_content %}

View File

@ -10,33 +10,76 @@
<div class="row"> <div class="row">
<div class="col s1"></div> <div class="col s1"></div>
<div class="col s3"> <div class="col s3">
{% if about_me %}
<img src="{{ user_image }}" alt="user-image" class="circle responsive-img" style="margin-top:50px;">
{% else %}
<img src="{{ user_image }}" alt="user-image" class="circle responsive-img"> <img src="{{ user_image }}" alt="user-image" class="circle responsive-img">
{% endif %}
</div> </div>
<div class="col s1"></div> <div class="col s1"></div>
<div class="col s7"> <div class="col s7">
<h3>{{ user_name }}</h3> <h3>{{ user_name }}</h3>
<div class="chip">Last seen: {{ last_seen }}</div> <div class="chip">Last seen: {{ last_seen }}</div>
<p><span class="material-icons" style="margin-right:20px; margin-top:20px;">location_on</span><i>Bielefeld</i></p> {% if location %}
<p><span class="material-icons" style="margin-right:20px; margin-top:20px;">location_on</span><i>{{ location }}</i></p>
{% endif %}
<p></p> <p></p>
<br> <br>
<p>Inga Kirschnick</p> {% if about_me%}
<p>Bio</p> <div class="card">
<div class="card-content">
<span class="card-title">About me</span>
<p>{{ about_me }}</p>
</div>
</div>
{% endif %}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col s1"></div> <div class="col s1"></div>
<div class="col s6"> <div class="col s6">
<p>{{ email }}</p> <table>
<p>Webseite</p> {% if full_name %}
<p>Organization</p> <tr>
<p>Member since: {{ member_since }}</p> <td><span class="material-icons">person</span></td>
<p>Role: {{ role }}</p> <td>{{ full_name }} </td>
</tr>
{% endif %}
{% if email %}
<tr>
<td><span class="material-icons">email</span></td>
<td>{{ email }}</td>
</tr>
{% endif %}
{% if website %}
<tr>
<td><span class="material-icons">laptop</span></td>
<td><a href="{{ website }}">{{ website }}</a></td>
</tr>
{% endif %}
{% if organization %}
<tr>
<td><span class="material-icons">business</span></td>
<td>{{ organization }}</td>
</tr>
{% endif %}
</table>
<br>
<p><i>Member since: {{ member_since }}</i></p>
<p></p>
<br>
<a class="waves-effect waves-light btn-small" href="{{ url_for('settings.settings') }}">Edit profile</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="row">
<div class="col s6">
</div>
</div> </div>
</div> </div>
{% endblock page_content %} {% endblock page_content %}

View File

@ -8,46 +8,7 @@
<div class="col s12"> <div class="col s12">
<h1 id="title">{{ title }}</h1> <h1 id="title">{{ title }}</h1>
</div> </div>
<div class="col s12"> <div class="col s12">
<form method="POST">
{{ edit_general_settings_form.hidden_tag() }}
<div class="card">
<div class="card-content">
<span class="card-title" style="margin-bottom: 40px;">Your profile</span>
<div class="row">
<div class="col s1"></div>
<div class="col s3">
<img src="{{ user_image }}" alt="user-image" class="circle responsive-img">
{{wtf.render_field(edit_general_settings_form.user_avatar, accept='image/*', class='file-path validate')}}
</div>
<div class="col s1"></div>
<div class="col s7">
{{ wtf.render_field(edit_general_settings_form.username, material_icon='person') }}
{{ wtf.render_field(edit_general_settings_form.email, material_icon='email') }}
{{ wtf.render_field(edit_general_settings_form.full_name, material_icon='badge') }}
{{ wtf.render_field(edit_general_settings_form.bio, material_icon='description') }}
</div>
</div>
<div class="row">
<div class="col s6">
{{ wtf.render_field(edit_general_settings_form.website, material_icon='laptop') }}
{{ wtf.render_field(edit_general_settings_form.organization, material_icon='business') }}
{{ wtf.render_field(edit_general_settings_form.location, material_icon='location_on') }}
</div>
</div>
</div>
<div class="card-action">
<div class="right-align">
{{ wtf.render_field(edit_general_settings_form.submit, material_icon='send') }}
</div>
</div>
</div>
</form>
<div class="card">
<div class="card-content">
<span class="card-title" style="margin-bottom: 40px;">Settings</span>
<form method="POST"> <form method="POST">
{{ edit_notification_settings_form.hidden_tag() }} {{ edit_notification_settings_form.hidden_tag() }}
<div class="card"> <div class="card">
@ -108,8 +69,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
{% endblock page_content %} {% endblock page_content %}
{% block scripts %} {% block scripts %}

View File

@ -0,0 +1,44 @@
"""Add profile pages columns to users table
Revision ID: 4820fa2e3ee2
Revises: 31b9c0259e6b
Create Date: 2022-11-30 10:03:49.080576
"""
from alembic import op
import sqlalchemy as sa
revision = '4820fa2e3ee2'
down_revision = '31b9c0259e6b'
branch_labels = None
depends_on = None
def upgrade():
op.add_column(
'users',
sa.Column('full_name', sa.String(length=64), nullable=True)
)
op.add_column(
'users',
sa.Column('about_me', sa.String(length=256), nullable=True)
)
op.add_column(
'users',
sa.Column('location', sa.String(length=64), nullable=True)
)
op.add_column(
'users',
sa.Column('website', sa.String(length=128), nullable=True)
)
op.add_column(
'users',
sa.Column('organization', sa.String(length=128), nullable=True)
)
def downgrade():
op.drop_column('users', 'organization')
op.drop_column('users', 'website')
op.drop_column('users', 'location')
op.drop_column('users', 'about_me')
op.drop_column('users', 'full_name')