Better contact form

This commit is contained in:
2025-06-05 21:05:15 +02:00
parent f65265b4a5
commit 57fa221d47
6 changed files with 106 additions and 120 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,5 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, BooleanField, SubmitField, PasswordField, SelectMultipleField from wtforms import StringField, TextAreaField, BooleanField, SubmitField, PasswordField, SelectMultipleField, SelectField
from wtforms.validators import DataRequired, Email, Length, Optional, ValidationError from wtforms.validators import DataRequired, Email, Length, Optional, ValidationError
from models import User from models import User
from flask_login import current_user from flask_login import current_user
@@ -13,8 +13,11 @@ class UserForm(FlaskForm):
company = StringField('Company (Optional)', validators=[Optional(), Length(max=100)]) company = StringField('Company (Optional)', validators=[Optional(), Length(max=100)])
position = StringField('Position (Optional)', validators=[Optional(), Length(max=100)]) position = StringField('Position (Optional)', validators=[Optional(), Length(max=100)])
notes = TextAreaField('Notes (Optional)', validators=[Optional()]) notes = TextAreaField('Notes (Optional)', validators=[Optional()])
is_admin = BooleanField('Admin Role', default=False) role = SelectField('Role', choices=[
is_manager = BooleanField('Manager Role', default=False) ('user', 'Standard User'),
('manager', 'Manager'),
('admin', 'Administrator')
], validators=[DataRequired()])
new_password = PasswordField('New Password (Optional)') new_password = PasswordField('New Password (Optional)')
confirm_password = PasswordField('Confirm Password (Optional)') confirm_password = PasswordField('Confirm Password (Optional)')
profile_picture = FileField('Profile Picture (Optional)', validators=[FileAllowed(['jpg', 'jpeg', 'png', 'gif'], 'Images only!')]) profile_picture = FileField('Profile Picture (Optional)', validators=[FileAllowed(['jpg', 'jpeg', 'png', 'gif'], 'Images only!')])

View File

@@ -98,14 +98,8 @@ def new_contact():
form = UserForm() form = UserForm()
total_admins = User.query.filter_by(is_admin=True).count() total_admins = User.query.filter_by(is_admin=True).count()
if request.method == 'GET': if request.method == 'GET':
form.is_admin.data = False # Ensure admin role is unchecked by default form.role.data = 'user' # Default to standard user
form.is_manager.data = False # Ensure manager role is unchecked by default
elif request.method == 'POST': elif request.method == 'POST':
if 'is_admin' not in request.form:
form.is_admin.data = False
if 'is_manager' not in request.form:
form.is_manager.data = False
if form.validate_on_submit(): if form.validate_on_submit():
# Check if a user with this email already exists # Check if a user with this email already exists
existing_user = User.query.filter_by(email=form.email.data).first() existing_user = User.query.filter_by(email=form.email.data).first()
@@ -126,7 +120,7 @@ def new_contact():
alphabet = string.ascii_letters + string.digits + string.punctuation alphabet = string.ascii_letters + string.digits + string.punctuation
random_password = ''.join(secrets.choice(alphabet) for _ in range(32)) random_password = ''.join(secrets.choice(alphabet) for _ in range(32))
# Create new user account # Create new user
user = User( user = User(
username=form.first_name.data, username=form.first_name.data,
last_name=form.last_name.data, last_name=form.last_name.data,
@@ -135,39 +129,12 @@ def new_contact():
company=form.company.data, company=form.company.data,
position=form.position.data, position=form.position.data,
notes=form.notes.data, notes=form.notes.data,
is_active=True, # Set default value is_admin=(form.role.data == 'admin'),
is_admin=form.is_admin.data, is_manager=(form.role.data == 'manager'),
is_manager=form.is_manager.data,
profile_picture=profile_picture profile_picture=profile_picture
) )
user.set_password(random_password) user.set_password(random_password)
db.session.add(user) db.session.add(user)
db.session.commit()
# Create password setup token
token = secrets.token_urlsafe(32)
setup_token = PasswordSetupToken(
user_id=user.id,
token=token,
expires_at=datetime.utcnow() + timedelta(hours=24)
)
db.session.add(setup_token)
db.session.commit()
# Create notification for the new user
create_notification(
notif_type='account_created',
user_id=user.id,
sender_id=current_user.id, # Admin who created the account
details={
'message': 'Your DocuPulse account has been created by an administrator.',
'username': user.username,
'email': user.email,
'created_by': f"{current_user.username} {current_user.last_name}",
'timestamp': datetime.utcnow().isoformat(),
'setup_link': url_for('auth.setup_password', token=token, _external=True)
}
)
# Log user creation event # Log user creation event
log_event( log_event(
@@ -178,8 +145,7 @@ def new_contact():
'user_id': user.id, 'user_id': user.id,
'user_name': f"{user.username} {user.last_name}", 'user_name': f"{user.username} {user.last_name}",
'email': user.email, 'email': user.email,
'is_admin': user.is_admin, 'role': form.role.data,
'is_manager': user.is_manager,
'method': 'admin_creation' 'method': 'admin_creation'
} }
) )
@@ -287,7 +253,13 @@ def edit_contact(id):
form.company.data = user.company form.company.data = user.company
form.position.data = user.position form.position.data = user.position
form.notes.data = user.notes form.notes.data = user.notes
form.is_admin.data = user.is_admin # Set role based on current permissions
if user.is_admin:
form.role.data = 'admin'
elif user.is_manager:
form.role.data = 'manager'
else:
form.role.data = 'user'
if form.validate_on_submit(): if form.validate_on_submit():
# Handle profile picture removal # Handle profile picture removal
if 'remove_picture' in request.form: if 'remove_picture' in request.form:
@@ -315,9 +287,10 @@ def edit_contact(id):
user.profile_picture = filename user.profile_picture = filename
# Prevent removing admin from the last admin # Prevent removing admin from the last admin
if not form.is_admin.data and user.is_admin and total_admins <= 1: if form.role.data != 'admin' and user.is_admin and total_admins <= 1:
flash('There must be at least one admin user in the system.', 'error') flash('There must be at least one admin user in the system.', 'error')
return render_template('contacts/form.html', form=form, title='Edit User', total_admins=total_admins, user=user) return render_template('contacts/form.html', form=form, title='Edit User', total_admins=total_admins, user=user)
# Check if the new email is already used by another user # Check if the new email is already used by another user
if form.email.data != user.email: if form.email.data != user.email:
existing_user = User.query.filter_by(email=form.email.data).first() existing_user = User.query.filter_by(email=form.email.data).first()
@@ -332,7 +305,7 @@ def edit_contact(id):
'phone': user.phone, 'phone': user.phone,
'company': user.company, 'company': user.company,
'position': user.position, 'position': user.position,
'is_admin': user.is_admin 'role': 'admin' if user.is_admin else 'manager' if user.is_manager else 'user'
} }
user.username = form.first_name.data user.username = form.first_name.data
@@ -342,7 +315,8 @@ def edit_contact(id):
user.company = form.company.data user.company = form.company.data
user.position = form.position.data user.position = form.position.data
user.notes = form.notes.data user.notes = form.notes.data
user.is_admin = form.is_admin.data user.is_admin = (form.role.data == 'admin')
user.is_manager = (form.role.data == 'manager')
# Set password if provided # Set password if provided
password_changed = False password_changed = False
@@ -366,6 +340,7 @@ def edit_contact(id):
'phone': user.phone, 'phone': user.phone,
'company': user.company, 'company': user.company,
'position': user.position, 'position': user.position,
'role': form.role.data,
'password_changed': password_changed 'password_changed': password_changed
}, },
'timestamp': datetime.utcnow().isoformat() 'timestamp': datetime.utcnow().isoformat()
@@ -387,7 +362,7 @@ def edit_contact(id):
'phone': user.phone, 'phone': user.phone,
'company': user.company, 'company': user.company,
'position': user.position, 'position': user.position,
'is_admin': user.is_admin 'role': form.role.data
}, },
'password_changed': password_changed, 'password_changed': password_changed,
'method': 'admin_update' 'method': 'admin_update'

