chore(deps): update pre-commit config and apply bulk formatting

- build(pre-commit): upgrade hooks (django-upgrade 1.29.1, uv 0.9.7, ruff 0.14.3, bandit 1.8.6)
- build(pre-commit): add uv-lock hook, tombi TOML formatter, prettier-plugin-packagejson
- build(pre-commit): disable Django check hooks (commented out)
- build(pre-commit): switch npx → bunx for prettier execution
- build(node): add bun.lock, update prettier config with schema + packagejson plugin
- style: apply ruff format to all Python files (comments, spacing, imports)
- style: apply prettier format to all JS/CSS files (comment styles, spacing)
- style: apply tombi format to pyproject.toml (reordered sections, consistent formatting)
- chore: remove emoji from bash script comments for consistency

BREAKING CHANGE: Django check and migration check hooks disabled in pre-commit config
This commit is contained in:
2025-11-05 14:34:08 +01:00
parent b1b5207888
commit c106792e78
35 changed files with 615 additions and 328 deletions

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
# !/usr/bin/env python
"""
Entry point for Django commands executed as Python modules.
This enables commands like `python -m runserver`.

View File

@@ -4,7 +4,7 @@ ASGI config for dashboard_project project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
<https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/>
"""
import os

View File

@@ -1,2 +1,3 @@
# dashboard/management/__init__.py
# This file is intentionally left empty to mark the directory as a Python package

View File

@@ -1,2 +1,3 @@
# dashboard/management/commands/__init__.py
# This file is intentionally left empty to mark the directory as a Python package

View File

@@ -1,2 +1,3 @@
# dashboard/templatetags/__init__.py
# This file is intentionally left empty to mark the directory as a Python package

View File

