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,95 +98,61 @@ 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: if form.validate_on_submit():
form.is_admin.data = False # Check if a user with this email already exists
if 'is_manager' not in request.form: existing_user = User.query.filter_by(email=form.email.data).first()
form.is_manager.data = False if existing_user:
flash('A user with this email already exists.', 'error')
return render_template('contacts/form.html', form=form, title='New User', total_admins=total_admins)
if form.validate_on_submit(): # Handle profile picture upload
# Check if a user with this email already exists profile_picture = None
existing_user = User.query.filter_by(email=form.email.data).first() file = request.files.get('profile_picture')
if existing_user: if file and file.filename:
flash('A user with this email already exists.', 'error') filename = secure_filename(file.filename)
return render_template('contacts/form.html', form=form, title='New User', total_admins=total_admins) file_path = os.path.join(UPLOAD_FOLDER, filename)
file.save(file_path)
profile_picture = filename
# Handle profile picture upload # Generate a random password
profile_picture = None alphabet = string.ascii_letters + string.digits + string.punctuation
file = request.files.get('profile_picture') random_password = ''.join(secrets.choice(alphabet) for _ in range(32))
if file and file.filename:
filename = secure_filename(file.filename)
file_path = os.path.join(UPLOAD_FOLDER, filename)
file.save(file_path)
profile_picture = filename
# Generate a random password # Create new user
alphabet = string.ascii_letters + string.digits + string.punctuation user = User(
random_password = ''.join(secrets.choice(alphabet) for _ in range(32)) username=form.first_name.data,
last_name=form.last_name.data,
email=form.email.data,
phone=form.phone.data,
company=form.company.data,
position=form.position.data,
notes=form.notes.data,
is_admin=(form.role.data == 'admin'),
is_manager=(form.role.data == 'manager'),
profile_picture=profile_picture
)
user.set_password(random_password)
db.session.add(user)
# Create new user account # Log user creation event
user = User( log_event(
username=form.first_name.data, event_type='user_create',
last_name=form.last_name.data, details={
email=form.email.data, 'created_by': current_user.id,
phone=form.phone.data, 'created_by_name': f"{current_user.username} {current_user.last_name}",
company=form.company.data, 'user_id': user.id,
position=form.position.data, 'user_name': f"{user.username} {user.last_name}",
notes=form.notes.data, 'email': user.email,
is_active=True, # Set default value 'role': form.role.data,
is_admin=form.is_admin.data, 'method': 'admin_creation'
is_manager=form.is_manager.data, }
profile_picture=profile_picture )
) db.session.commit()
user.set_password(random_password)
db.session.add(user)
db.session.commit()
# Create password setup token flash('User created successfully! They will receive an email with a link to set up their password.', 'success')
token = secrets.token_urlsafe(32) return redirect(url_for('contacts.contacts_list'))
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_event(
event_type='user_create',
details={
'created_by': current_user.id,
'created_by_name': f"{current_user.username} {current_user.last_name}",
'user_id': user.id,
'user_name': f"{user.username} {user.last_name}",
'email': user.email,
'is_admin': user.is_admin,
'is_manager': user.is_manager,
'method': 'admin_creation'
}
)
db.session.commit()
flash('User created successfully! They will receive an email with a link to set up their password.', 'success')
return redirect(url_for('contacts.contacts_list'))
return render_template('contacts/form.html', form=form, title='New User', total_admins=total_admins) return render_template('contacts/form.html', form=form, title='New User', total_admins=total_admins)
@contacts_bp.route('/profile/edit', methods=['GET', 'POST']) @contacts_bp.route('/profile/edit', methods=['GET', 'POST'])
@@ -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 %}
{% endif %} {% if value == 'admin' and current_user.is_admin and total_admins <= 1 and form.role.data == 'admin' %}disabled{% 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"> <label for="role_{{ value }}" class="ml-3 block text-sm font-medium text-gray-700">
Admin users have full access to manage contacts and system settings. {{ label }}
</div> <span class="text-gray-500 text-xs block mt-0.5">
</div> {% if value == 'admin' %}
<div class="flex items-center relative group"> Full access to all contacts, rooms and conversations. Can manage system settings and website customization.
{{ form.is_manager( {% elif value == 'manager' %}
class="h-4 w-4 focus:ring-blue-500 border-gray-300 rounded", Can create and manage rooms and conversations. Can view all contacts. Only limited access to system settings.
style="accent-color: #16767b;" {% else %}
) }} Basic user access.
{{ form.is_manager.label(class="ml-2 block text-sm text-gray-900") }} {% 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"> </span>
Manager users have similar permissions to admins but with some restrictions. </label>
</div>
</div> </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 %}
</div> </div>
{% if form.is_admin.errors %} </div>
<div class="flex flex-col space-y-4">
{% if form.role.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>