Implement CORS-fields in views & HTML, improve list rendering & selection

This commit is contained in:
Josako
2024-05-15 21:27:23 +02:00
parent ea23e8d327
commit 8c6d9bf5ca
13 changed files with 434 additions and 68 deletions

View File

@@ -37,9 +37,11 @@ class Tenant(db.Model):
license_start_date = db.Column(db.Date, nullable=True) license_start_date = db.Column(db.Date, nullable=True)
license_end_date = db.Column(db.Date, nullable=True) license_end_date = db.Column(db.Date, nullable=True)
allowed_monthly_interactions = db.Column(db.Integer, nullable=True) allowed_monthly_interactions = db.Column(db.Integer, nullable=True)
encrypted_api_key = db.Column(db.String(500), nullable=True)
# Relations # Relations
users = db.relationship('User', backref='tenant') users = db.relationship('User', backref='tenant')
domains = db.relationship('TenantDomain', backref='tenant')
def __repr__(self): def __repr__(self):
return f"<Tenant {self.id}: {self.name}>" return f"<Tenant {self.id}: {self.name}>"
@@ -115,3 +117,23 @@ class User(db.Model, UserMixin):
def has_roles(self, *args): def has_roles(self, *args):
return any(role.name in args for role in self.roles) return any(role.name in args for role in self.roles)
class TenantDomain(db.Model):
__bind_key__ = 'public'
__table_args__ = {'schema': 'public'}
id = db.Column(db.Integer, primary_key=True)
tenant_id = db.Column(db.Integer, db.ForeignKey('public.tenant.id'), nullable=False)
domain = db.Column(db.String(255), unique=True, nullable=False)
valid_to = db.Column(db.Date, nullable=True)
# Versioning Information
created_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now())
created_by = db.Column(db.Integer, db.ForeignKey(User.id), nullable=False)
updated_at = db.Column(db.DateTime, nullable=False, server_default=db.func.now(), onupdate=db.func.now())
updated_by = db.Column(db.Integer, db.ForeignKey(User.id))
def __repr__(self):
return f"<TenantDomain {self.id}: {self.domain}>"

View File

