- Changes to the list views - now using tabulator with filtering and sorting, client-side pagination, ...

- Adaptation of all list views in the app
This commit is contained in:
Josako
2025-07-14 18:58:54 +02:00
parent acad28b623
commit 000636a229
50 changed files with 2162 additions and 2174 deletions

View File

@@ -0,0 +1,2 @@
# List Views module initialization
# This module contains utility functions for handling list views

View File

@@ -1,83 +0,0 @@
from datetime import datetime as dt, timezone as tz
from flask import request, render_template, current_app
from sqlalchemy import desc, asc
from common.models.interaction import EveAIAsset
from eveai_app.views.list_views.filtered_list_view import FilteredListView
class AssetsListView(FilteredListView):
allowed_filters = ['type', 'file_type']
allowed_sorts = ['id', 'last_used_at']
def get_query(self):
return EveAIAsset.query
def apply_filters(self, query):
filters = request.args.to_dict(flat=False)
if 'type' in filters and filters['type']:
query = query.filter(EveAIAsset.type.in_(filters['type']))
if 'file_type' in filters and filters['file_type']:
query = query.filter(EveAIAsset.file_type.in_(filters['file_type']))
return query
def apply_sorting(self, query):
sort_by = request.args.get('sort_by', 'id')
sort_order = request.args.get('sort_order', 'asc')
if sort_by in self.allowed_sorts:
column = getattr(EveAIAsset, sort_by)
if sort_order == 'asc':
query = query.order_by(asc(column))
elif sort_order == 'desc':
query = query.order_by(desc(column))
return query
def get(self):
query = self.get_query()
query = self.apply_filters(query)
query = self.apply_sorting(query)
pagination = self.paginate(query)
def format_date(date):
if isinstance(date, dt):
return date.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(date, str):
return date
else:
return ''
rows = [
[
{'value': item.id, 'class': '', 'type': 'text'},
{'value': item.name, 'class': '', 'type': 'text'},
{'value': item.type, 'class': '', 'type': 'text'},
{'value': item.type_version, 'class': '', 'type': 'text'},
{'value': item.file_type or '', 'class': '', 'type': 'text'},
{'value': format_date(item.last_used_at), 'class': '', 'type': 'text'}
] for item in pagination.items
]
context = {
'rows': rows,
'pagination': pagination,
'filters': request.args.to_dict(flat=False),
'sort_by': request.args.get('sort_by', 'id'),
'sort_order': request.args.get('sort_order', 'asc'),
'filter_options': self.get_filter_options()
}
return render_template(self.template, **context)
def get_filter_options(self):
# Haal unieke waarden op voor filters
types = [t[0] for t in EveAIAsset.query.with_entities(EveAIAsset.type).distinct().all() if t[0]]
file_types = [f[0] for f in EveAIAsset.query.with_entities(EveAIAsset.file_type).distinct().all() if f[0]]
return {
'type': [(t, t) for t in types],
'file_type': [(f, f) for f in file_types]
}

View File

@@ -1,81 +0,0 @@
from datetime import datetime as dt, timezone as tz
from flask import request, render_template, session, current_app
from sqlalchemy import desc, asc, or_, and_
from common.models.document import Document
from eveai_app.views.list_views.filtered_list_view import FilteredListView
class DocumentListView(FilteredListView):
allowed_filters = ['validity']
allowed_sorts = ['id', 'name', 'valid_from', 'valid_to']
def get_query(self):
catalog_id = session.get('catalog_id')
return Document.query.filter_by(catalog_id=catalog_id)
def apply_filters(self, query):
filters = request.args.to_dict(flat=False)
if 'validity' in filters:
now = dt.now(tz.utc).date()
if 'valid' in filters['validity']:
query = query.filter(
and_(
or_(Document.valid_from.is_(None), Document.valid_from <= now),
or_(Document.valid_to.is_(None), Document.valid_to >= now)
)
)
return query
def apply_sorting(self, query):
sort_by = request.args.get('sort_by', 'id')
sort_order = request.args.get('sort_order', 'asc')
if sort_by in self.allowed_sorts:
column = getattr(Document, sort_by)
if sort_order == 'asc':
query = query.order_by(asc(column))
elif sort_order == 'desc':
query = query.order_by(desc(column))
return query
def get(self):
query = self.get_query()
# query = self.apply_filters(query)
# query = self.apply_sorting(query)
pagination = self.paginate(query)
def format_date(date):
if isinstance(date, dt):
return date.strftime('%Y-%m-%d')
elif isinstance(date, str):
return date
else:
return ''
rows = [
[
{'value': item.id, 'class': '', 'type': 'text'},
{'value': item.name, 'class': '', 'type': 'text'},
{'value': format_date(item.valid_from), 'class': '', 'type': 'text'},
{'value': format_date(item.valid_to), 'class': '', 'type': 'text'}
] for item in pagination.items
]
context = {
'rows': rows,
'pagination': pagination,
'filters': request.args.to_dict(flat=False),
'sort_by': request.args.get('sort_by', 'id'),
'sort_order': request.args.get('sort_order', 'asc'),
'filter_options': self.get_filter_options()
}
return render_template(self.template, **context)
def get_filter_options(self):
return {
'validity': [('valid', 'Valid'), ('all', 'All')]
}

View File

