- Catalog functionality integrated into document and document_version views
- small bugfixes and improvements
This commit is contained in:
@@ -34,32 +34,32 @@ class Tenant(db.Model):
|
|||||||
embedding_model = db.Column(db.String(50), nullable=True)
|
embedding_model = db.Column(db.String(50), nullable=True)
|
||||||
llm_model = db.Column(db.String(50), nullable=True)
|
llm_model = db.Column(db.String(50), nullable=True)
|
||||||
|
|
||||||
# Embedding variables ==> To be removed once all migrations (dev + prod) have been done
|
# # Embedding variables ==> To be removed once all migrations (dev + prod) have been done
|
||||||
html_tags = db.Column(ARRAY(sa.String(10)), nullable=True, default=['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li'])
|
# html_tags = db.Column(ARRAY(sa.String(10)), nullable=True, default=['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li'])
|
||||||
html_end_tags = db.Column(ARRAY(sa.String(10)), nullable=True, default=['p', 'li'])
|
# html_end_tags = db.Column(ARRAY(sa.String(10)), nullable=True, default=['p', 'li'])
|
||||||
html_included_elements = db.Column(ARRAY(sa.String(50)), nullable=True)
|
# html_included_elements = db.Column(ARRAY(sa.String(50)), nullable=True)
|
||||||
html_excluded_elements = db.Column(ARRAY(sa.String(50)), nullable=True)
|
# html_excluded_elements = db.Column(ARRAY(sa.String(50)), nullable=True)
|
||||||
html_excluded_classes = db.Column(ARRAY(sa.String(200)), nullable=True)
|
# html_excluded_classes = db.Column(ARRAY(sa.String(200)), nullable=True)
|
||||||
|
#
|
||||||
min_chunk_size = db.Column(db.Integer, nullable=True, default=2000)
|
# min_chunk_size = db.Column(db.Integer, nullable=True, default=2000)
|
||||||
max_chunk_size = db.Column(db.Integer, nullable=True, default=3000)
|
# max_chunk_size = db.Column(db.Integer, nullable=True, default=3000)
|
||||||
|
#
|
||||||
# Embedding search variables
|
# # Embedding search variables
|
||||||
es_k = db.Column(db.Integer, nullable=True, default=5)
|
# es_k = db.Column(db.Integer, nullable=True, default=5)
|
||||||
es_similarity_threshold = db.Column(db.Float, nullable=True, default=0.7)
|
# es_similarity_threshold = db.Column(db.Float, nullable=True, default=0.7)
|
||||||
|
#
|
||||||
# Chat variables
|
# # Chat variables
|
||||||
chat_RAG_temperature = db.Column(db.Float, nullable=True, default=0.3)
|
# chat_RAG_temperature = db.Column(db.Float, nullable=True, default=0.3)
|
||||||
chat_no_RAG_temperature = db.Column(db.Float, nullable=True, default=0.5)
|
# chat_no_RAG_temperature = db.Column(db.Float, nullable=True, default=0.5)
|
||||||
fallback_algorithms = db.Column(ARRAY(sa.String(50)), nullable=True)
|
fallback_algorithms = db.Column(ARRAY(sa.String(50)), nullable=True)
|
||||||
|
|
||||||
# Licensing Information
|
# Licensing Information
|
||||||
encrypted_chat_api_key = db.Column(db.String(500), nullable=True)
|
encrypted_chat_api_key = db.Column(db.String(500), nullable=True)
|
||||||
encrypted_api_key = db.Column(db.String(500), nullable=True)
|
encrypted_api_key = db.Column(db.String(500), nullable=True)
|
||||||
|
|
||||||
# Tuning enablers
|
# # Tuning enablers
|
||||||
embed_tuning = db.Column(db.Boolean, nullable=True, default=False)
|
# embed_tuning = db.Column(db.Boolean, nullable=True, default=False)
|
||||||
rag_tuning = db.Column(db.Boolean, nullable=True, default=False)
|
# rag_tuning = db.Column(db.Boolean, nullable=True, default=False)
|
||||||
|
|
||||||
# Entitlements
|
# Entitlements
|
||||||
currency = db.Column(db.String(20), nullable=True)
|
currency = db.Column(db.String(20), nullable=True)
|
||||||
|
|||||||
@@ -319,9 +319,9 @@ def refresh_document_with_info(doc_id, tenant_id, api_input):
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
file_content = response.content
|
file_content = response.content
|
||||||
|
|
||||||
upload_file_for_version(new_doc_vers, file_content, extension, doc.tenant_id)
|
upload_file_for_version(new_doc_vers, file_content, extension, tenant_id)
|
||||||
|
|
||||||
task = current_celery.send_task('create_embeddings', args=[doc.tenant_id, new_doc_vers.id,], queue='embeddings')
|
task = current_celery.send_task('create_embeddings', args=[tenant_id, new_doc_vers.id,], queue='embeddings')
|
||||||
current_app.logger.info(f'Embedding creation started for document {doc_id} on version {new_doc_vers.id} '
|
current_app.logger.info(f'Embedding creation started for document {doc_id} on version {new_doc_vers.id} '
|
||||||
f'with task id: {task.id}.')
|
f'with task id: {task.id}.')
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/eveai_app.log',
|
'filename': 'logs/eveai_app.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -45,7 +45,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/eveai_workers.log',
|
'filename': 'logs/eveai_workers.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -53,7 +53,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/eveai_chat.log',
|
'filename': 'logs/eveai_chat.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -61,7 +61,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/eveai_chat_workers.log',
|
'filename': 'logs/eveai_chat_workers.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -69,7 +69,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/eveai_api.log',
|
'filename': 'logs/eveai_api.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -77,7 +77,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/eveai_beat.log',
|
'filename': 'logs/eveai_beat.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -85,7 +85,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/eveai_entitlements.log',
|
'filename': 'logs/eveai_entitlements.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -93,7 +93,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/sqlalchemy.log',
|
'filename': 'logs/sqlalchemy.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -101,7 +101,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/mailman.log',
|
'filename': 'logs/mailman.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -109,7 +109,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/security.log',
|
'filename': 'logs/security.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -117,7 +117,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/rag_tuning.log',
|
'filename': 'logs/rag_tuning.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -125,7 +125,7 @@ LOGGING = {
|
|||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/embed_tuning.log',
|
'filename': 'logs/embed_tuning.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
@@ -133,7 +133,7 @@ LOGGING = {
|
|||||||
'level': 'INFO',
|
'level': 'INFO',
|
||||||
'class': 'logging.handlers.RotatingFileHandler',
|
'class': 'logging.handlers.RotatingFileHandler',
|
||||||
'filename': 'logs/business_events.log',
|
'filename': 'logs/business_events.log',
|
||||||
'maxBytes': 1024 * 1024 * 5, # 5MB
|
'maxBytes': 1024 * 1024 * 1, # 1MB
|
||||||
'backupCount': 10,
|
'backupCount': 10,
|
||||||
'formatter': 'standard',
|
'formatter': 'standard',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -23,15 +23,23 @@
|
|||||||
|
|
||||||
{{ render_collapsible_section('Filter', 'Filter Options', filter_form) }}
|
{{ render_collapsible_section('Filter', 'Filter Options', filter_form) }}
|
||||||
|
|
||||||
<!-- Document Versions Table -->
|
<div class="form-group mt-3">
|
||||||
{{ render_selectable_sortable_table(
|
<form method="POST" action="{{ url_for('document_bp.handle_document_version_selection') }}">
|
||||||
headers=["ID", "File Type", "Processing", "Processing Start", "Processing Finish", "Processing Error"],
|
<!-- Document Versions Table -->
|
||||||
rows=rows,
|
{{ render_selectable_sortable_table(
|
||||||
selectable=True,
|
headers=["ID", "File Type", "Processing", "Processing Start", "Processing Finish", "Processing Error"],
|
||||||
id="documentVersionsTable",
|
rows=rows,
|
||||||
sort_by=sort_by,
|
selectable=True,
|
||||||
sort_order=sort_order
|
id="documentVersionsTable",
|
||||||
) }}
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order
|
||||||
|
) }}
|
||||||
|
<div class="form-group mt-4">
|
||||||
|
<button type="submit" name="action" value="edit_document_version" class="btn btn-primary">Edit Document Version</button>
|
||||||
|
<button type="submit" name="action" value="process_document_version" class="btn btn-danger">Process Document Version</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_footer %}
|
{% block content_footer %}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% from 'macros.html' import render_selectable_table, render_pagination %}
|
{% from 'macros.html' import render_selectable_table, render_pagination, render_filter_field, render_date_filter_field, render_collapsible_section, render_selectable_sortable_table_with_dict_headers %}
|
||||||
|
|
||||||
{% block title %}Documents{% endblock %}
|
{% block title %}Documents{% endblock %}
|
||||||
|
|
||||||
@@ -8,18 +8,88 @@
|
|||||||
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
|
{% block content_class %}<div class="col-xl-12 col-lg-5 col-md-7 mx-auto"></div>{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container">
|
<!-- Filter Form -->
|
||||||
<form method="POST" action="{{ url_for('document_bp.handle_document_selection') }}">
|
{% set filter_form %}
|
||||||
{{ render_selectable_table(headers=["Document ID", "Name", "Valid From", "Valid To"], rows=rows, selectable=True, id="documentsTable") }}
|
<form method="GET" action="{{ url_for('document_bp.documents') }}">
|
||||||
<div class="form-group mt-3">
|
{{ render_filter_field('catalog_id', 'Catalog', filter_options['catalog_id'], filters.get('catalog_id', [])) }}
|
||||||
<button type="submit" name="action" value="edit_document" class="btn btn-primary">Edit Document</button>
|
{{ render_filter_field('validity', 'Validity', filter_options['validity'], filters.get('validity', [])) }}
|
||||||
<button type="submit" name="action" value="document_versions" class="btn btn-secondary">Show Document Versions</button>
|
|
||||||
<button type="submit" name="action" value="refresh_document" class="btn btn-secondary">Refresh Document (new version)</button>
|
<button type="submit" class="btn btn-primary">Apply Filters</button>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
{% endset %}
|
||||||
</div>
|
|
||||||
|
{{ render_collapsible_section('Filter', 'Filter Options', filter_form) }}
|
||||||
|
|
||||||
|
<div class="form-group mt-3">
|
||||||
|
<form method="POST" action="{{ url_for('document_bp.handle_document_selection') }}">
|
||||||
|
<!-- Documents Table -->
|
||||||
|
{{ render_selectable_sortable_table_with_dict_headers(
|
||||||
|
headers=[
|
||||||
|
{"text": "ID", "sort": "id"},
|
||||||
|
{"text": "Name", "sort": "name"},
|
||||||
|
{"text": "Catalog", "sort": "catalog_name"},
|
||||||
|
{"text": "Valid From", "sort": "valid_from"},
|
||||||
|
{"text": "Valid To", "sort": "valid_to"}
|
||||||
|
],
|
||||||
|
rows=rows,
|
||||||
|
selectable=True,
|
||||||
|
id="documentsTable",
|
||||||
|
sort_by=sort_by,
|
||||||
|
sort_order=sort_order
|
||||||
|
) }}
|
||||||
|
<div class="form-group mt-4">
|
||||||
|
<button type="submit" name="action" value="edit_document" class="btn btn-primary">Edit Document</button>
|
||||||
|
<button type="submit" name="action" value="document_versions" class="btn btn-secondary">Show Document Versions</button>
|
||||||
|
<button type="submit" name="action" value="refresh_document" class="btn btn-secondary">Refresh Document (new version)</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_footer %}
|
{% block content_footer %}
|
||||||
{{ render_pagination(pagination, 'document_bp.documents') }}
|
{{ render_pagination(pagination, 'document_bp.documents') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const table = document.getElementById('documentsTable');
|
||||||
|
const headers = table.querySelectorAll('th.sortable');
|
||||||
|
|
||||||
|
headers.forEach(header => {
|
||||||
|
header.addEventListener('click', function() {
|
||||||
|
const sortBy = this.dataset.sort;
|
||||||
|
let sortOrder = 'asc';
|
||||||
|
|
||||||
|
if (this.querySelector('.fa-sort-up')) {
|
||||||
|
sortOrder = 'desc';
|
||||||
|
} else if (this.querySelector('.fa-sort-down')) {
|
||||||
|
sortOrder = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = updateQueryStringParameter(window.location.href, 'sort_by', sortBy);
|
||||||
|
window.location.href = updateQueryStringParameter(window.location.href, 'sort_order', sortOrder);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateQueryStringParameter(uri, key, value) {
|
||||||
|
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
|
||||||
|
var separator = uri.indexOf('?') !== -1 ? "&" : "?";
|
||||||
|
if (uri.match(re)) {
|
||||||
|
return uri.replace(re, '$1' + key + "=" + value + '$2');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return uri + separator + key + "=" + value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table.addEventListener('change', function(event) {
|
||||||
|
if (event.target.type === 'radio') {
|
||||||
|
var selectedRow = event.target.closest('tr');
|
||||||
|
var documentId = selectedRow.cells[1].textContent;
|
||||||
|
console.log('Selected Document ID:', documentId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -8,11 +8,17 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
{% set disabled_fields = [] %}
|
{% set disabled_fields = [] %}
|
||||||
{% set exclude_fields = [] %}
|
{% set exclude_fields = [] %}
|
||||||
{% for field in form %}
|
|
||||||
{{ render_field(field, disabled_fields, exclude_fields) }}
|
{{ render_field(form.name, disabled_fields, exclude_fields) }}
|
||||||
{% endfor %}
|
{{ render_field(form.valid_from, disabled_fields, exclude_fields) }}
|
||||||
|
{{ render_field(form.valid_to, disabled_fields, exclude_fields) }}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="catalog_name">Catalog</label>
|
||||||
|
<input type="text" class="form-control" id="catalog_name" value="{{ catalog_name }}" readonly>
|
||||||
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Update Document</button>
|
<button type="submit" class="btn btn-primary">Update Document</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<header class="header-2">
|
<header class="header-2">
|
||||||
<div class="page-header min-vh-25" style="background-image: url({{url_for('static', filename='/assets/img/EveAI_bg.jpg')}})" loading="lazy">
|
<div class="page-header min-vh-25" style="background-image: url({{url_for('static', filename='/assets/img/EveAI_bg.jpg')}}); background-position: top left; background-repeat: no-repeat; background-size: cover;" loading="lazy">
|
||||||
<span class="mask bg-gradient-primary opacity-4"></span>
|
<span class="mask bg-gradient-primary opacity-4"></span>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -177,6 +177,48 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro render_selectable_sortable_table_with_dict_headers(headers, rows, selectable, id, sort_by, sort_order) %}
|
||||||
|
<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 sortable" data-sort="{{ header['sort'] }}">
|
||||||
|
{{ header['text'] }}
|
||||||
|
{% if sort_by == header['sort'] %}
|
||||||
|
{% if sort_order == 'asc' %}
|
||||||
|
<i class="fas fa-sort-up"></i>
|
||||||
|
{% elif sort_order == 'desc' %}
|
||||||
|
<i class="fas fa-sort-down"></i>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<i class="fas fa-sort"></i>
|
||||||
|
{% endif %}
|
||||||
|
</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for row in rows %}
|
||||||
|
<tr>
|
||||||
|
{% if selectable %}
|
||||||
|
<td><input type="radio" name="selected_row" value="{{ row[0].value }}"></td>
|
||||||
|
{% endif %}
|
||||||
|
{% for cell in row %}
|
||||||
|
<td>{{ cell.value }}</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">
|
||||||
|
|||||||
102
eveai_app/views/document_list_view.py
Normal file
102
eveai_app/views/document_list_view.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from flask import request, render_template, session
|
||||||
|
from sqlalchemy import desc, asc, or_, and_, cast, Integer
|
||||||
|
from common.models.document import Document, Catalog
|
||||||
|
from common.utils.filtered_list_view import FilteredListView
|
||||||
|
from common.utils.view_assistants import prepare_table_for_macro
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentListView(FilteredListView):
|
||||||
|
allowed_filters = ['catalog_id', 'validity']
|
||||||
|
allowed_sorts = ['id', 'name', 'catalog_name', 'valid_from', 'valid_to']
|
||||||
|
|
||||||
|
def get_query(self):
|
||||||
|
return Document.query.join(Catalog).add_columns(
|
||||||
|
Document.id,
|
||||||
|
Document.name,
|
||||||
|
Catalog.name.label('catalog_name'),
|
||||||
|
Document.valid_from,
|
||||||
|
Document.valid_to
|
||||||
|
)
|
||||||
|
|
||||||
|
def apply_filters(self, query):
|
||||||
|
filters = request.args.to_dict(flat=False)
|
||||||
|
|
||||||
|
if 'catalog_id' in filters:
|
||||||
|
catalog_ids = filters['catalog_id']
|
||||||
|
if catalog_ids:
|
||||||
|
# Convert catalog_ids to a list of integers
|
||||||
|
catalog_ids = [int(cid) for cid in catalog_ids if cid.isdigit()]
|
||||||
|
if catalog_ids:
|
||||||
|
query = query.filter(Document.catalog_id.in_(catalog_ids))
|
||||||
|
|
||||||
|
if 'validity' in filters:
|
||||||
|
now = datetime.utcnow().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:
|
||||||
|
if sort_by == 'catalog_name':
|
||||||
|
column = Catalog.name
|
||||||
|
else:
|
||||||
|
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, datetime):
|
||||||
|
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': item.catalog_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
|
||||||
|
]
|
||||||
|
|
||||||
|
catalogs = Catalog.query.all()
|
||||||
|
|
||||||
|
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(catalogs)
|
||||||
|
}
|
||||||
|
return render_template(self.template, **context)
|
||||||
|
|
||||||
|
def get_filter_options(self, catalogs):
|
||||||
|
return {
|
||||||
|
'catalog_id': [(str(cat.id), cat.name) for cat in catalogs],
|
||||||
|
'validity': [('valid', 'Valid'), ('all', 'All')]
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ class DocumentVersionListView(FilteredListView):
|
|||||||
allowed_sorts = ['id', 'processing_started_at', 'processing_finished_at', 'processing_error']
|
allowed_sorts = ['id', 'processing_started_at', 'processing_finished_at', 'processing_error']
|
||||||
|
|
||||||
def get_query(self):
|
def get_query(self):
|
||||||
return DocumentVersion.query.join(Document).filter(Document.tenant_id == session.get('tenant', {}).get('id'))
|
return DocumentVersion.query.join(Document)
|
||||||
|
|
||||||
def apply_filters(self, query):
|
def apply_filters(self, query):
|
||||||
filters = request.args.to_dict()
|
filters = request.args.to_dict()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from babel.messages.setuptools_frontend import update_catalog
|
|||||||
from flask import request, redirect, flash, render_template, Blueprint, session, current_app
|
from flask import request, redirect, flash, render_template, Blueprint, session, current_app
|
||||||
from flask_security import roles_accepted, current_user
|
from flask_security import roles_accepted, current_user
|
||||||
from sqlalchemy import desc
|
from sqlalchemy import desc
|
||||||
|
from sqlalchemy.orm import aliased
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
import requests
|
import requests
|
||||||
@@ -26,6 +27,7 @@ from common.utils.middleware import mw_before_request
|
|||||||
from common.utils.celery_utils import current_celery
|
from common.utils.celery_utils import current_celery
|
||||||
from common.utils.nginx_utils import prefixed_url_for
|
from common.utils.nginx_utils import prefixed_url_for
|
||||||
from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro, form_to_dict
|
from common.utils.view_assistants import form_validation_failed, prepare_table_for_macro, form_to_dict
|
||||||
|
from .document_list_view import DocumentListView
|
||||||
from .document_version_list_view import DocumentVersionListView
|
from .document_version_list_view import DocumentVersionListView
|
||||||
|
|
||||||
document_bp = Blueprint('document_bp', __name__, url_prefix='/document')
|
document_bp = Blueprint('document_bp', __name__, url_prefix='/document')
|
||||||
@@ -286,22 +288,23 @@ def add_urls():
|
|||||||
@document_bp.route('/documents', methods=['GET', 'POST'])
|
@document_bp.route('/documents', methods=['GET', 'POST'])
|
||||||
@roles_accepted('Super User', 'Tenant Admin')
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
def documents():
|
def documents():
|
||||||
page = request.args.get('page', 1, type=int)
|
view = DocumentListView(Document, 'document/documents.html', per_page=10)
|
||||||
per_page = request.args.get('per_page', 10, type=int)
|
return view.get()
|
||||||
|
|
||||||
pagination = get_documents_list(page, per_page)
|
|
||||||
docs = pagination.items
|
|
||||||
|
|
||||||
rows = prepare_table_for_macro(docs, [('id', ''), ('name', ''), ('valid_from', ''), ('valid_to', '')])
|
|
||||||
|
|
||||||
return render_template('document/documents.html', rows=rows, pagination=pagination)
|
|
||||||
|
|
||||||
|
|
||||||
@document_bp.route('/handle_document_selection', methods=['POST'])
|
@document_bp.route('/handle_document_selection', methods=['POST'])
|
||||||
@roles_accepted('Super User', 'Tenant Admin')
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
def handle_document_selection():
|
def handle_document_selection():
|
||||||
document_identification = request.form['selected_row']
|
document_identification = request.form['selected_row']
|
||||||
doc_id = ast.literal_eval(document_identification).get('value')
|
if isinstance(document_identification, int) or document_identification.isdigit():
|
||||||
|
doc_id = int(document_identification)
|
||||||
|
else:
|
||||||
|
# If it's not an integer, assume it's a string representation of a dictionary
|
||||||
|
try:
|
||||||
|
doc_id = ast.literal_eval(document_identification).get('value')
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
flash('Invalid document selection.', 'error')
|
||||||
|
return redirect(prefixed_url_for('document_bp.documents'))
|
||||||
|
|
||||||
action = request.form['action']
|
action = request.form['action']
|
||||||
|
|
||||||
@@ -323,9 +326,25 @@ def handle_document_selection():
|
|||||||
@document_bp.route('/edit_document/<int:document_id>', methods=['GET', 'POST'])
|
@document_bp.route('/edit_document/<int:document_id>', methods=['GET', 'POST'])
|
||||||
@roles_accepted('Super User', 'Tenant Admin')
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
def edit_document_view(document_id):
|
def edit_document_view(document_id):
|
||||||
doc = Document.query.get_or_404(document_id)
|
# Use an alias for the Catalog to avoid column name conflicts
|
||||||
|
CatalogAlias = aliased(Catalog)
|
||||||
|
|
||||||
|
# Query for the document and its catalog
|
||||||
|
result = db.session.query(Document, CatalogAlias.name.label('catalog_name')) \
|
||||||
|
.join(CatalogAlias, Document.catalog_id == CatalogAlias.id) \
|
||||||
|
.filter(Document.id == document_id) \
|
||||||
|
.first_or_404()
|
||||||
|
|
||||||
|
doc, catalog_name = result
|
||||||
|
|
||||||
form = EditDocumentForm(obj=doc)
|
form = EditDocumentForm(obj=doc)
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
# Populate form with current values
|
||||||
|
form.name.data = doc.name
|
||||||
|
form.valid_from.data = doc.valid_from
|
||||||
|
form.valid_to.data = doc.valid_to
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
updated_doc, error = edit_document(
|
updated_doc, error = edit_document(
|
||||||
document_id,
|
document_id,
|
||||||
@@ -341,7 +360,7 @@ def edit_document_view(document_id):
|
|||||||
else:
|
else:
|
||||||
form_validation_failed(request, form)
|
form_validation_failed(request, form)
|
||||||
|
|
||||||
return render_template('document/edit_document.html', form=form, document_id=document_id)
|
return render_template('document/edit_document.html', form=form, document_id=document_id, catalog_name=catalog_name)
|
||||||
|
|
||||||
|
|
||||||
@document_bp.route('/edit_document_version/<int:document_version_id>', methods=['GET', 'POST'])
|
@document_bp.route('/edit_document_version/<int:document_version_id>', methods=['GET', 'POST'])
|
||||||
@@ -395,7 +414,15 @@ def document_versions(document_id):
|
|||||||
@roles_accepted('Super User', 'Tenant Admin')
|
@roles_accepted('Super User', 'Tenant Admin')
|
||||||
def handle_document_version_selection():
|
def handle_document_version_selection():
|
||||||
document_version_identification = request.form['selected_row']
|
document_version_identification = request.form['selected_row']
|
||||||
doc_vers_id = ast.literal_eval(document_version_identification).get('value')
|
if isinstance(document_version_identification, int) or document_version_identification.isdigit():
|
||||||
|
doc_vers_id = int(document_version_identification)
|
||||||
|
else:
|
||||||
|
# If it's not an integer, assume it's a string representation of a dictionary
|
||||||
|
try:
|
||||||
|
doc_vers_id = ast.literal_eval(document_version_identification).get('value')
|
||||||
|
except (ValueError, AttributeError):
|
||||||
|
flash('Invalid document version selection.', 'error')
|
||||||
|
return redirect(prefixed_url_for('document_bp.document_versions_list'))
|
||||||
|
|
||||||
action = request.form['action']
|
action = request.form['action']
|
||||||
|
|
||||||
@@ -447,7 +474,7 @@ def refresh_all_documents():
|
|||||||
|
|
||||||
|
|
||||||
def refresh_document_view(document_id):
|
def refresh_document_view(document_id):
|
||||||
new_version, result = refresh_document(document_id)
|
new_version, result = refresh_document(document_id, session['tenant']['id'])
|
||||||
if new_version:
|
if new_version:
|
||||||
flash(f'Document refreshed. New version: {new_version.id}. Task ID: {result}', 'success')
|
flash(f'Document refreshed. New version: {new_version.id}. Task ID: {result}', 'success')
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -48,22 +48,6 @@ def tenant():
|
|||||||
new_tenant = Tenant()
|
new_tenant = Tenant()
|
||||||
form.populate_obj(new_tenant)
|
form.populate_obj(new_tenant)
|
||||||
|
|
||||||
# Handle Embedding Variables
|
|
||||||
new_tenant.html_tags = [tag.strip() for tag in form.html_tags.data.split(',')] if form.html_tags.data else []
|
|
||||||
new_tenant.html_end_tags = [tag.strip() for tag in form.html_end_tags.data.split(',')] \
|
|
||||||
if form.html_end_tags.data else []
|
|
||||||
new_tenant.html_included_elements = [tag.strip() for tag in form.html_included_elements.data.split(',')] \
|
|
||||||
if form.html_included_elements.data else []
|
|
||||||
new_tenant.html_excluded_elements = [tag.strip() for tag in form.html_excluded_elements.data.split(',')] \
|
|
||||||
if form.html_excluded_elements.data else []
|
|
||||||
new_tenant.html_excluded_classes = [cls.strip() for cls in form.html_excluded_classes.data.split(',')] \
|
|
||||||
if form.html_excluded_classes.data else []
|
|
||||||
|
|
||||||
current_app.logger.debug(f'html_tags: {new_tenant.html_tags},'
|
|
||||||
f'html_end_tags: {new_tenant.html_end_tags},'
|
|
||||||
f'html_included_elements: {new_tenant.html_included_elements},'
|
|
||||||
f'html_excluded_elements: {new_tenant.html_excluded_elements}')
|
|
||||||
|
|
||||||
# Handle Timestamps
|
# Handle Timestamps
|
||||||
timestamp = dt.now(tz.utc)
|
timestamp = dt.now(tz.utc)
|
||||||
new_tenant.created_at = timestamp
|
new_tenant.created_at = timestamp
|
||||||
@@ -105,30 +89,11 @@ def edit_tenant(tenant_id):
|
|||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
# Populate the form with tenant data
|
# Populate the form with tenant data
|
||||||
form.populate_obj(tenant)
|
form.populate_obj(tenant)
|
||||||
if tenant.html_tags:
|
|
||||||
form.html_tags.data = ', '.join(tenant.html_tags)
|
|
||||||
if tenant.html_end_tags:
|
|
||||||
form.html_end_tags.data = ', '.join(tenant.html_end_tags)
|
|
||||||
if tenant.html_included_elements:
|
|
||||||
form.html_included_elements.data = ', '.join(tenant.html_included_elements)
|
|
||||||
if tenant.html_excluded_elements:
|
|
||||||
form.html_excluded_elements.data = ', '.join(tenant.html_excluded_elements)
|
|
||||||
if tenant.html_excluded_classes:
|
|
||||||
form.html_excluded_classes.data = ', '.join(tenant.html_excluded_classes)
|
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
current_app.logger.debug(f'Updating tenant {tenant_id}')
|
current_app.logger.debug(f'Updating tenant {tenant_id}')
|
||||||
# Populate the tenant with form data
|
# Populate the tenant with form data
|
||||||
form.populate_obj(tenant)
|
form.populate_obj(tenant)
|
||||||
# Then handle the special fields manually
|
|
||||||
tenant.html_tags = [tag.strip() for tag in form.html_tags.data.split(',') if tag.strip()]
|
|
||||||
tenant.html_end_tags = [tag.strip() for tag in form.html_end_tags.data.split(',') if tag.strip()]
|
|
||||||
tenant.html_included_elements = [elem.strip() for elem in form.html_included_elements.data.split(',') if
|
|
||||||
elem.strip()]
|
|
||||||
tenant.html_excluded_elements = [elem.strip() for elem in form.html_excluded_elements.data.split(',') if
|
|
||||||
elem.strip()]
|
|
||||||
tenant.html_excluded_classes = [elem.strip() for elem in form.html_excluded_classes.data.split(',') if
|
|
||||||
elem.strip()]
|
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Tenant updated successfully.', 'success')
|
flash('Tenant updated successfully.', 'success')
|
||||||
|
|||||||
Reference in New Issue
Block a user