@@ -0,0 +1,51 @@
from google.cloud import kms
from base64 import b64encode, b64decode
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from flask import current_app
client = kms.KeyManagementServiceClient()
key_name = client.crypto_key_path('your-project-id', 'your-key-ring', 'your-crypto-key')
def encrypt_api_key(api_key):
"""Encrypts the API key using the latest version of the KEK."""
dek = get_random_bytes(32) # AES 256-bit key
cipher = AES.new(dek, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(api_key.encode())
# Encrypt the DEK using the latest version of the Google Cloud KMS key
encrypt_response = client.encrypt(
request={'name': key_name, 'plaintext': dek}
)
encrypted_dek = encrypt_response.ciphertext
# Store the version of the key used
key_version = encrypt_response.name
return {
'key_version': key_version,
'encrypted_dek': b64encode(encrypted_dek).decode('utf-8'),
'nonce': b64encode(cipher.nonce).decode('utf-8'),
'tag': b64encode(tag).decode('utf-8'),
'ciphertext': b64encode(ciphertext).decode('utf-8')
}
def decrypt_api_key(encrypted_data):
"""Decrypts the API key using the specified key version."""
key_version = encrypted_data['key_version']
encrypted_dek = b64decode(encrypted_data['encrypted_dek'])
nonce = b64decode(encrypted_data['nonce'])
tag = b64decode(encrypted_data['tag'])
ciphertext = b64decode(encrypted_data['ciphertext'])
# Decrypt the DEK using the specified version of the Google Cloud KMS key
decrypt_response = client.decrypt(
request={'name': key_version, 'ciphertext': encrypted_dek}
)
dek = decrypt_response.plaintext
cipher = AES.new(dek, AES.MODE_GCM, nonce=nonce)
api_key = cipher.decrypt_and_verify(ciphertext, tag)
return api_key.decode()

View File

@@ -0,0 +1,36 @@
def prepare_table(model_objects, column_names):
"""
Converts a list of SQLAlchemy model objects into a list of dictionaries based on specified column names.
Args:
model_objects (list): List of SQLAlchemy model instances.
column_names (list): List of strings representing the column names to be included in the dictionaries.
Returns:
list: List of dictionaries where each dictionary represents a record with keys as column names.
"""
table_data = [
{col: getattr(obj, col) for col in column_names}
for obj in model_objects
]
return table_data
def prepare_table_for_macro(model_objects, column_attrs):
"""
Prepare data for rendering in a macro that expects each cell as a dictionary with class, type, and value.
Args:
model_objects (list): List of model instances or dictionaries.
column_attrs (list of tuples): Each tuple contains the attribute name and additional properties like class.
Returns:
list: A list of rows, where each row is a list of cell dictionaries.
"""
return [
[
{'value': getattr(obj, attr), 'class': cls, 'type': 'text'} # Adjust 'type' as needed
for attr, cls in column_attrs
]
for obj in model_objects
]

View File

@@ -64,6 +64,53 @@
</div> </div>
{% endmacro %} {% endmacro %}
{% macro render_selectable_table(headers, rows, selectable, id) %}
<div class="card">
<div class="table-responsive">
<table class="table align-items-center mb-0" id="{{ id }}">
<thead>
<tr>
{% if selectable %}
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7">Select</th>
{% endif %}
{% for header in headers %}
<th class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7">{{ header }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in rows %}
<tr>
{% if selectable %}
<td><input type="radio" name="selected_row" value="{{ row[0] }}" required></td>
{% endif %}
{% for cell in row %}
<td class="{{ cell.class }}">
{% if cell.type == 'image' %}
<div class="d-flex px-2 py-1">
<div>
<img src="{{ cell.value }}" class="avatar avatar-sm me-3">
</div>
</div>
{% elif cell.type == 'text' %}
<p class="text-xs {{ cell.text_class }}">{{ cell.value }}</p>
{% elif cell.type == 'badge' %}
<span class="badge badge-sm {{ cell.badge_class }}">{{ cell.value }}</span>
{% elif cell.type == 'link' %}
<a href="{{ cell.href }}" class="text-secondary font-weight-normal text-xs" data-toggle="tooltip" data-original-title="{{ cell.title }}">{{ cell.value }}</a>
{% else %}
{{ cell.value }}
{% endif %}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endmacro %}
{% macro render_accordion(accordion_id, accordion_items, header_title, header_description) %} {% macro render_accordion(accordion_id, accordion_items, header_title, header_description) %}
<div class="accordion-1"> <div class="accordion-1">
<div class="container"> <div class="container">

View File

@@ -69,10 +69,13 @@
<ul class="navbar-nav navbar-nav-hover mx-auto"> <ul class="navbar-nav navbar-nav-hover mx-auto">
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
{{ dropdown('User Mgmt', 'contacts', [ {{ dropdown('User Mgmt', 'contacts', [
{'name': 'Select Tenant', 'url': '/user/select_tenant', 'roles': ['Super User']}, {'name': 'Tenant List', 'url': '/user/select_tenant', 'roles': ['Super User']},
{'name': 'Tenant Registration', 'url': '/user/tenant', 'roles': ['Super User']}, {'name': 'Tenant Registration', 'url': '/user/tenant', 'roles': ['Super User']},
{'name': 'Generate API Key', 'url': '/user/generate_api_key', 'roles': ['Super User']},
{'name': 'Tenant Domains', 'url': '/user/view_tenant_domains/' + session['tenant']['id']|string, 'roles': ['Super User', 'Tenant Admin']},
{'name': 'Tenant Domain Registration', 'url': '/user/tenant_domain', 'roles': ['Super User', 'Tenant Admin']},
{'name': 'User List', 'url': '/user/view_users/' + session['tenant']['id']|string, 'roles': ['Super User', 'Tenant Admin']},
{'name': 'User Registration', 'url': '/user/user', 'roles': ['Super User', 'Tenant Admin']}, {'name': 'User Registration', 'url': '/user/user', 'roles': ['Super User', 'Tenant Admin']},
{'name': 'User List', 'url': '/user/view_users/' + session['tenant']['id']|string, 'roles': ['Super User', 'Tenant Admin']}
]) }} ]) }}
{% endif %} {% endif %}
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}