@@ -0,0 +1,324 @@
from flask import current_app, url_for
from sqlalchemy.exc import SQLAlchemyError
from common.extensions import db
from common.models.document import Catalog, Document, DocumentVersion, Processor, Retriever
def get_catalogs_list_view():
"""Genereer de catalogi lijst-weergave configuratie"""
# Haal alle catalogi op (geen server-side filtering - wordt client-side afgehandeld)
catalog_query = Catalog.query.order_by(Catalog.id)
all_catalogs = catalog_query.all()
# Bereid data voor voor Tabulator
data = []
for catalog in all_catalogs:
data.append({
'id': catalog.id,
'name': catalog.name,
'type': catalog.type
})
# Kolomdefinities
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Naam', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
]
actions = [
{'value': 'set_session_catalog', 'text': 'Set Session Catalog', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'edit_catalog', 'text': 'Edit Catalog', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_catalog', 'text': 'Register Catalog', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Catalogi',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'catalogs_table',
'form_action': url_for('document_bp.handle_catalog_selection'),
'description': 'Bekijk en beheer catalogi'
}
def get_processors_list_view(catalog_id):
"""Generate the processors list view configuration"""
# Get all processors for this catalog
processor_query = Processor.query.filter_by(catalog_id=catalog_id).order_by(Processor.id)
all_processors = processor_query.all()
# Prepare data for Tabulator
data = []
for processor in all_processors:
data.append({
'id': processor.id,
'name': processor.name,
'type': processor.type,
'active': processor.active
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
{'title': 'Active', 'field': 'active'}
]
actions = [
{'value': 'edit_processor', 'text': 'Edit Processor', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_processor', 'text': 'Register Processor', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Processors',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'processors_table',
'form_action': url_for('document_bp.handle_processor_selection'),
'description': 'View and manage processors'
}
def get_retrievers_list_view(catalog_id):
"""Generate the retrievers list view configuration"""
# Get all retrievers for this catalog
retriever_query = Retriever.query.filter_by(catalog_id=catalog_id).order_by(Retriever.id)
all_retrievers = retriever_query.all()
# Prepare data for Tabulator
data = []
for retriever in all_retrievers:
data.append({
'id': retriever.id,
'name': retriever.name,
'type': retriever.type
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'}
]
actions = [
{'value': 'edit_retriever', 'text': 'Edit Retriever', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_retriever', 'text': 'Register Retriever', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Retrievers',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'retrievers_table',
'form_action': url_for('document_bp.handle_retriever_selection'),
'description': 'View and manage retrievers'
}
def get_documents_list_view(catalog_id):
# Query all documents for the given catalog_id, along with their latest version
# We use a subquery to get the latest document version for each document
from sqlalchemy import desc, func
from sqlalchemy.orm import aliased
# Subquery to get the max version id for each document
latest_version_subquery = db.session.query(
DocumentVersion.doc_id,
func.max(DocumentVersion.id).label('max_id')
).group_by(DocumentVersion.doc_id).subquery()
# Alias for the latest document version
LatestVersion = aliased(DocumentVersion)
# Main query with join to get documents with their latest version
document_query = db.session.query(
Document.id,
Document.name,
Document.valid_from,
Document.valid_to,
LatestVersion.file_type,
LatestVersion.file_size,
LatestVersion.processing
).join(
latest_version_subquery,
Document.id == latest_version_subquery.c.doc_id
).join(
LatestVersion,
(LatestVersion.doc_id == latest_version_subquery.c.doc_id) &
(LatestVersion.id == latest_version_subquery.c.max_id)
).filter(
Document.catalog_id == catalog_id
).order_by(Document.id)
# Execute the query
try:
documents_with_latest_versions = document_query.all()
# Prepare data for Tabulator
data = []
for doc in documents_with_latest_versions:
data.append({
'id': doc.id,
'name': doc.name,
'valid_from': doc.valid_from.strftime('%Y-%m-%d') if doc.valid_from else '',
'valid_to': doc.valid_to.strftime('%Y-%m-%d') if doc.valid_to else '',
'file_type': doc.file_type,
'file_size': f"{doc.file_size:.2f}" if doc.file_size else '',
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Valid From', 'field': 'valid_from'},
{'title': 'Valid To', 'field': 'valid_to'},
{'title': 'File Type', 'field': 'file_type'},
{'title': 'File Size', 'field': 'file_size'},
]
actions = [
{'value': 'edit_document', 'text': 'Edit Document', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'edit_document_version', 'text': 'Edit Document Version', 'class': 'btn-secondary',
'requiresSelection': True},
{'value': 'refresh', 'text': 'Refresh', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 're_process', 'text': 'Re-Process', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'view_document_markdown', 'text': 'View Document', 'class': 'btn-secondary', 'requiresSelection': True},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Documents',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'documents_table',
'form_action': url_for('document_bp.handle_document_selection'),
'description': 'Manage Documents and Document Versions'
}
except SQLAlchemyError as e:
current_app.logger.error(f"Error querying documents with latest versions: {str(e)}")
return {
'title': 'Documents',
'data': [],
'columns': [],
'actions': [],
'initial_sort': [],
'table_id': 'documents_table',
'form_action': url_for('document_bp.handle_document_selection'),
'description': 'There was an error while retrieving the documents. Please try again later.'
}
def get_documents_processing_list_view(catalog_id):
# Query all documents for the given catalog_id, along with their latest version
# We use a subquery to get the latest document version for each document
from sqlalchemy import desc, func
from sqlalchemy.orm import aliased
# Subquery to get the max version id for each document
latest_version_subquery = db.session.query(
DocumentVersion.doc_id,
func.max(DocumentVersion.id).label('max_id')
).group_by(DocumentVersion.doc_id).subquery()
# Alias for the latest document version
LatestVersion = aliased(DocumentVersion)
# Main query with join to get documents with their latest version
document_query = db.session.query(
Document.id,
Document.name,
LatestVersion.processing,
LatestVersion.processing_started_at,
LatestVersion.processing_finished_at,
LatestVersion.processing_error,
).join(
latest_version_subquery,
Document.id == latest_version_subquery.c.doc_id
).join(
LatestVersion,
(LatestVersion.doc_id == latest_version_subquery.c.doc_id) &
(LatestVersion.id == latest_version_subquery.c.max_id)
).filter(
Document.catalog_id == catalog_id
).order_by(Document.id)
# Execute the query
try:
documents_with_latest_versions = document_query.all()
# Prepare data for Tabulator
data = []
for doc in documents_with_latest_versions:
data.append({
'id': doc.id,
'name': doc.name,
'processing': doc.processing,
'processing_started_at': doc.processing_started_at.strftime('%Y-%m-%d %H:%M:%S') if doc.processing_started_at else '',
'processing_finished_at': doc.processing_finished_at.strftime('%Y-%m-%d %H:%M:%S') if doc.processing_finished_at else '',
'processing_error': doc.processing_error,
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Processing', 'field': 'processing', 'formatter': 'tickCross'},
{'title': 'Start', 'field': 'processing_started_at'},
{'title': 'Finish', 'field': 'processing_finished_at'},
{'title': 'Error', 'field': 'processing_error'},
]
actions = [
{'value': 'edit_document', 'text': 'Edit Document', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'edit_document_version', 'text': 'Edit Document Version', 'class': 'btn-secondary',
'requiresSelection': True},
{'value': 'refresh', 'text': 'Refresh', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 're_process', 'text': 'Re-Process', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'view_document_markdown', 'text': 'View Document', 'class': 'btn-secondary', 'requiresSelection': True},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Document Processing Status',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'documents_table',
'form_action': url_for('document_bp.handle_document_selection'),
'description': 'View Processing Status of Document Versions'
}
except SQLAlchemyError as e:
current_app.logger.error(f"Error querying documents with latest versions: {str(e)}")
return {
'title': 'Document Processing Status',
'data': [],
'columns': [],
'actions': [],
'initial_sort': [],
'table_id': 'documents_table',
'form_action': url_for('document_bp.handle_document_selection'),
'description': 'An error occurred while retrieving the documents. Please try again later.'
}

View File

@@ -1,83 +0,0 @@
from datetime import datetime
from flask import request, render_template
from sqlalchemy import desc, asc
from common.models.document import DocumentVersion, Document
from eveai_app.views.list_views.filtered_list_view import FilteredListView
from common.utils.view_assistants import prepare_table_for_macro
class DocumentVersionListView(FilteredListView):
allowed_filters = ['file_type', 'processing', 'processing_error']
allowed_sorts = ['id', 'processing_started_at', 'processing_finished_at', 'processing_error']
def get_query(self):
return DocumentVersion.query.join(Document)
def apply_filters(self, query):
filters = request.args.to_dict()
if filters.get('file_type'):
query = query.filter(DocumentVersion.file_type == filters['file_type'])
if filters.get('processing'):
query = query.filter(DocumentVersion.processing == (filters['processing'] == 'true'))
if filters.get('processing_error'):
if filters['processing_error'] == 'true':
query = query.filter(DocumentVersion.processing_error.isnot(None))
elif filters['processing_error'] == 'false':
query = query.filter(DocumentVersion.processing_error.is_(None))
if filters.get('start_date'):
query = query.filter(
DocumentVersion.processing_started_at >= datetime.strptime(filters['start_date'], '%Y-%m-%d'))
if filters.get('end_date'):
query = query.filter(
DocumentVersion.processing_finished_at <= datetime.strptime(filters['end_date'], '%Y-%m-%d'))
return query
def apply_sorting(self, query):
sort_by = request.args.get('sort_by', 'id')
sort_order = request.args.get('sort_order', 'asc')
if sort_by in self.allowed_sorts:
column = getattr(DocumentVersion, sort_by)
if sort_order == 'asc':
query = query.order_by(asc(column))
elif sort_order == 'desc':
query = query.order_by(desc(column))
return query
def get(self):
query = self.get_query()
query = self.apply_filters(query)
query = self.apply_sorting(query)
pagination = self.paginate(query)
rows = prepare_table_for_macro(
pagination.items,
[('id', ''), ('file_type', ''), ('processing', ''),
('processing_started_at', ''), ('processing_finished_at', ''),
('processing_error', '')]
)
context = {
'rows': rows,
'pagination': pagination,
'filters': request.args.to_dict(),
'sort_by': request.args.get('sort_by', 'id'),
'sort_order': request.args.get('sort_order', 'asc'),
'filter_options': self.get_filter_options()
}
return render_template(self.template, **context)
def get_filter_options(self):
return {
'file_type': [('pdf', 'PDF'), ('docx', 'DOCX')],
'processing': [('true', 'Processing'), ('false', 'Not Processing')],
'processing_error': [('true', 'With Errors'), ('false', 'Without Errors')]
}

View File

@@ -0,0 +1,164 @@
from flask import url_for, current_app, session
from datetime import datetime as dt, timezone as tz
from flask import flash
from sqlalchemy import or_, desc
from common.models.entitlements import LicenseTier, License
from common.services.user import PartnerServices, UserServices
from common.utils.eveai_exceptions import EveAIException
from common.utils.security_utils import current_user_has_role
def get_license_tiers_list_view():
"""Generate the license tiers list view configuration"""
today = dt.now(tz.utc)
# Build the query
query = LicenseTier.query.filter(
or_(
LicenseTier.end_date == None,
LicenseTier.end_date >= today
)
)
# Apply partner-specific filtering if needed
if current_user_has_role('Partner Admin'):
try:
license_tier_ids = PartnerServices.get_allowed_license_tier_ids()
except EveAIException as e:
flash(f"Cannot retrieve License Tiers: {str(e)}", 'danger')
current_app.logger.error(f'Cannot retrieve License Tiers for partner: {str(e)}')
return {
'title': 'License Tiers',
'data': [],
'columns': [],
'actions': [],
'initial_sort': [],
'table_id': 'license_tiers_table',
'form_action': url_for('entitlements_bp.handle_license_tier_selection'),
'description': 'View and manage license tiers',
'table_height': 700,
'error': True
}
if license_tier_ids and len(license_tier_ids) > 0:
query = query.filter(LicenseTier.id.in_(license_tier_ids))
# Order the results
query = query.order_by(LicenseTier.start_date.desc(), LicenseTier.id)
# Get all license tiers
license_tiers = query.all()
# Prepare data for Tabulator
data = []
for tier in license_tiers:
data.append({
'id': tier.id,
'name': tier.name,
'version': tier.version,
'start_date': tier.start_date.strftime('%Y-%m-%d') if tier.start_date else '',
'end_date': tier.end_date.strftime('%Y-%m-%d') if tier.end_date else ''
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80, 'type': 'number'},
{'title': 'Name', 'field': 'name'},
{'title': 'Version', 'field': 'version', 'width': 120},
{'title': 'Start Date', 'field': 'start_date', 'width': 120},
{'title': 'End Date', 'field': 'end_date', 'width': 120}
]
# Action definitions
actions = [
{'value': 'edit_license_tier', 'text': 'Edit License Tier', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_license_tier', 'text': 'Create License Tier', 'class': 'btn-secondary', 'position': 'right',
'requiresSelection': False}
]
# Add assign license action if user has permission
if UserServices.can_user_assign_license():
actions.insert(1, {'value': 'assign_license', 'text': 'Assign License', 'class': 'btn-info',
'requiresSelection': True})
# Initial sort configuration
initial_sort = [{'column': 'start_date', 'dir': 'desc'}, {'column': 'id', 'dir': 'asc'}]
return {
'title': 'License Tiers',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'license_tiers_table',
'form_action': url_for('entitlements_bp.handle_license_tier_selection'),
'description': 'View and manage license tiers',
'table_height': 700
}
def get_license_list_view():
"""Generate the licenses list view configuration"""
tenant_id = session.get('tenant').get('id')
# Get current date in UTC
current_date = dt.now(tz=tz.utc).date()
# Query licenses for the tenant, with ordering and active status
query = (
License.query
.join(LicenseTier) # Join with LicenseTier
.filter(License.tenant_id == tenant_id)
.add_columns(
License.id,
License.start_date,
License.nr_of_periods,
LicenseTier.name.label('license_tier_name'), # Access name through LicenseTier
(License.start_date <= current_date).label('active')
)
.order_by(License.start_date.desc())
)
# Get all licenses
licenses_list = query.all()
# Prepare data for Tabulator
data = []
for license in licenses_list:
data.append({
'id': license.id,
'license_tier_name': license.license_tier_name,
'start_date': license.start_date.strftime('%Y-%m-%d') if license.start_date else '',
'nr_of_periods': license.nr_of_periods,
'active': license.active
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80, 'type': 'number'},
{'title': 'License Tier', 'field': 'license_tier_name'},
{'title': 'Start Date', 'field': 'start_date', 'width': 120},
{'title': 'Nr of Periods', 'field': 'nr_of_periods', 'width': 120},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross', 'width': 100}
]
# Action definitions
actions = [
{'value': 'edit_license', 'text': 'Edit License', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'view_periods', 'text': 'View Periods', 'class': 'btn-secondary', 'requiresSelection': True}
]
# Initial sort configuration
initial_sort = [{'column': 'start_date', 'dir': 'desc'}]
return {
'title': 'Licenses',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'licenses_table',
'form_action': url_for('entitlements_bp.handle_license_selection'),
'description': 'View and manage licenses',
'table_height': 700
}

View File

@@ -1,54 +0,0 @@
from flask import request, render_template, abort
from sqlalchemy import desc, asc
class FilteredListView:
def __init__(self, model, template, per_page=10):
self.model = model
self.template = template
self.per_page = per_page
def get_query(self):
return self.model.query
def apply_filters(self, query):
filters = request.args.get('filters', {})
for key, value in filters.items():
if hasattr(self.model, key):
column = getattr(self.model, key)
if value.startswith('like:'):
query = query.filter(column.like(f"%{value[5:]}%"))
else:
query = query.filter(column == value)
return query
def apply_sorting(self, query):
sort_by = request.args.get('sort_by')
if sort_by and hasattr(self.model, sort_by):
sort_order = request.args.get('sort_order', 'asc')
column = getattr(self.model, sort_by)
if sort_order == 'desc':
query = query.order_by(desc(column))
else:
query = query.order_by(asc(column))
return query
def paginate(self, query):
page = request.args.get('page', 1, type=int)
return query.paginate(page=page, per_page=self.per_page, error_out=False)
def get(self):
query = self.get_query()
query = self.apply_filters(query)
query = self.apply_sorting(query)
pagination = self.paginate(query)
context = {
'items': pagination.items,
'pagination': pagination,
'model': self.model.__name__,
'filters': request.args.get('filters', {}),
'sort_by': request.args.get('sort_by'),
'sort_order': request.args.get('sort_order', 'asc')
}
return render_template(self.template, **context)

View File

@@ -1,178 +0,0 @@
from datetime import datetime as dt, timezone as tz
from flask import request, render_template, session, current_app
from sqlalchemy import desc, asc, or_, and_
from sqlalchemy.orm import aliased
from common.models.document import Document, DocumentVersion
from eveai_app.views.list_views.filtered_list_view import FilteredListView
from common.utils.view_assistants import prepare_table_for_macro
class FullDocumentListView(FilteredListView):
allowed_filters = ['validity', 'file_type', 'processing', 'processing_error']
allowed_sorts = ['id', 'name', 'valid_from', 'valid_to', 'file_type', 'processing_started_at',
'processing_finished_at', 'processing_error']
def __init__(self, model, template, per_page=10):
super().__init__(model, template, per_page)
self.version_alias = None
def get_query(self):
catalog_id = session.get('catalog_id')
# Fix: Selecteer alleen de id kolom in de subquery
latest_version_subquery = (
DocumentVersion.query
.with_entities(DocumentVersion.id, DocumentVersion.doc_id, DocumentVersion.url,
DocumentVersion.bucket_name, DocumentVersion.object_name,
DocumentVersion.file_type, DocumentVersion.sub_file_type,
DocumentVersion.file_size, DocumentVersion.language,
DocumentVersion.user_context, DocumentVersion.system_context,
DocumentVersion.user_metadata, DocumentVersion.system_metadata,
DocumentVersion.catalog_properties, DocumentVersion.created_at,
DocumentVersion.created_by, DocumentVersion.updated_at,
DocumentVersion.updated_by, DocumentVersion.processing,
DocumentVersion.processing_started_at, DocumentVersion.processing_finished_at,
DocumentVersion.processing_error)
.filter(DocumentVersion.id == (
DocumentVersion.query
.with_entities(DocumentVersion.id) # Selecteer alleen de id kolom
.filter(DocumentVersion.doc_id == Document.id)
.order_by(DocumentVersion.id.desc())
.limit(1)
.scalar_subquery()
))
.subquery()
)
self.version_alias = aliased(DocumentVersion, latest_version_subquery)
return Document.query.filter_by(catalog_id=catalog_id).outerjoin(
self.version_alias, Document.id == self.version_alias.doc_id
)
def apply_filters(self, query):
filters = request.args.to_dict(flat=False)
# Document filters
if 'validity' in filters:
now = dt.now(tz.utc).date()
if 'valid' in filters['validity']:
query = query.filter(
and_(
or_(Document.valid_from.is_(None), Document.valid_from <= now),
or_(Document.valid_to.is_(None), Document.valid_to >= now)
)
)
# DocumentVersion filters - use the same alias from get_query
if filters.get('file_type') and self.version_alias is not None:
query = query.filter(self.version_alias.file_type == filters['file_type'][0])
if filters.get('processing') and self.version_alias is not None:
query = query.filter(self.version_alias.processing == (filters['processing'][0] == 'true'))
if filters.get('processing_error') and self.version_alias is not None:
if filters['processing_error'][0] == 'true':
query = query.filter(self.version_alias.processing_error.isnot(None))
elif filters['processing_error'][0] == 'false':
query = query.filter(self.version_alias.processing_error.is_(None))
# Controleer of start_date een waarde heeft voordat we proberen te parsen
if filters.get('start_date') and self.version_alias is not None and filters['start_date'][0].strip():
query = query.filter(
self.version_alias.processing_started_at >= dt.strptime(filters['start_date'][0], '%Y-%m-%d'))
# Controleer of end_date een waarde heeft voordat we proberen te parsen
if filters.get('end_date') and self.version_alias is not None and filters['end_date'][0].strip():
query = query.filter(
self.version_alias.processing_finished_at <= dt.strptime(filters['end_date'][0], '%Y-%m-%d'))
return query
def apply_sorting(self, query):
sort_by = request.args.get('sort_by', 'id')
sort_order = request.args.get('sort_order', 'asc')
document_columns = ['id', 'name', 'valid_from', 'valid_to']
version_columns = ['file_type', 'processing', 'processing_started_at', 'processing_finished_at',
'processing_error']
if sort_by in self.allowed_sorts:
if sort_by in document_columns:
column = getattr(Document, sort_by)
elif sort_by in version_columns and self.version_alias is not None:
column = getattr(self.version_alias, sort_by)
else:
column = Document.id
if sort_order == 'asc':
query = query.order_by(asc(column))
elif sort_order == 'desc':
query = query.order_by(desc(column))
return query
def get(self):
query = self.get_query()
query = self.apply_filters(query)
query = self.apply_sorting(query)
pagination = self.paginate(query)
# Haal de laatste versies op voor elke document
items_with_versions = []
for doc in pagination.items:
latest_version = DocumentVersion.query.filter_by(doc_id=doc.id).order_by(desc(DocumentVersion.id)).first()
items_with_versions.append((doc, latest_version))
def format_date(date):
if isinstance(date, dt):
return date.strftime('%Y-%m-%d')
elif isinstance(date, str):
return date
else:
return ''
# Maak rijen voor de tabel met document en versie informatie
rows = []
for doc, version in items_with_versions:
if version:
row = [
{'value': doc.id, 'class': '', 'type': 'text'},
{'value': doc.name, 'class': '', 'type': 'text'},
{'value': format_date(doc.valid_from), 'class': '', 'type': 'text'},
{'value': format_date(doc.valid_to), 'class': '', 'type': 'text'},
{'value': version.id, 'class': '', 'type': 'text'},
{'value': version.file_type, 'class': '', 'type': 'text'},
{'value': 'Ja' if version.processing else 'Nee', 'class': '', 'type': 'text'},
{'value': version.processing_error or '', 'class': '', 'type': 'text'}
]
else:
row = [
{'value': doc.id, 'class': '', 'type': 'text'},
{'value': doc.name, 'class': '', 'type': 'text'},
{'value': format_date(doc.valid_from), 'class': '', 'type': 'text'},
{'value': format_date(doc.valid_to), 'class': '', 'type': 'text'},
{'value': '', 'class': '', 'type': 'text'},
{'value': '', 'class': '', 'type': 'text'},
{'value': '', 'class': '', 'type': 'text'},
{'value': '', 'class': '', 'type': 'text'}
]
rows.append(row)
context = {
'rows': rows,
'pagination': pagination,
'filters': request.args.to_dict(flat=False),
'sort_by': request.args.get('sort_by', 'id'),
'sort_order': request.args.get('sort_order', 'asc'),
'filter_options': self.get_filter_options()
}
return render_template(self.template, **context)
def get_filter_options(self):
return {
'validity': [('valid', 'Valid'), ('all', 'All')],
'file_type': [('pdf', 'PDF'), ('docx', 'DOCX')],
'processing': [('true', 'Processing'), ('false', 'Not Processing')],
'processing_error': [('true', 'With Errors'), ('false', 'Without Errors')]
}

View File

@@ -0,0 +1,198 @@
from flask import redirect, flash, current_app, session, url_for
from flask_security import roles_accepted
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import desc
import ast
from common.models.interaction import Specialist, SpecialistMagicLink, ChatSession
from common.utils.nginx_utils import prefixed_url_for
from eveai_app.views.list_views.list_view_utils import render_list_view
# Specialists list view helper
def get_specialists_list_view():
"""Generate the specialists list view configuration"""
# Get all specialists
specialists_query = Specialist.query.order_by(Specialist.id)
all_specialists = specialists_query.all()
# Prepare data for Tabulator
data = []
for specialist in all_specialists:
data.append({
'id': specialist.id,
'name': specialist.name,
'type': specialist.type,
'type_version': specialist.type_version,
'active': specialist.active
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
{'title': 'Type Version', 'field': 'type_version'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
# Action definitions
actions = [
{'value': 'edit_specialist', 'text': 'Edit Specialist', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'execute_specialist', 'text': 'Execute Specialist', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_specialist', 'text': 'Register Specialist', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False}
]
# Initial sort configuration
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Specialists',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'specialists_table',
'form_action': url_for('interaction_bp.handle_specialist_selection'),
'description': 'View and manage specialists',
'table_height': 800 # Hogere tabel voor specialists view
}
def get_assets_list_view():
"""Generate the assets list view configuration"""
# Get all assets
from common.models.interaction import EveAIAsset
assets_query = EveAIAsset.query.order_by(EveAIAsset.id)
all_assets = assets_query.all()
# Prepare data for Tabulator
data = []
for asset in all_assets:
data.append({
'id': asset.id,
'name': asset.name,
'type': asset.type,
'type_version': asset.type_version,
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
{'title': 'Type Version', 'field': 'type_version'}
]
# Action definitions
actions = [
{'value': 'edit_asset', 'text': 'Edit Asset', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_asset', 'text': 'Register Asset', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False}
]
# Initial sort configuration
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Assets',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'assets_table',
'form_action': url_for('interaction_bp.handle_asset_selection'),
'description': 'View and manage assets',
'table_height': 800
}
def get_magic_links_list_view():
"""Generate the specialist magic links list view configuration"""
# Get all specialist magic links
magic_links_query = SpecialistMagicLink.query.order_by(SpecialistMagicLink.id)
all_magic_links = magic_links_query.all()
# Prepare data for Tabulator
data = []
for magic_link in all_magic_links:
data.append({
'id': magic_link.id,
'name': magic_link.name,
'magic_link_code': magic_link.magic_link_code,
'specialist_id': magic_link.specialist_id
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Magic Link Code', 'field': 'magic_link_code'},
{'title': 'Specialist ID', 'field': 'specialist_id'}
]
# Action definitions
actions = [
{'value': 'edit_specialist_magic_link', 'text': 'Edit Magic Link', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_specialist_magic_link', 'text': 'Create Magic Link', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False}
]
# Initial sort configuration
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Specialist Magic Links',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'specialist_magic_links_table',
'form_action': url_for('interaction_bp.handle_specialist_magic_link_selection'),
'description': 'View and manage specialist magic links',
'table_height': 800
}
def get_chat_sessions_list_view():
"""Generate the chat sessions list view configuration"""
# Get all chat sessions ordered by session_start (descending)
chat_sessions_query = ChatSession.query.order_by(desc(ChatSession.session_start))
all_chat_sessions = chat_sessions_query.all()
# Prepare data for Tabulator
data = []
for chat_session in all_chat_sessions:
data.append({
'id': chat_session.id,
'session_id': chat_session.session_id,
'session_start': chat_session.session_start.strftime('%Y-%m-%d %H:%M:%S') if chat_session.session_start else '',
'session_end': chat_session.session_end.strftime('%Y-%m-%d %H:%M:%S') if chat_session.session_end else '',
})
# Column definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Session ID', 'field': 'session_id'},
{'title': 'Start Time', 'field': 'session_start'},
{'title': 'End Time', 'field': 'session_end'},
]
# Action definitions
actions = [
{'value': 'view_chat_session', 'text': 'View Details', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'chat_session_interactions', 'text': 'View Interactions', 'class': 'btn-secondary', 'requiresSelection': True}
]
# Initial sort configuration
initial_sort = [{'column': 'session_start', 'dir': 'desc'}]
return {
'title': 'Chat Sessions',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'chat_sessions_table',
'form_action': url_for('interaction_bp.handle_chat_session_selection'),
'description': 'View all chat sessions',
'table_height': 800
}

View File

@@ -0,0 +1,92 @@
from flask import render_template, current_app
def get_list_view_config(title, data, columns, actions, initial_sort=None, table_id=None, additional_config=None):
"""
Creates a standardized configuration dictionary for list views.
Args:
title (str): The title of the page
data (list): The data to display in the table
columns (list): Column definitions for the table
actions (list): Action button definitions
initial_sort (list, optional): Initial sort configuration
table_id (str, optional): Custom table ID, generated from title if not provided
additional_config (dict, optional): Any additional configuration to include
Returns:
dict: A standardized configuration dictionary
"""
# Generate table_id from title if not provided
if not table_id:
table_id = f"{title.lower().replace(' ', '_')}_table"
# Create the base configuration
config = {
'title': title,
'data': data, # Consistent data parameter name
'columns': columns,
'actions': actions,
'initial_sort': initial_sort or [],
'table_id': table_id
}
# Add any additional configuration
if additional_config:
config.update(additional_config)
return config
def render_list_view(template_name, title, data, columns, actions, form_action, initial_sort=None,
table_id=None, additional_config=None, **kwargs):
"""
Renders a list view template with standardized configuration.
Args:
template_name (str): The name of the template to render
title (str): The title of the page
data (list): The data to display in the table
columns (list): Column definitions for the table
actions (list): Action button definitions
form_action (str): Form action URL for the table
initial_sort (list, optional): Initial sort configuration
table_id (str, optional): Custom table ID
additional_config (dict, optional): Any additional configuration
**kwargs: Additional template variables
Returns:
str: The rendered template
"""
# Zorg ervoor dat table_id altijd een string is zonder spaties of speciale tekens
if not table_id:
table_id = f"{title.lower().replace(' ', '_').replace('-', '_')}_table"
# Zorg ervoor dat initial_sort altijd een lijst is
if initial_sort is None:
initial_sort = []
# Zorg ervoor dat actions altijd een lijst is
if actions is None:
actions = []
# Maak config dictionary
config = {
'title': title,
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': table_id,
'form_action': form_action
}
# Voeg extra configuratie toe indien aanwezig
if additional_config:
config.update(additional_config)
# Voeg eventuele extra template variabelen toe
config.update(kwargs)
current_app.logger.debug(f"List view config: {config}")
return render_template(template_name, **config)

View File

@@ -0,0 +1,129 @@
from flask import current_app, url_for
from sqlalchemy.exc import SQLAlchemyError
from common.extensions import db
from common.models.user import Partner, Tenant, PartnerService
def get_partners_list_view():
"""Genereer de partners lijst-weergave configuratie"""
# Haal alle partners op met hun tenant informatie
query = (db.session.query(
Partner.id,
Partner.code,
Partner.active,
Partner.logo_url,
Tenant.name.label('name')
).join(Tenant, Partner.tenant_id == Tenant.id).order_by(Partner.id))
try:
all_partners = query.all()
# Bereid data voor voor Tabulator
data = []
for partner in all_partners:
data.append({
'id': partner.id,
'code': partner.code,
'name': partner.name,
'active': partner.active
})
# Kolomdefinities
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Code', 'field': 'code'},
{'title': 'Name', 'field': 'name'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
actions = [
{'value': 'set_session_partner', 'text': 'Set Session Partner', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'edit_partner', 'text': 'Edit Partner', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_partner', 'text': 'Register Partner', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Partners',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'partners_table',
'form_action': url_for('partner_bp.handle_partner_selection'),
'description': 'Manage partners'
}
except SQLAlchemyError as e:
current_app.logger.error(f"Error bij het ophalen van partners: {str(e)}")
return {
'title': 'Partners',
'data': [],
'columns': [],
'actions': [],
'initial_sort': [],
'table_id': 'partners_table',
'form_action': url_for('partner_bp.handle_partner_selection'),
'description': 'Er is een fout opgetreden bij het ophalen van partners. Probeer het later opnieuw.'
}
def get_partner_services_list_view(partner_id):
"""Genereer de partner services lijst-weergave configuratie"""
# Haal alle partner services op voor deze partner
query = PartnerService.query.filter(PartnerService.partner_id == partner_id)
try:
all_partner_services = query.all()
# Bereid data voor voor Tabulator
data = []
for service in all_partner_services:
data.append({
'id': service.id,
'name': service.name,
'type': service.type,
'type_version': service.type_version
})
# Kolomdefinities
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
{'title': 'Version', 'field': 'type_version'}
]
actions = [
{'value': 'edit_partner_service', 'text': 'Edit Service', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'add_partner_service_for_tenant', 'text': 'Assign to Tenant', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'create_partner_service', 'text': 'Register Service', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Partner Services',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'partner_services_table',
'form_action': url_for('partner_bp.handle_partner_service_selection'),
'description': 'Manage Partner Services'
}
except SQLAlchemyError as e:
current_app.logger.error(f"Error bij het ophalen van partner services: {str(e)}")
return {
'title': 'Partner Services',
'data': [],
'columns': [],
'actions': [],
'initial_sort': [],
'table_id': 'partner_services_table',
'form_action': url_for('partner_bp.handle_partner_service_selection'),
'description': 'Er is een fout opgetreden bij het ophalen van partner services. Probeer het later opnieuw.'
}

View File

@@ -0,0 +1,234 @@
from flask import redirect, flash, current_app, session, url_for
from flask_security import roles_accepted
from sqlalchemy.exc import SQLAlchemyError
import ast
from common.models.user import Tenant, User, TenantDomain, TenantProject, TenantMake
from common.services.user import UserServices
from eveai_app.views.list_views.list_view_utils import render_list_view
# Tenant list view helper
def get_tenants_list_view():
"""Generate the tenants list view configuration"""
# Get all tenants (no server side filtering - handled client-side)
tenant_query = Tenant.query.order_by(Tenant.id)
all_tenants = tenant_query.all()
# Prepare data for Tabulator
data = []
for tenant in all_tenants:
data.append({
'id': tenant.id,
'name': tenant.name,
'type': tenant.type
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Type', 'field': 'type'},
]
actions = [
{'value': 'select_tenant', 'text': 'Set Session Tenant', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'edit_tenant', 'text': 'Edit tenant', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_tenant', 'text': 'Register tenant', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Tenants',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'tenants_table',
'form_action': url_for('user_bp.handle_tenant_selection'),
'description': 'View and manage tenants'
}
# Users list view helper
def get_users_list_view(tenant_id):
"""Generate the users list view configuration for a specific tenant"""
# Get users for the tenant
query = User.query.filter_by(tenant_id=tenant_id).order_by(User.user_name)
users = query.all()
# Prepare data for Tabulator
data = []
for user in users:
data.append({
'id': user.id,
'user_name': user.user_name,
'email': user.email,
'first_name': user.first_name,
'last_name': user.last_name,
'active': user.active
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'User Name', 'field': 'user_name'},
{'title': 'Email', 'field': 'email'},
{'title': 'First Name', 'field': 'first_name'},
{'title': 'Last Name', 'field': 'last_name'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
actions = [
{'value': 'edit_user', 'text': 'Edit User', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'resend_confirmation_email', 'text': 'Resend Confirmation', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'send_password_reset_email', 'text': 'Send Password Reset', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'reset_uniquifier', 'text': 'Reset Uniquifier', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'create_user', 'text': 'Register User', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'user_name', 'dir': 'asc'}]
return {
'title': 'Users',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'users_table',
'form_action': url_for('user_bp.handle_user_action'),
'description': f'Users for tenant {tenant_id}'
}
# Tenant Domains list view helper
def get_tenant_domains_list_view(tenant_id):
"""Generate the tenant domains list view configuration for a specific tenant"""
# Get domains for the tenant
query = TenantDomain.query.filter_by(tenant_id=tenant_id).order_by(TenantDomain.domain)
domains = query.all()
# Prepare data for Tabulator
data = []
for domain in domains:
data.append({
'id': domain.id,
'domain': domain.domain,
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Domain', 'field': 'domain'},
]
actions = [
{'value': 'edit_tenant_domain', 'text': 'Edit Domain', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'create_tenant_domain', 'text': 'Register Domain', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'domain', 'dir': 'asc'}]
return {
'title': 'Domains',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'tenant_domains_table',
'form_action': url_for('user_bp.handle_tenant_domain_action'),
'description': f'Domains for tenant {tenant_id}'
}
# Tenant Projects list view helper
def get_tenant_projects_list_view(tenant_id):
"""Generate the tenant projects list view configuration for a specific tenant"""
# Get projects for the tenant
query = TenantProject.query.filter_by(tenant_id=tenant_id).order_by(TenantProject.id)
projects = query.all()
# Prepare data for Tabulator
data = []
for project in projects:
data.append({
'id': project.id,
'name': project.name,
'visual_api_key': project.visual_api_key,
'responsible_email': project.responsible_email,
'active': project.active
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'API Key', 'field': 'visual_api_key'},
{'title': 'Responsible', 'field': 'responsible_email'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
actions = [
{'value': 'edit_tenant_project', 'text': 'Edit Project', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'invalidate_tenant_project', 'text': 'Invalidate Project', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'delete_tenant_project', 'text': 'Delete Project', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'create_tenant_project', 'text': 'Register Project', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Projects',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'tenant_projects_table',
'form_action': url_for('user_bp.handle_tenant_project_selection'),
'description': f'Projects for tenant {tenant_id}'
}
# Tenant Makes list view helper
def get_tenant_makes_list_view(tenant_id):
"""Generate the tenant makes list view configuration for a specific tenant"""
# Get makes for the tenant
query = TenantMake.query.filter_by(tenant_id=tenant_id).order_by(TenantMake.id)
makes = query.all()
# Prepare data for Tabulator
data = []
for make in makes:
data.append({
'id': make.id,
'name': make.name,
'website': make.website,
'active': make.active
})
# Column Definitions
columns = [
{'title': 'ID', 'field': 'id', 'width': 80},
{'title': 'Name', 'field': 'name'},
{'title': 'Website', 'field': 'website'},
{'title': 'Active', 'field': 'active', 'formatter': 'tickCross'}
]
actions = [
{'value': 'edit_tenant_make', 'text': 'Edit Make', 'class': 'btn-primary', 'requiresSelection': True},
{'value': 'set_as_default', 'text': 'Set as Default', 'class': 'btn-secondary', 'requiresSelection': True},
{'value': 'create_tenant_make', 'text': 'Create Make', 'class': 'btn-success', 'position': 'right', 'requiresSelection': False},
]
initial_sort = [{'column': 'id', 'dir': 'asc'}]
return {
'title': 'Makes',
'data': data,
'columns': columns,
'actions': actions,
'initial_sort': initial_sort,
'table_id': 'tenant_makes_table',
'form_action': url_for('user_bp.handle_tenant_make_selection'),
'description': f'Makes for tenant {tenant_id}'
}