Apr 2 2026: update user's info, adding Full Name record

This commit is contained in:
2026-04-02 16:53:09 -04:00
parent bda4b3f5b3
commit 51d8088b25
10 changed files with 94 additions and 11 deletions
+6
View File
@@ -12,6 +12,7 @@ class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(100), unique=True, nullable=False, index=True)
full_name = db.Column(db.String(150), nullable=True)
email = db.Column(db.String(255), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(255), nullable=False)
role = db.Column(
@@ -37,5 +38,10 @@ class User(UserMixin, db.Model):
def check_password(self, password):
return check_password_hash(self.password_hash, password)
@property
def display_name(self):
"""Return full name if set, otherwise fall back to username."""
return self.full_name.strip() if self.full_name and self.full_name.strip() else self.username
def __repr__(self):
return f'<User {self.username}>'
+6 -3
View File
@@ -74,6 +74,7 @@ def profile():
form = ProfileForm(user=current_user, obj=current_user)
if form.validate_on_submit():
current_user.full_name = form.full_name.data.strip() or None
current_user.email = form.email.data
if form.new_password.data:
@@ -134,6 +135,7 @@ def create_user():
if form.validate_on_submit():
user = User(
username=form.username.data,
full_name=form.full_name.data.strip() or None,
email=form.email.data,
role=form.role.data
)
@@ -158,9 +160,10 @@ def edit_user(user_id):
form = UserForm(user=user, obj=user)
if form.validate_on_submit():
user.username = form.username.data
user.email = form.email.data
user.role = form.role.data
user.username = form.username.data
user.full_name = form.full_name.data.strip() or None
user.email = form.email.data
user.role = form.role.data
if form.password.data:
user.set_password(form.password.data)
+8 -6
View File
@@ -107,10 +107,11 @@ def create():
if form.validate_on_submit():
user = User(
username = form.username.data,
email = form.email.data,
role = 'customer',
active = True,
username = form.username.data,
full_name = form.full_name.data.strip() or None,
email = form.email.data,
role = 'customer',
active = True,
)
user.set_password(form.password.data)
db.session.add(user)
@@ -139,8 +140,9 @@ def edit(customer_id):
form = CustomerUserForm(user=customer, obj=customer)
if form.validate_on_submit():
customer.username = form.username.data
customer.email = form.email.data
customer.username = form.username.data
customer.full_name = form.full_name.data.strip() or None
customer.email = form.email.data
if form.password.data:
customer.set_password(form.password.data)
db.session.commit()
+13 -1
View File
@@ -23,7 +23,10 @@
<i class="bi bi-person-circle"></i>
</span>
</div>
<h4 class="mb-1 fw-bold">{{ current_user.username }}</h4>
<h4 class="mb-1 fw-bold">{{ current_user.display_name }}</h4>
{% if current_user.full_name %}
<p class="text-muted mb-1" style="font-size:.85rem;">@{{ current_user.username }}</p>
{% endif %}
<p class="text-muted mb-2">{{ current_user.email }}</p>
<span class="badge fs-6 bg-{% if current_user.role == 'admin' %}danger{% elif current_user.role == 'supervisor' %}warning{% else %}info{% endif %}">
<i class="bi bi-{% if current_user.role == 'admin' %}shield-fill{% elif current_user.role == 'supervisor' %}star-fill{% else %}person-badge-fill{% endif %} me-1"></i>
@@ -92,6 +95,15 @@
<form method="POST" action="{{ url_for('auth.profile') }}">
{{ form.hidden_tag() }}
<!-- Full Name -->
<div class="mb-3">
{{ form.full_name.label(class="form-label fw-semibold") }}
{{ form.full_name(class="form-control", placeholder="e.g. Jane Smith") }}
{% for error in form.full_name.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
<!-- Email -->
<div class="mb-3">
{{ form.email.label(class="form-label fw-semibold") }}
+12
View File
@@ -56,6 +56,18 @@
{% endif %}
</div>
<div class="col-md-6 mb-3">
{{ form.full_name.label(class="form-label") }}
{{ form.full_name(class="form-control", placeholder="e.g. Jane Smith") }}
{% if form.full_name.errors %}
<div class="text-danger small mt-1">
{% for error in form.full_name.errors %}{{ error }}{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
+2
View File
@@ -21,6 +21,7 @@
<thead class="table-light">
<tr>
<th>Username</th>
<th>Full Name</th>
<th>Email</th>
<th>Role</th>
<th>Created</th>
@@ -32,6 +33,7 @@
{% for user in users %}
<tr class="{{ 'table-secondary text-muted' if not user.active else '' }}">
<td><strong>{{ user.username }}</strong></td>
<td>{{ user.full_name or '—' }}</td>
<td>{{ user.email }}</td>
<td>
<span class="badge bg-{% if user.role == 'admin' %}danger{% elif user.role == 'supervisor' %}warning{% elif user.role == 'project_manager' %}primary{% elif user.role == 'customer' %}success{% else %}info{% endif %}">
+12
View File
@@ -52,6 +52,18 @@
</div>
{% endif %}
</div>
<div class="col-md-6 mb-3">
{{ form.full_name.label(class="form-label") }}
{{ form.full_name(class="form-control", placeholder="e.g. Jane Smith") }}
{% if form.full_name.errors %}
<div class="text-danger small mt-1">
{% for e in form.full_name.errors %}{{ e }}{% endfor %}
</div>
{% endif %}
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control") }}
+2
View File
@@ -25,6 +25,7 @@
<thead class="table-light">
<tr>
<th>Username</th>
<th>Full Name</th>
<th>Email</th>
<th>Status</th>
<th>Assigned Projects</th>
@@ -46,6 +47,7 @@
</a>
</strong>
</td>
<td>{{ customer.full_name or '—' }}</td>
<td class="small text-muted">{{ customer.email }}</td>
<td>
{% if customer.active %}
+4 -1
View File
@@ -18,6 +18,7 @@ class LoginForm(FlaskForm):
class ProfileForm(FlaskForm):
"""Self-service profile update form — available to all authenticated users."""
full_name = StringField('Full Name', validators=[Optional(), Length(max=150)])
email = StringField('Email', validators=[DataRequired(), Email(), Length(max=255)])
current_password = PasswordField('Current Password', validators=[Optional()])
new_password = PasswordField('New Password', validators=[Optional(), Length(min=6, max=100)])
@@ -45,6 +46,7 @@ class ProfileForm(FlaskForm):
class UserForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=3, max=100)])
full_name = StringField('Full Name', validators=[Optional(), Length(max=150)])
email = StringField('Email', validators=[DataRequired(), Email(), Length(max=255)])
password = PasswordField('Password', validators=[Optional(), Length(min=6, max=100)])
confirm_password = PasswordField('Confirm Password', validators=[Optional(), EqualTo('password')])
@@ -101,7 +103,7 @@ class FacilityForm(FlaskForm):
class AreaForm(FlaskForm):
name = StringField('Area Name', validators=[DataRequired(), Length(max=255)])
area_type = SelectField('Area Type', choices=[
('building','Building'), ('restroom','Restroom'), ('lobby','Lobby'), ('hallway','Hallway'),
('restroom','Restroom'), ('lobby','Lobby'), ('hallway','Hallway'),
('office','Office'), ('kitchen','Kitchen'), ('storage','Storage'),
('outdoor','Outdoor'), ('other','Other'),
], validators=[Optional()])
@@ -198,6 +200,7 @@ class CustomerUserForm(FlaskForm):
Password is required on create; optional on edit.
"""
username = StringField('Username', validators=[DataRequired(), Length(min=3, max=100)])
full_name = StringField('Full Name', validators=[Optional(), Length(max=150)])
email = StringField('Email', validators=[DataRequired(), Email(), Length(max=255)])
password = PasswordField('Password', validators=[Optional(), Length(min=8)])
confirm_password = PasswordField('Confirm Password',
@@ -0,0 +1,29 @@
"""Phase 9: Add full_name column to users table
Revision ID: phase9_user_full_name
Revises: phase8_notification_matrix
Create Date: 2026-04-02
"""
from alembic import op
import sqlalchemy as sa
revision = 'phase9_user_full_name'
down_revision = 'phase8_notification_matrix'
branch_labels = None
depends_on = None
def upgrade():
bind = op.get_bind()
inspector = sa.inspect(bind)
columns = [c['name'] for c in inspector.get_columns('users')]
if 'full_name' not in columns:
op.add_column(
'users',
sa.Column('full_name', sa.String(150), nullable=True, default=None),
)
def downgrade():
op.drop_column('users', 'full_name')