@@ -200,12 +200,10 @@ def chat_session_detail_view(request, session_id):
# Check if this is an AJAX navigation request
if is_ajax_navigation(request):
html_content = render_to_string("dashboard/chat_session_detail.html", context, request=request)
return JsonResponse(
{
"html": html_content,
"title": f"Chat Session {session_id} | Chat Analytics",
}
)
return JsonResponse({
"html": html_content,
"title": f"Chat Session {session_id} | Chat Analytics",
})
return render(request, "dashboard/chat_session_detail.html", context)
@@ -282,12 +280,10 @@ def edit_dashboard_view(request, dashboard_id):
# Check if this is an AJAX navigation request
if is_ajax_navigation(request):
html_content = render_to_string("dashboard/dashboard_form.html", context, request=request)
return JsonResponse(
{
"html": html_content,
"title": f"Edit Dashboard: {dashboard.name} | Chat Analytics",
}
)
return JsonResponse({
"html": html_content,
"title": f"Edit Dashboard: {dashboard.name} | Chat Analytics",
})
return render(request, "dashboard/dashboard_form.html", context)
@@ -349,6 +345,8 @@ def delete_data_source_view(request, data_source_id):
# API views for dashboard data
@login_required
def dashboard_data_api(request, dashboard_id):
"""API endpoint for dashboard data"""
@@ -450,26 +448,24 @@ def search_chat_sessions(request):
# Check if this is an AJAX pagination request
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
return JsonResponse(
{
"status": "success",
"html_data": render(request, "dashboard/partials/search_results_table.html", context).content.decode(
"utf-8"
),
"page_obj": {
"number": page_obj.number,
"has_previous": page_obj.has_previous(),
"has_next": page_obj.has_next(),
"previous_page_number": page_obj.previous_page_number() if page_obj.has_previous() else None,
"next_page_number": page_obj.next_page_number() if page_obj.has_next() else None,
"paginator": {
"num_pages": page_obj.paginator.num_pages,
"count": page_obj.paginator.count,
},
return JsonResponse({
"status": "success",
"html_data": render(request, "dashboard/partials/search_results_table.html", context).content.decode(
"utf-8"
),
"page_obj": {
"number": page_obj.number,
"has_previous": page_obj.has_previous(),
"has_next": page_obj.has_next(),
"previous_page_number": page_obj.previous_page_number() if page_obj.has_previous() else None,
"next_page_number": page_obj.next_page_number() if page_obj.has_next() else None,
"paginator": {
"num_pages": page_obj.paginator.num_pages,
"count": page_obj.paginator.count,
},
"query": query,
}
)
},
"query": query,
})
return render(request, "dashboard/search_results.html", context)
@@ -554,26 +550,24 @@ def data_view(request):
# Check if this is an AJAX pagination request
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
return JsonResponse(
{
"status": "success",
"html_data": render(request, "dashboard/partials/data_table.html", context).content.decode("utf-8"),
"page_obj": {
"number": page_obj.number,
"has_previous": page_obj.has_previous(),
"has_next": page_obj.has_next(),
"previous_page_number": page_obj.previous_page_number() if page_obj.has_previous() else None,
"next_page_number": page_obj.next_page_number() if page_obj.has_next() else None,
"paginator": {
"num_pages": page_obj.paginator.num_pages,
"count": page_obj.paginator.count,
},
return JsonResponse({
"status": "success",
"html_data": render(request, "dashboard/partials/data_table.html", context).content.decode("utf-8"),
"page_obj": {
"number": page_obj.number,
"has_previous": page_obj.has_previous(),
"has_next": page_obj.has_next(),
"previous_page_number": page_obj.previous_page_number() if page_obj.has_previous() else None,
"next_page_number": page_obj.next_page_number() if page_obj.has_next() else None,
"paginator": {
"num_pages": page_obj.paginator.num_pages,
"count": page_obj.paginator.count,
},
"view": view,
"avg_response_time": avg_response_time,
"avg_messages": avg_messages,
"escalation_rate": escalation_rate,
}
)
},
"view": view,
"avg_response_time": avg_response_time,
"avg_messages": avg_messages,
"escalation_rate": escalation_rate,
})
return render(request, "dashboard/data_view.html", context)

View File

@@ -91,51 +91,47 @@ def export_chats_csv(request):
writer = csv.writer(response)
# Write CSV header
writer.writerow(
[
"Session ID",
"Start Time",
"End Time",
"IP Address",
"Country",
"Language",
"Messages Sent",
"Sentiment",
"Escalated",
"Forwarded HR",
"Full Transcript",
"Avg Response Time (s)",
"Tokens",
"Tokens EUR",
"Category",
"Initial Message",
"User Rating",
]
)
writer.writerow([
"Session ID",
"Start Time",
"End Time",
"IP Address",
"Country",
"Language",
"Messages Sent",
"Sentiment",
"Escalated",
"Forwarded HR",
"Full Transcript",
"Avg Response Time (s)",
"Tokens",
"Tokens EUR",
"Category",
"Initial Message",
"User Rating",
])
# Write data rows
for session in sessions:
writer.writerow(
[
session.session_id,
session.start_time,
session.end_time,
session.ip_address,
session.country,
session.language,
session.messages_sent,
session.sentiment,
"Yes" if session.escalated else "No",
"Yes" if session.forwarded_hr else "No",
session.full_transcript,
session.avg_response_time,
session.tokens,
session.tokens_eur,
session.category,
session.initial_msg,
session.user_rating,
]
)
writer.writerow([
session.session_id,
session.start_time,
session.end_time,
session.ip_address,
session.country,
session.language,
session.messages_sent,
session.sentiment,
"Yes" if session.escalated else "No",
"Yes" if session.forwarded_hr else "No",
session.full_transcript,
session.avg_response_time,
session.tokens,
session.tokens_eur,
session.category,
session.initial_msg,
session.user_rating,
])
return response

View File

@@ -4,7 +4,7 @@ ASGI config for dashboard_project project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
<https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/>
"""
import os

View File

@@ -2,18 +2,24 @@ import os
from celery import Celery
# Set the default Django settings module for the 'celery' program.
# Set the default Django settings module for the 'celery' program
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings")
app = Celery("dashboard_project")
# Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# the configuration object to child processes
# - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
# should have a `CELERY_` prefix
app.config_from_object("django.conf:settings", namespace="CELERY")
# Load task modules from all registered Django app configs.
# Load task modules from all registered Django app configs
app.autodiscover_tasks()

View File

@@ -7,6 +7,7 @@ from pathlib import Path
from django.core.management.utils import get_random_secret_key
# Load environment variables from .env file if present
try:
from dotenv import load_dotenv
@@ -14,18 +15,22 @@ try:
except ImportError:
pass
# Build paths inside the project like this: BASE_DIR / 'subdir'.
# Build paths inside the project like this: BASE_DIR / 'subdir'
BASE_DIR = Path(__file__).resolve().parent.parent
# SECURITY WARNING: keep the secret key used in production secret!
# SECURITY WARNING: keep the secret key used in production secret
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", get_random_secret_key())
# SECURITY WARNING: don't run with debug turned on in production!
# SECURITY WARNING: don't run with debug turned on in production
DEBUG = os.environ.get("DJANGO_DEBUG", "True") == "True"
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
@@ -80,6 +85,7 @@ TEMPLATES = [
WSGI_APPLICATION = "dashboard_project.wsgi.application"
# Database
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
@@ -88,6 +94,7 @@ DATABASES = {
}
# Password validation
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
@@ -104,12 +111,14 @@ AUTH_PASSWORD_VALIDATORS = [
]
# Internationalization
LANGUAGE_CODE = "en-US"
TIME_ZONE = "Europe/Amsterdam"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
STATIC_URL = "static/"
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"),
@@ -125,23 +134,28 @@ STORAGES = {
}
# Media files
MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# Default primary key field type
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# Crispy Forms
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5"
# Authentication
AUTH_USER_MODEL = "accounts.CustomUser"
LOGIN_REDIRECT_URL = "dashboard"
LOGOUT_REDIRECT_URL = "login"
ACCOUNT_LOGOUT_ON_GET = True
# django-allauth
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
@@ -150,7 +164,9 @@ SITE_ID = 1
ACCOUNT_EMAIL_VERIFICATION = "none"
# Celery Configuration
# Check if Redis is available
try:
import redis
@@ -184,6 +200,7 @@ CELERY_TIMEZONE = TIME_ZONE
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
# Get schedule from environment variables or use defaults
CHAT_DATA_FETCH_INTERVAL = int(os.environ.get("CHAT_DATA_FETCH_INTERVAL", 3600)) # Default: 1 hour
CELERY_BEAT_SCHEDULE = {

View File

@@ -4,7 +4,7 @@ WSGI config for dashboard_project project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
<https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/>
"""
import os

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
# !/usr/bin/env python
"""
Migration Fix Script for ExternalDataSource

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
# !/usr/bin/env python
"""
Test the ExternalDataSource Model Schema

View File

@@ -39,7 +39,7 @@ class ChatMessage(models.Model):
class ExternalDataSource(models.Model):
name = models.CharField(max_length=255, default="External API")
api_url = models.URLField(default="https://proto.notso.ai/jumbo/chats")
api_url = models.URLField(default="<https://proto.notso.ai/jumbo/chats>")
auth_username = models.CharField(max_length=255, blank=True, null=True)
auth_password = models.CharField(
max_length=255, blank=True, null=True

View File

@@ -1 +1 @@
# Create your tests here.
# Create your tests here

View File

@@ -7,7 +7,7 @@ from .models import ExternalDataSource
from .tasks import periodic_fetch_chat_data, refresh_specific_source
from .utils import fetch_and_store_chat_data
# Create your views here.
# Create your views here
def is_superuser(user):

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
# !/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os

View File

@@ -4,6 +4,7 @@ import os
import sys
# Add the project root to sys.path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings")

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python
# !/usr/bin/env python
# scripts/fix_dashboard_data.py
import os
@@ -15,11 +16,13 @@ from django.db import transaction
from django.utils.timezone import make_aware
# Set up Django environment
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings")
django.setup()
# SCRIPT CONFIG
CREATE_TEST_DATA = False # Set to True to create sample data if none exists
COMPANY_NAME = "Notso AI" # The company name to use

View File

@@ -1,10 +1,11 @@
/**
* dashboard.css - Styles specific to dashboard functionality
* dashboard.css - Styles specific to dashboard functionality
*/
/* Theme variables */
/*Theme variables */
:root {
/* Light theme (default) */
/* Light theme (default)*/
--bg-color: #f8f9fa;
--text-color: #212529;
--card-bg: #ffffff;
@@ -26,7 +27,7 @@
color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}
/* Dark theme */
/*Dark theme*/
[data-bs-theme="dark"] {
--bg-color: #212529;
--text-color: #f8f9fa;
@@ -47,7 +48,7 @@
--icon-color: #6ea8fe;
}
/* Apply theme variables */
/*Apply theme variables*/
body {
background-color: var(--bg-color);
color: var(--text-color);
@@ -91,7 +92,7 @@ body {
background-color: var(--sidebar-bg) !important;
}
/* Sidebar navigation styling with dark mode support */
/*Sidebar navigation styling with dark mode support*/
.sidebar .nav-link {
color: var(--text-color);
transition: all 0.2s ease;
@@ -168,7 +169,7 @@ body {
color: var(--text-color);
}
/* Footer */
/*Footer*/
footer {
background-color: var(--card-bg);
border-top: 1px solid var(--border-color);
@@ -182,7 +183,7 @@ footer {
background-color: var(--navbar-bg);
}
/* Dashboard grid layout */
/*Dashboard grid layout*/
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
@@ -193,7 +194,7 @@ footer {
/* Increased gap */
}
/* Dashboard widget cards */
/*Dashboard widget cards*/
.dashboard-widget {
display: flex;
@@ -254,7 +255,7 @@ footer {
/* Consistent padding */
}
/* Chart widgets */
/*Chart widgets*/
.chart-widget .card-body {
display: flex;
flex-direction: column;
@@ -270,7 +271,7 @@ footer {
/* Ensure it takes full width of card body */
}
/* Stat widgets / Stat Cards */
/*Stat widgets / Stat Cards*/
.stat-card {
text-align: center;
padding: 1.5rem;
@@ -319,7 +320,7 @@ footer {
margin-bottom: 0;
}
/* Dashboard theme variations */
/*Dashboard theme variations*/
.dashboard-theme-light .card {
background-color: #fff;
}
@@ -344,7 +345,7 @@ footer {
color: #adb5bd;
}
/* Time period selector */
/*Time period selector*/
.time-period-selector {
display: flex;
align-items: center;
@@ -367,7 +368,7 @@ footer {
font-size: 0.875rem;
}
/* Custom metric selector */
/*Custom metric selector*/
.metric-selector {
max-width: 100%;
overflow-x: auto;
@@ -388,7 +389,7 @@ footer {
border-radius: 0.25rem;
}
/* Dashboard loading states */
/*Dashboard loading states*/
.widget-placeholder {
min-height: 300px;
background: linear-gradient(90deg, #e9ecef 25%, #f8f9fa 50%, #e9ecef 75%);
@@ -413,7 +414,7 @@ footer {
}
}
/* Dashboard empty states */
/*Dashboard empty states*/
.empty-state {
padding: 2.5rem;
@@ -449,7 +450,7 @@ footer {
margin-top: 1rem;
}
/* Responsive adjustments */
/*Responsive adjustments*/
@media (width <=767.98px) {
.dashboard-grid {
grid-template-columns: 1fr;
@@ -471,7 +472,7 @@ footer {
}
}
/* Preserve colored background for stat cards in both themes */
/*Preserve colored background for stat cards in both themes*/
.col-md-3 .card.stats-card.bg-primary {
background-color: var(--bs-primary) !important;
color: white !important;
@@ -507,7 +508,7 @@ footer {
color: var(--bs-dark) !important;
}
/* Stats Cards Alignment Fix (Bottom Align, No Overlap) */
/*Stats Cards Alignment Fix (Bottom Align, No Overlap)*/
.stats-row {
display: flex;
flex-wrap: wrap;

View File

@@ -1,8 +1,9 @@
/**
* style.css - Global styles for the application
* style.css - Global styles for the application
*/
/* General Styles */
/*General Styles*/
body {
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif,
@@ -25,14 +26,14 @@ body {
/* Ensures body takes at least full viewport height */
}
/* Navbar adjustments (if needed, Bootstrap usually handles this well) */
/*Navbar adjustments (if needed, Bootstrap usually handles this well)*/
.navbar {
box-shadow: 0 2px 4px rgb(0 0 0 / 5%);
/* Subtle shadow for depth */
}
/* Helper Classes */
/*Helper Classes*/
.text-truncate-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
@@ -49,7 +50,7 @@ body {
min-width: 150px;
}
/* Card styles */
/*Card styles*/
.card {
border: 1px solid #e0e5e9;
@@ -91,7 +92,7 @@ body {
font-weight: 600;
}
/* Sidebar enhancements */
/*Sidebar enhancements*/
.sidebar {
background-color: #fff;
@@ -159,7 +160,7 @@ body {
margin-top: 1rem;
}
/* Dashboard stats cards */
/*Dashboard stats cards*/
.stats-card {
border-radius: 0.5rem;
overflow: hidden;
@@ -176,14 +177,14 @@ body {
opacity: 0.8;
}
/* Chart containers */
/*Chart containers*/
.chart-container {
width: 100%;
height: 300px;
position: relative;
}
/* Loading overlay */
/*Loading overlay*/
.loading-overlay {
position: fixed;
top: 0;
@@ -197,7 +198,7 @@ body {
z-index: 9999;
}
/* Table enhancements */
/*Table enhancements*/
.table {
border-color: #e0e5e9;
}
@@ -224,7 +225,7 @@ body {
/* Consistent hover with sidebar */
}
/* Form improvements */
/*Form improvements*/
.form-control,
.form-select {
border-color: #ced4da;
@@ -246,7 +247,7 @@ body {
/* Bootstrap focus shadow */
}
/* Button styling */
/*Button styling*/
.btn {
border-radius: 0.375rem;
@@ -281,13 +282,13 @@ body {
border-color: #545b62;
}
/* Alert styling */
/*Alert styling*/
.alert {
border-radius: 0.375rem;
padding: 0.9rem 1.25rem;
}
/* Chat transcript styling */
/*Chat transcript styling*/
.chat-transcript {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
@@ -304,7 +305,7 @@ body {
margin-bottom: 0;
}
/* Footer styling */
/*Footer styling*/
footer {
background-color: #fff;
@@ -318,7 +319,7 @@ footer {
/* Added for sticky footer */
}
/* Responsive adjustments */
/*Responsive adjustments*/
@media (width <=767.98px) {
.main-content {
margin-left: 0;
@@ -337,7 +338,7 @@ footer {
}
}
/* Print styles */
/*Print styles*/
@media print {
.sidebar,
.navbar,

View File

@@ -1,8 +1,9 @@
/**
* ajax-navigation.js - JavaScript for AJAX-based navigation across the entire application
*
* This script handles AJAX navigation between pages in the Chat Analytics Dashboard.
* It intercepts link clicks, loads content via AJAX, and updates the browser history.
* ajax-navigation.js - JavaScript for AJAX-based navigation across the entire application
*
* This script handles AJAX navigation between pages in the Chat Analytics Dashboard.
* It intercepts link clicks, loads content via AJAX, and updates the browser history.
*/
document.addEventListener("DOMContentLoaded", function () {

View File

@@ -1,8 +1,9 @@
/**
* ajax-pagination.js - Common JavaScript for AJAX pagination across the application
*
* This script handles AJAX-based pagination for all pages in the Chat Analytics Dashboard.
* It intercepts pagination link clicks, loads content via AJAX, and updates the browser history.
* ajax-pagination.js - Common JavaScript for AJAX pagination across the application
*
* This script handles AJAX-based pagination for all pages in the Chat Analytics Dashboard.
* It intercepts pagination link clicks, loads content via AJAX, and updates the browser history.
*/
document.addEventListener("DOMContentLoaded", function () {

View File

@@ -1,9 +1,10 @@
/**
* dashboard.js - JavaScript for the dashboard functionality
*
* This file handles the interactive features of the dashboard,
* including chart refreshing, dashboard filtering, and dashboard
* customization.
* dashboard.js - JavaScript for the dashboard functionality
*
* This file handles the interactive features of the dashboard,
* including chart refreshing, dashboard filtering, and dashboard
* customization.
*/
document.addEventListener("DOMContentLoaded", function () {

View File

@@ -1,8 +1,9 @@
/**
* main.js - Global JavaScript functionality
*
* This file contains general JavaScript functionality used across
* the entire application, including navigation, forms, and UI interactions.
* main.js - Global JavaScript functionality
*
* This file contains general JavaScript functionality used across
* the entire application, including navigation, forms, and UI interactions.
*/
document.addEventListener("DOMContentLoaded", function () {

View File

@@ -4,7 +4,7 @@ WSGI config for dashboard_project project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
<https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/>
"""
import os