View File

@@ -113,37 +113,45 @@
{% endif %} {% endif %}
</div> </div>
<div class="flex flex-col space-y-4"> <!-- Role Selection Section -->
<div class="flex items-center space-x-4"> <div class="bg-gray-50 p-4 rounded-lg border border-gray-200">
<div class="flex items-center relative group"> <h4 class="text-lg font-medium text-gray-900 mb-4">Role Selection</h4>
{% set is_last_admin = current_user.is_admin and total_admins <= 1 %} <div class="space-y-3">
{{ form.is_admin( {% for value, label in form.role.choices %}
class="h-4 w-4 focus:ring-blue-500 border-gray-300 rounded", <div class="flex items-center">
style="accent-color: #16767b;", <input type="radio"
disabled=is_last_admin and form.is_admin.data name="{{ form.role.name }}"
) }} value="{{ value }}"
{{ form.is_admin.label(class="ml-2 block text-sm text-gray-900") }} id="role_{{ value }}"
{% if is_last_admin and form.is_admin.data %} class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300"
<input type="hidden" name="is_admin" value="y"> {% if form.role.data == value %}checked{% endif %}
{% if value == 'admin' and current_user.is_admin and total_admins <= 1 and form.role.data == 'admin' %}disabled{% endif %}>
<label for="role_{{ value }}" class="ml-3 block text-sm font-medium text-gray-700">
{{ label }}
<span class="text-gray-500 text-xs block mt-0.5">
{% if value == 'admin' %}
Full access to all contacts, rooms and conversations. Can manage system settings and website customization.
{% elif value == 'manager' %}
Can create and manage rooms and conversations. Can view all contacts. Only limited access to system settings.
{% else %}
Basic user access.
{% endif %}
</span>
</label>
</div>
{% endfor %}
{% if form.role.errors %}
{% for error in form.role.errors %}
<p class="mt-1 text-sm text-red-600">{{ error }}</p>
{% endfor %}
{% endif %} {% endif %}
<div class="absolute left-0 bottom-full mb-2 hidden group-hover:block bg-gray-800 text-white text-xs rounded py-1 px-2 w-48">
Admin users have full access to manage contacts and system settings.
</div> </div>
</div> </div>
<div class="flex items-center relative group">
{{ form.is_manager( <div class="flex flex-col space-y-4">
class="h-4 w-4 focus:ring-blue-500 border-gray-300 rounded", {% if form.role.errors %}
style="accent-color: #16767b;"
) }}
{{ form.is_manager.label(class="ml-2 block text-sm text-gray-900") }}
<div class="absolute left-0 bottom-full mb-2 hidden group-hover:block bg-gray-800 text-white text-xs rounded py-1 px-2 w-48">
Manager users have similar permissions to admins but with some restrictions.
</div>
</div>
</div>
{% if form.is_admin.errors %}
<div class="p-3 bg-red-50 border border-red-200 rounded-lg"> <div class="p-3 bg-red-50 border border-red-200 rounded-lg">
{% for error in form.is_admin.errors %} {% for error in form.role.errors %}
<p class="text-sm text-red-700">{{ error }}</p> <p class="text-sm text-red-700">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>