Apr 2 2026: update user's info, adding Full Name record
This commit is contained in:
@@ -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
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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") }}
|
||||
|
||||
@@ -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") }}
|
||||
|
||||
@@ -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 %}">
|
||||
|
||||
@@ -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") }}
|
||||
|
||||
@@ -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
@@ -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')
|
||||
Reference in New Issue
Block a user