View File

@@ -1,6 +1,7 @@
<!-- Optional JavaScript --> <!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS --> <!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
{% block scripts %} {% block scripts %}

View File

@@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% from "macros.html" import render_field %}
{% block title %}Update Tenant Domain{% endblock %}
{% block content_title %}Update Tenant Domain{% endblock %}
{% block content_description %}Disable a tenant domain{% endblock %}
{% block content %}
<form method="post">
{{ form.hidden_tag() }}
{% set disabled_fields = ['domain'] %}
{% set exclude_fields = [] %}
{% for field in form %}
{{ render_field(field, disabled_fields, exclude_fields) }}
{% endfor %}
<button type="submit" class="btn btn-primary">Update Domain</button>
</form>
{% endblock %}
{% block content_footer %}
{% endblock %}

View File

@@ -1,5 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% from "macros.html" import render_field %} {% from "macros.html" import render_selectable_table %}
{% block title %}Tenant Selection{% endblock %} {% block title %}Tenant Selection{% endblock %}
@@ -8,27 +8,38 @@
{% block content %} {% block content %}
<form method="POST" action="{{ url_for('user_bp.handle_tenant_selection') }}"> <form method="POST" action="{{ url_for('user_bp.handle_tenant_selection') }}">
<table class="table"> {{ render_selectable_table(headers=["Tenant ID", "Tenant Name","Website"], rows=tenants, selectable=True, id="tenantsTable") }}
<thead> <div class="form-group mt-3">
<tr> <button type="submit" name="action" value="select_tenant" class="btn btn-primary">Set Session Tenant</button>
<th>Select</th> <button type="submit" name="action" value="view_users" class="btn btn-secondary">View Users</button>
<th>Tenant Name</th> <button type="submit" name="action" value="edit_tenant" class="btn btn-secondary">Edit Tenant</button>
<th>Tenant ID</th> <!-- Add more buttons as needed -->
</tr> </div>
</thead>
<tbody>
{% for tenant in tenants %}
<tr>
<td><input type="radio" name="tenant_id" value="{{ tenant.id }}" required></td>
<td>{{ tenant.name }}</td>
<td>{{ tenant.id }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<button type="submit" name="action" value="select_tenant" class="btn btn-primary">Set Session Tenant</button>
<button type="submit" name="action" value="view_users" class="btn btn-secondary">View Users</button>
<button type="submit" name="action" value="edit_tenant" class="btn btn-secondary">Edit Tenant</button>
<!-- Add more buttons as needed -->
</form> </form>
{% endblock %} {% endblock %}
{% block scripts %}
<script>
$(document).ready(function() {
$('#tenantsTable').DataTable({
'columnDefs': [{
'targets': 0,
'searchable': false,
'orderable': false,
'className': 'dt-body-center',
'render': function (data, type, full, meta){
return '<input type="radio" name="user_id" value="' + $('<div/>').text(data).html() + '">';
}
},
{
'targets': 1,
'orderable': true
},
{
'targets': 2,
'orderable': true
}],
'order': [[1, 'asc']]
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% from "macros.html" import render_field %}
{% block title %}Tenant Domain Registration{% endblock %}
{% block content_title %}Register Tenant Domain{% endblock %}
{% block content_description %}Make a new tenant domain, which enables the chat client on the specified domain{% endblock %}
{% block content %}
<form method="post">
{{ form.hidden_tag() }}
{% set disabled_fields = [] %}
{% set exclude_fields = [] %}
{% for field in form %}
{{ render_field(field, disabled_fields, exclude_fields) }}
{% endfor %}
<button type="submit" class="btn btn-primary">Register Domain</button>
</form>
{% endblock %}
{% block content_footer %}
{% endblock %}

View File

@@ -0,0 +1,45 @@
{% extends 'base.html' %}
{% from "macros.html" import render_selectable_table %}
{% block title %}View Tenant Domains{% endblock %}
{% block content_title %}Select a domain{% endblock %}
{% block content_description %}Select the domain you'd like to action upon{% endblock %}
{% block content %}
<form action="{{ url_for('user_bp.handle_tenant_domain_action') }}" method="POST">
{{ render_selectable_table(headers=["ID", "Domain Name", "Valid To"], rows=tenant_domains, selectable=True, id="tenantDomainsTable") }}
<div class="form-group mt-3">
<button type="submit" name="action" value="edit_tenant_domain" class="btn btn-primary">Edit Selected Domain</button>
<!-- Additional buttons can be added here for other actions -->
</div>
</form>
{% endblock %}
{% block scripts %}
<script>
$(document).ready(function() {
$('#tenantDomainsTable').DataTable({
'columnDefs': [
{
'targets': 0,
'searchable': false,
'orderable': false,
'className': 'dt-body-center',
'render': function (data, type, full, meta) {
return '<input type="radio" name="user_id" value="' + $('<div/>').text(data).html() + '">';
}
},
{
'targets': 1,
'orderable': true,
},
{
'targets': 2,
'orderable': true,
},
],
'order': [[1, 'asc']]
});
});
</script>
{% endblock %}

View File

@@ -1,5 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% from "macros.html" import render_field %} {% from "macros.html" import render_selectable_table %}
{% block title %}View Users{% endblock %} {% block title %}View Users{% endblock %}
@@ -7,49 +7,43 @@
{% block content_description %}Select the user you'd like to action upon{% endblock %} {% block content_description %}Select the user you'd like to action upon{% endblock %}
{% block content %} {% block content %}
<h3>Users for Selected Tenant</h3>
<form action="{{ url_for('user_bp.handle_user_action') }}" method="POST"> <form action="{{ url_for('user_bp.handle_user_action') }}" method="POST">
<table id="usersTable" class="display" style="width:100%"> {{ render_selectable_table(headers=["User ID", "User Name", "Email"], rows=users, selectable=True, id="usersTable") }}
<thead>
<tr>
<th>Select</th>
<th>User ID</th>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td><input type="radio" name="user_id" value="{{ user.id }}" required></td>
<td>{{ user.id }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="form-group mt-3"> <div class="form-group mt-3">
<button type="submit" name="action" value="edit_user" class="btn btn-primary">Edit Selected User</button> <button type="submit" name="action" value="edit_user" class="btn btn-primary">Edit Selected User</button>
<!-- Additional buttons can be added here for other actions --> <!-- Additional buttons can be added here for other actions -->
</div> </div>
</form> </form>
<script src="https://code.jquery.com/jquery-3.5.1.js"></script> {% endblock %}
<script src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script> {% block scripts %}
<script> <script>
$(document).ready(function() { $(document).ready(function() {
$('#usersTable').DataTable({ $('#usersTable').DataTable({
'columnDefs': [{ 'columnDefs': [
'targets': 0, {
'searchable': false, 'targets': 0,
'orderable': false, 'searchable': false,
'className': 'dt-body-center', 'orderable': false,
'render': function (data, type, full, meta){ 'className': 'dt-body-center',
return '<input type="radio" name="user_id" value="' + $('<div/>').text(data).html() + '">'; 'render': function (data, type, full, meta) {
return '<input type="radio" name="user_id" value="' + $('<div/>').text(data).html() + '">';
}
},
{
'targets': 1,
'orderable': true,
},
{
'targets': 2,
'orderable': true,
},
{
'targets': 3,
'orderable': true,
} }
}], ],
'order': [[1, 'asc']] 'order': [[1, 'asc']]
}); });
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -74,3 +74,12 @@ class UserRoleForm(FlaskForm):
email = EmailField('Email', validators=[DataRequired(), Email()]) email = EmailField('Email', validators=[DataRequired(), Email()])
roles = FieldList(FormField(RoleForm), min_entries=1) roles = FieldList(FormField(RoleForm), min_entries=1)
submit = SubmitField('Update Roles') submit = SubmitField('Update Roles')
class TenantDomainForm(FlaskForm):
domain = StringField('Domain', validators=[DataRequired(), Length(max=80)])
valid_to = DateField('Valid to', id='form-control datepicker', validators=[Optional()])
submit = SubmitField('Add Domain')

View File

@@ -2,13 +2,15 @@
import uuid import uuid
from datetime import datetime as dt, timezone as tz from datetime import datetime as dt, timezone as tz
from flask import request, redirect, url_for, flash, render_template, Blueprint, session, current_app from flask import request, redirect, url_for, flash, render_template, Blueprint, session, current_app
from flask_security import hash_password, roles_required, roles_accepted from flask_security import hash_password, roles_required, roles_accepted, current_user
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
import ast
from common.models.user import User, Tenant, Role from common.models.user import User, Tenant, Role, TenantDomain
from common.extensions import db from common.extensions import db
from .user_forms import TenantForm, CreateUserForm, EditUserForm from .user_forms import TenantForm, CreateUserForm, EditUserForm, TenantDomainForm
from common.utils.database import Database from common.utils.database import Database
from common.utils.view_assistants import prepare_table_for_macro
user_bp = Blueprint('user_bp', __name__, url_prefix='/user') user_bp = Blueprint('user_bp', __name__, url_prefix='/user')
@@ -32,8 +34,10 @@ def tenant():
# Handle Embedding Variables # Handle Embedding Variables
new_tenant.html_tags = form.html_tags.data.split(',') if form.html_tags.data else [], new_tenant.html_tags = form.html_tags.data.split(',') if form.html_tags.data else [],
new_tenant.html_end_tags = form.html_end_tags.data.split(',') if form.html_end_tags.data else [], new_tenant.html_end_tags = form.html_end_tags.data.split(',') if form.html_end_tags.data else [],
new_tenant.html_included_elements = form.html_included_elements.data.split(',') if form.html_included_elements.data else [], new_tenant.html_included_elements = form.html_included_elements.data.split(
new_tenant.html_excluded_elements = form.html_excluded_elements.data.split(',') if form.html_excluded_elements.data else [] ',') if form.html_included_elements.data else [],
new_tenant.html_excluded_elements = form.html_excluded_elements.data.split(
',') if form.html_excluded_elements.data else []
# Handle Timestamps # Handle Timestamps
timestamp = dt.now(tz.utc) timestamp = dt.now(tz.utc)
@@ -189,13 +193,17 @@ def edit_user(user_id):
@roles_required('Super User') @roles_required('Super User')
def select_tenant(): def select_tenant():
tenants = Tenant.query.all() # Fetch all tenants from the database tenants = Tenant.query.all() # Fetch all tenants from the database
return render_template('user/select_tenant.html', tenants=tenants)
prepared_data = prepare_table_for_macro(tenants, [('id', ''), ('name', ''), ('website', '')])
return render_template('user/select_tenant.html', tenants=prepared_data)
@user_bp.route('/handle_tenant_selection', methods=['POST']) @user_bp.route('/handle_tenant_selection', methods=['POST'])
@roles_required('Super User') @roles_required('Super User')
def handle_tenant_selection(): def handle_tenant_selection():
tenant_id = request.form['tenant_id'] tenant_identification = request.form['selected_row']
tenant_id = ast.literal_eval(tenant_identification).get('value')
the_tenant = Tenant.query.get(tenant_id) the_tenant = Tenant.query.get(tenant_id)
session['tenant'] = the_tenant.to_dict() session['tenant'] = the_tenant.to_dict()
session['default_language'] = the_tenant.default_language session['default_language'] = the_tenant.default_language
@@ -217,21 +225,112 @@ def handle_tenant_selection():
@user_bp.route('/view_users/<int:tenant_id>') @user_bp.route('/view_users/<int:tenant_id>')
@roles_accepted('Super User', 'Tenant Admin') @roles_accepted('Super User', 'Tenant Admin')
def view_users(tenant_id): def view_users(tenant_id):
print(tenant_id)
tenant_id = int(tenant_id) tenant_id = int(tenant_id)
users = User.query.filter_by(tenant_id=tenant_id).all() users = User.query.filter_by(tenant_id=tenant_id).all()
prepared_data = prepare_table_for_macro(users, [('id', ''), ('user_name', ''), ('email', '')])
current_app.logger.debug(f'prepared_data: {prepared_data}')
# Render the users in a template # Render the users in a template
return render_template('user/view_users.html', users=users) return render_template('user/view_users.html', users=prepared_data)
@user_bp.route('/handle_user_action', methods=['POST']) @user_bp.route('/handle_user_action', methods=['POST'])
@roles_accepted('Super User', 'Tenant Admin') @roles_accepted('Super User', 'Tenant Admin')
def handle_user_action(): def handle_user_action():
user_id = request.form['user_id'] user_identification = request.form['selected_row']
user_id = ast.literal_eval(user_identification).get('value')
action = request.form['action'] action = request.form['action']
if action == 'edit_user': if action == 'edit_user':
return redirect(url_for('user_bp.edit_user', user_id=user_id)) return redirect(url_for('user_bp.edit_user', user_id=user_id))
# Add more conditions for other actions # Add more conditions for other actions
return redirect(url_for('view_users')) return redirect(url_for('view_users'))
@user_bp.route('/view_tenant_domains/<int:tenant_id>')
@roles_accepted('Super User', 'Tenant Admin')
def view_tenant_domains(tenant_id):
tenant_id = int(tenant_id)
tenant_domains = TenantDomain.query.filter_by(tenant_id=tenant_id).all()
# prepare table data
prepared_data = prepare_table_for_macro(tenant_domains, [('id', ''), ('domain', ''), ('valid_to', '')])
# Render the users in a template
return render_template('user/view_tenant_domains.html', tenant_domains=prepared_data)
@user_bp.route('/handle_tenant_domain_action', methods=['POST'])
@roles_accepted('Super User', 'Tenant Admin')
def handle_tenant_domain_action():
tenant_domain_identification = request.form['selected_row']
tenant_domain_id = ast.literal_eval(tenant_domain_identification).get('value')
action = request.form['action']
if action == 'edit_tenant_domain':
return redirect(url_for('user_bp.edit_tenant_domain', tenant_domain_id=tenant_domain_id))
# Add more conditions for other actions
return redirect(url_for('view_tenant_domains'))
@user_bp.route('/tenant_domain', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Tenant Admin')
def tenant_domain():
form = TenantDomainForm()
if form.validate_on_submit():
new_tenant_domain = TenantDomain()
form.populate_obj(new_tenant_domain)
new_tenant_domain.tenant_id = session['tenant']['id']
set_logging_information(new_tenant_domain, dt.now(tz.utc))
# Add the new user to the database and commit the changes
try:
db.session.add(new_tenant_domain)
db.session.commit()
flash('Tenant Domain added successfully.')
except SQLAlchemyError as e:
db.session.rollback()
flash(f'Failed to add Tenant Domain. Error: {str(e)}')
current_app.logger.error(f'Failed to create Tenant Domain {new_tenant_domain.domain}. '
f'for tenant {session["tenant"]["id"]}'
f'Error: {str(e)}')
return render_template('user/tenant_domain.html', form=form)
@user_bp.route('/tenant_domain/<int:tenant_domain_id>', methods=['GET', 'POST'])
@roles_accepted('Super User', 'Tenant Admin')
def edit_tenant_domain(tenant_domain_id):
tenant_domain = TenantDomain.query.get_or_404(tenant_domain_id) # This will return a 404 if no user is found
form = TenantDomainForm(obj=tenant_domain)
if request.method == 'POST' and form.validate_on_submit():
form.populate_obj(tenant_domain)
update_logging_information(tenant_domain, dt.now(tz.utc))
try:
db.session.add(tenant_domain)
db.session.commit()
flash('Tenant Domain updated successfully.', 'success')
except SQLAlchemyError as e:
db.session.rollback()
flash(f'Failed to update Tenant Domain. Error: {str(e)}', 'danger')
current_app.logger.error(f'Failed to update Tenant Domain {tenant_domain.id}. '
f'for tenant {session["tenant"]["id"]}'
f'Error: {str(e)}')
return redirect(
url_for('user_bp.view_tenant_domains', tenant_id=session['tenant']['id'])) # Assuming there's a user profile view to redirect to
return render_template('user/edit_tenant_domain.html', form=form, tenant_domain_id=tenant_domain_id)
def set_logging_information(obj, timestamp):
obj.created_at = timestamp
obj.updated_at = timestamp
obj.created_by = current_user.id
obj.updated_by = current_user.id
def update_logging_information(obj, timestamp):
obj.created_by = current_user.id
obj.updated_by = current_user.id