Enable blocking and unblocking of users.

This commit is contained in:
Jens Luedicke
2025-06-28 11:10:22 +02:00
parent b2861686ea
commit 99765d5728
5 changed files with 75 additions and 11 deletions

46
app.py
View File

@@ -7,6 +7,7 @@ from sqlalchemy import func
from functools import wraps
from flask_mail import Mail, Message
from dotenv import load_dotenv
from werkzeug.security import check_password_hash
# Load environment variables from .env file
load_dotenv()
@@ -96,17 +97,21 @@ def login():
user = User.query.filter_by(username=username).first()
if user is None or not user.check_password(password):
if user and user.check_password(password): # Use the check_password method
# Check if user is blocked
if user.is_blocked:
flash('Your account has been disabled. Please contact an administrator.', 'error')
return render_template('login.html')
# Continue with normal login process
session['user_id'] = user.id
session['username'] = user.username
session['is_admin'] = user.is_admin
flash('Login successful!', 'success')
return redirect(url_for('home'))
else:
flash('Invalid username or password', 'error')
return redirect(url_for('login'))
if not user.is_verified:
flash('Please verify your email address before logging in. Check your inbox for the verification link.', 'warning')
return redirect(url_for('login'))
session.clear()
session['user_id'] = user.id
return redirect(url_for('home'))
return render_template('login.html', title='Login')
@@ -655,5 +660,26 @@ def test():
def inject_current_year():
return {'current_year': datetime.now().year}
@app.route('/admin/users/toggle-status/<int:user_id>')
@admin_required
def toggle_user_status(user_id):
user = User.query.get_or_404(user_id)
# Prevent blocking yourself
if user.id == session.get('user_id'):
flash('You cannot block your own account', 'error')
return redirect(url_for('admin_users'))
# Toggle the blocked status
user.is_blocked = not user.is_blocked
db.session.commit()
if user.is_blocked:
flash(f'User {user.username} has been blocked', 'success')
else:
flash(f'User {user.username} has been unblocked', 'success')
return redirect(url_for('admin_users'))
if __name__ == '__main__':
app.run(debug=True)

View File

@@ -98,6 +98,11 @@ def migrate_database():
if 'token_expiry' not in user_columns:
print("Adding token_expiry column to user table...")
cursor.execute("ALTER TABLE user ADD COLUMN token_expiry TIMESTAMP")
# Add is_blocked column to user table if it doesn't exist
if 'is_blocked' not in user_columns:
print("Adding is_blocked column to user table...")
cursor.execute("ALTER TABLE user ADD COLUMN is_blocked BOOLEAN DEFAULT 0")
# Commit changes and close connection
conn.commit()

View File

@@ -22,6 +22,9 @@ class User(db.Model):
time_entries = db.relationship('TimeEntry', backref='user', lazy=True)
work_config = db.relationship('WorkConfig', backref='user', lazy=True, uselist=False)
# New field for blocking users
is_blocked = db.Column(db.Boolean, default=False)
def set_password(self, password):
self.password_hash = generate_password_hash(password)

View File

@@ -503,4 +503,23 @@ input[type="time"]::-webkit-datetime-edit {
.admin-card p {
color: #666;
margin-bottom: 20px;
}
/* User status badges */
.status-badge {
display: inline-block;
padding: 3px 8px;
border-radius: 12px;
font-size: 0.85em;
font-weight: 500;
}
.status-active {
background-color: #d4edda;
color: #155724;
}
.status-blocked {
background-color: #f8d7da;
color: #721c24;
}

View File

@@ -23,6 +23,7 @@
<th>Username</th>
<th>Email</th>
<th>Role</th>
<th>Status</th>
<th>Created</th>
<th>Actions</th>
</tr>
@@ -33,11 +34,21 @@
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{% if user.is_admin %}Admin{% else %}User{% endif %}</td>
<td>
<span class="status-badge {% if user.is_blocked %}status-blocked{% else %}status-active{% endif %}">
{% if user.is_blocked %}Blocked{% else %}Active{% endif %}
</span>
</td>
<td>{{ user.created_at.strftime('%Y-%m-%d') }}</td>
<td>
<a href="{{ url_for('edit_user', user_id=user.id) }}" class="btn btn-sm btn-primary">Edit</a>
{% if user.id != g.user.id %}
<button class="btn btn-sm btn-danger" onclick="confirmDelete({{ user.id }}, '{{ user.username }}')">Delete</button>
{% if user.is_blocked %}
<a href="{{ url_for('toggle_user_status', user_id=user.id) }}" class="btn btn-sm btn-success">Unblock</a>
{% else %}
<a href="{{ url_for('toggle_user_status', user_id=user.id) }}" class="btn btn-sm btn-warning">Block</a>
{% endif %}
<button class="btn btn-sm btn-danger" onclick="confirmDelete({{ user.id }}, '{{ user.username }}')">Delete</button>
{% endif %}
</td>
</tr>