Initial commit

This commit is contained in:
2025-05-17 00:57:08 +02:00
commit fe69bdbc94
71 changed files with 6585 additions and 0 deletions

View File

@ -0,0 +1 @@
# This file is intentionally left empty to mark the directory as a Python package

View File

@ -0,0 +1,65 @@
# dashboard/admin.py
from django.contrib import admin
from .models import ChatSession, Dashboard, DataSource
class DataSourceAdmin(admin.ModelAdmin):
list_display = ("name", "company", "uploaded_at", "get_session_count")
list_filter = ("company", "uploaded_at")
search_fields = ("name", "description", "company__name")
ordering = ("-uploaded_at",)
def get_session_count(self, obj):
return obj.chat_sessions.count()
get_session_count.short_description = "Sessions"
class ChatSessionAdmin(admin.ModelAdmin):
list_display = (
"session_id",
"get_company",
"start_time",
"end_time",
"country",
"language",
"sentiment",
)
list_filter = (
"data_source__company",
"start_time",
"country",
"language",
"sentiment",
"escalated",
"forwarded_hr",
)
search_fields = (
"session_id",
"country",
"language",
"initial_msg",
"full_transcript",
)
ordering = ("-start_time",)
def get_company(self, obj):
return obj.data_source.company.name
get_company.short_description = "Company"
get_company.admin_order_field = "data_source__company__name"
class DashboardAdmin(admin.ModelAdmin):
list_display = ("name", "company", "created_at", "updated_at")
list_filter = ("company", "created_at")
search_fields = ("name", "description", "company__name")
filter_horizontal = ("data_sources",)
ordering = ("-updated_at",)
admin.site.register(DataSource, DataSourceAdmin)
admin.site.register(ChatSession, ChatSessionAdmin)
admin.site.register(Dashboard, DashboardAdmin)

View File

@ -0,0 +1,8 @@
# dashboard/apps.py
from django.apps import AppConfig
class DashboardConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "dashboard"

View File

@ -0,0 +1,49 @@
# dashboard/forms.py
from django import forms
from .models import Dashboard, DataSource
class DataSourceUploadForm(forms.ModelForm):
"""Form for uploading CSV files"""
class Meta:
model = DataSource
fields = ["name", "description", "file"]
def __init__(self, *args, **kwargs):
self.company = kwargs.pop("company", None)
super().__init__(*args, **kwargs)
def save(self, commit=True):
instance = super().save(commit=False)
if self.company:
instance.company = self.company
if commit:
instance.save()
return instance
class DashboardForm(forms.ModelForm):
"""Form for creating and editing dashboards"""
class Meta:
model = Dashboard
fields = ["name", "description", "data_sources"]
def __init__(self, *args, **kwargs):
self.company = kwargs.pop("company", None)
super().__init__(*args, **kwargs)
if self.company:
self.fields["data_sources"].queryset = DataSource.objects.filter(company=self.company)
def save(self, commit=True):
instance = super().save(commit=False)
if self.company:
instance.company = self.company
if commit:
instance.save()
self.save_m2m()
return instance

View File

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

View File

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

View File

@ -0,0 +1,277 @@
# dashboard/management/commands/create_sample_data.py
import csv
import io
import random
from datetime import datetime, timedelta
from accounts.models import Company
from dashboard.models import ChatSession, Dashboard, DataSource
from django.contrib.auth import get_user_model
from django.core.files.base import ContentFile
from django.core.management.base import BaseCommand
from django.utils import timezone
User = get_user_model()
class Command(BaseCommand):
help = "Create sample data for testing"
def handle(self, *args, **kwargs):
self.stdout.write("Creating sample data...")
# Create admin user if it doesn't exist
if not User.objects.filter(username="admin").exists():
admin_user = User.objects.create_superuser(username="admin", email="admin@example.com", password="admin123")
self.stdout.write(self.style.SUCCESS(f"Created admin user: {admin_user.username}"))
else:
admin_user = User.objects.get(username="admin")
self.stdout.write(f"Admin user already exists: {admin_user.username}")
# Create companies
companies = []
company_names = ["Acme Inc.", "TechCorp", "GlobalServices"]
for name in company_names:
company, created = Company.objects.get_or_create(
name=name, defaults={"description": f"Sample company: {name}"}
)
companies.append(company)
if created:
self.stdout.write(self.style.SUCCESS(f"Created company: {company.name}"))
else:
self.stdout.write(f"Company already exists: {company.name}")
# Create users for each company
for i, company in enumerate(companies):
# Company admin
username = f"admin_{company.name.lower().replace(' ', '_')}"
if not User.objects.filter(username=username).exists():
user = User.objects.create_user(
username=username,
email=f"{username}@example.com",
password="password123",
company=company,
is_company_admin=True,
)
self.stdout.write(self.style.SUCCESS(f"Created company admin: {user.username}"))
# Regular users
for j in range(2):
username = f"user_{company.name.lower().replace(' ', '_')}_{j + 1}"
if not User.objects.filter(username=username).exists():
user = User.objects.create_user(
username=username,
email=f"{username}@example.com",
password="password123",
company=company,
)
self.stdout.write(self.style.SUCCESS(f"Created user: {user.username}"))
# Create sample data for each company
for company in companies:
self._create_sample_data_for_company(company)
self.stdout.write(self.style.SUCCESS("Sample data created successfully!"))
def _create_sample_data_for_company(self, company):
# Create sample CSV data
csv_data = self._generate_sample_csv_data(company.name)
# Create data source
data_source_name = f"{company.name} Chat Data"
try:
data_source = DataSource.objects.get(name=data_source_name, company=company)
self.stdout.write(f"Data source already exists: {data_source.name}")
except DataSource.DoesNotExist:
# Create file from CSV data
csv_file = ContentFile(csv_data.encode("utf-8"))
data_source = DataSource.objects.create(
name=data_source_name,
description=f"Sample chat data for {company.name}",
company=company,
)
data_source.file.save(f"{company.name.lower().replace(' ', '_')}_chat_data.csv", csv_file)
self.stdout.write(self.style.SUCCESS(f"Created data source: {data_source.name}"))
# Parse CSV data and create chat sessions
reader = csv.DictReader(io.StringIO(csv_data))
for row in reader:
# Convert datetime strings to datetime objects
start_time = datetime.strptime(row["start_time"], "%Y-%m-%d %H:%M:%S")
end_time = datetime.strptime(row["end_time"], "%Y-%m-%d %H:%M:%S")
# Convert boolean strings to actual booleans
escalated = row["escalated"].lower() in ["true", "yes", "1", "t", "y"]
forwarded_hr = row["forwarded_hr"].lower() in [
"true",
"yes",
"1",
"t",
"y",
]
# Create chat session
ChatSession.objects.create(
data_source=data_source,
session_id=row["session_id"],
start_time=timezone.make_aware(start_time),
end_time=timezone.make_aware(end_time),
ip_address=row["ip_address"],
country=row["country"],
language=row["language"],
messages_sent=int(row["messages_sent"]),
sentiment=row["sentiment"],
escalated=escalated,
forwarded_hr=forwarded_hr,
full_transcript=row["full_transcript"],
avg_response_time=float(row["avg_response_time"]),
tokens=int(row["tokens"]),
tokens_eur=float(row["tokens_eur"]),
category=row["category"],
initial_msg=row["initial_msg"],
user_rating=row["user_rating"],
)
self.stdout.write(self.style.SUCCESS(f"Created {reader.line_num} chat sessions"))
# Create default dashboard
dashboard_name = f"{company.name} Dashboard"
try:
dashboard = Dashboard.objects.get(name=dashboard_name, company=company)
self.stdout.write(f"Dashboard already exists: {dashboard.name}")
except Dashboard.DoesNotExist:
dashboard = Dashboard.objects.create(
name=dashboard_name,
description=f"Default dashboard for {company.name}",
company=company,
)
dashboard.data_sources.add(data_source)
self.stdout.write(self.style.SUCCESS(f"Created dashboard: {dashboard.name}"))
def _generate_sample_csv_data(self, company_name):
"""Generate sample CSV data for a company"""
rows = []
headers = [
"session_id",
"start_time",
"end_time",
"ip_address",
"country",
"language",
"messages_sent",
"sentiment",
"escalated",
"forwarded_hr",
"full_transcript",
"avg_response_time",
"tokens",
"tokens_eur",
"category",
"initial_msg",
"user_rating",
]
# Sample data for generating random values
countries = [
"USA",
"UK",
"Germany",
"France",
"Spain",
"Italy",
"Japan",
"Australia",
"Canada",
"Brazil",
]
languages = ["English", "Spanish", "German", "French", "Japanese", "Portuguese"]
sentiments = [
"Positive",
"Negative",
"Neutral",
"Very Positive",
"Very Negative",
]
categories = ["Support", "Sales", "Technical", "Billing", "General"]
ratings = ["Excellent", "Good", "Average", "Poor", "Terrible", ""]
# Generate rows
num_rows = random.randint(50, 100)
for i in range(num_rows):
# Generate random dates in the last 30 days
end_date = datetime.now() - timedelta(days=random.randint(0, 30))
start_date = end_date - timedelta(minutes=random.randint(5, 60))
# Generate random IP address
ip = ".".join(str(random.randint(0, 255)) for _ in range(4))
# Random country and language
country = random.choice(countries)
language = random.choice(languages)
# Random message count
messages_sent = random.randint(3, 20)
# Random sentiment
sentiment = random.choice(sentiments)
# Random escalation and forwarding
escalated = random.random() < 0.2 # 20% chance of escalation
forwarded_hr = random.random() < 0.1 # 10% chance of forwarding to HR
# Generate a sample transcript
transcript = (
"User: Hello, I need help with my account.\n"
"Agent: Hello! I'd be happy to help. What seems to be the issue?\n"
"User: I can't log in to my account.\n"
"Agent: I understand. Let me help you reset your password."
)
# Random response time, tokens, and cost
avg_response_time = round(random.uniform(0.5, 10.0), 2)
tokens = random.randint(100, 2000)
tokens_eur = round(tokens * 0.00002, 4) # Example rate: €0.00002 per token
# Random category
category = random.choice(categories)
# Initial message
initial_msg = "Hello, I need help with my account."
# Random rating
user_rating = random.choice(ratings)
# Create row
row = {
"session_id": f"{company_name.lower().replace(' ', '_')}_{i + 1}",
"start_time": start_date.strftime("%Y-%m-%d %H:%M:%S"),
"end_time": end_date.strftime("%Y-%m-%d %H:%M:%S"),
"ip_address": ip,
"country": country,
"language": language,
"messages_sent": str(messages_sent),
"sentiment": sentiment,
"escalated": str(escalated),
"forwarded_hr": str(forwarded_hr),
"full_transcript": transcript,
"avg_response_time": str(avg_response_time),
"tokens": str(tokens),
"tokens_eur": str(tokens_eur),
"category": category,
"initial_msg": initial_msg,
"user_rating": user_rating,
}
rows.append(row)
# Write to CSV string
output = io.StringIO()
writer = csv.DictWriter(output, fieldnames=headers)
writer.writeheader()
writer.writerows(rows)
return output.getvalue()

View File

@ -0,0 +1 @@
# This file is intentionally left empty to mark the directory as a Python package

View File

@ -0,0 +1,57 @@
# dashboard/models.py
from accounts.models import Company
from django.db import models
class DataSource(models.Model):
"""Model for uploaded data sources (CSV files)"""
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
file = models.FileField(upload_to="data_sources/")
uploaded_at = models.DateTimeField(auto_now_add=True)
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="data_sources")
def __str__(self):
return self.name
class ChatSession(models.Model):
"""Model to store parsed chat session data from CSV"""
data_source = models.ForeignKey(DataSource, on_delete=models.CASCADE, related_name="chat_sessions")
session_id = models.CharField(max_length=255)
start_time = models.DateTimeField(null=True, blank=True)
end_time = models.DateTimeField(null=True, blank=True)
ip_address = models.GenericIPAddressField(null=True, blank=True)
country = models.CharField(max_length=100, blank=True)
language = models.CharField(max_length=50, blank=True)
messages_sent = models.IntegerField(default=0)
sentiment = models.CharField(max_length=50, blank=True)
escalated = models.BooleanField(default=False)
forwarded_hr = models.BooleanField(default=False)
full_transcript = models.TextField(blank=True)
avg_response_time = models.FloatField(null=True, blank=True)
tokens = models.IntegerField(default=0)
tokens_eur = models.FloatField(null=True, blank=True)
category = models.CharField(max_length=100, blank=True)
initial_msg = models.TextField(blank=True)
user_rating = models.CharField(max_length=50, blank=True)
def __str__(self):
return f"Session {self.session_id}"
class Dashboard(models.Model):
"""Model for custom dashboards that can be created by users"""
name = models.CharField(max_length=255)
description = models.TextField(blank=True)
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="dashboards")
data_sources = models.ManyToManyField(DataSource, related_name="dashboards")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name

View File

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

View File

@ -0,0 +1,56 @@
# dashboard/templatetags/dashboard_extras.py
from django import template
register = template.Library()
@register.filter
def split(value, delimiter):
"""Split a string into a list based on the delimiter"""
return value.split(delimiter)
@register.filter
def get_item(dictionary, key):
"""Get an item from a dictionary using the key"""
return dictionary.get(key)
@register.filter
def truncate_middle(value, max_length):
"""Truncate a string in the middle, keeping the beginning and end"""
if len(value) <= max_length:
return value
# Calculate how many characters to keep at the start and end
half_max = max_length // 2
start = value[:half_max]
end = value[-half_max:]
return f"{start}...{end}"
@register.filter
def format_duration(seconds):
"""Format seconds into a human-readable duration"""
if not seconds:
return "0s"
minutes, seconds = divmod(int(seconds), 60)
hours, minutes = divmod(minutes, 60)
if hours > 0:
return f"{hours}h {minutes}m {seconds}s"
elif minutes > 0:
return f"{minutes}m {seconds}s"
else:
return f"{seconds}s"
@register.simple_tag
def url_replace(request, field, value):
"""Replace a GET parameter in the current URL"""
dict_ = request.GET.copy()
dict_[field] = value
return dict_.urlencode()

View File

View File

@ -0,0 +1,43 @@
# dashboard/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.dashboard_view, name="dashboard"),
path("upload/", views.upload_data_view, name="upload_data"),
path(
"data-source/<int:data_source_id>/",
views.data_source_detail_view,
name="data_source_detail",
),
path(
"chat-session/<str:session_id>/",
views.chat_session_detail_view,
name="chat_session_detail",
),
path("dashboard/create/", views.create_dashboard_view, name="create_dashboard"),
path(
"dashboard/<int:dashboard_id>/edit/",
views.edit_dashboard_view,
name="edit_dashboard",
),
path(
"dashboard/<int:dashboard_id>/delete/",
views.delete_dashboard_view,
name="delete_dashboard",
),
path(
"data-source/<int:data_source_id>/delete/",
views.delete_data_source_view,
name="delete_data_source",
),
path(
"api/dashboard/<int:dashboard_id>/data/",
views.dashboard_data_api,
name="dashboard_data_api",
),
path("search/", views.search_chat_sessions, name="search_chat_sessions"),
path("data-view/", views.data_view, name="data_view"),
]

View File

@ -0,0 +1,161 @@
# dashboard/utils.py
import numpy as np
import pandas as pd
from django.db import models
from django.utils.timezone import make_aware
from .models import ChatSession
def process_csv_file(data_source):
"""
Process the uploaded CSV file and create ChatSession objects
Args:
data_source: DataSource model instance containing the CSV file
"""
try:
# Read the CSV file
file_path = data_source.file.path
df = pd.read_csv(file_path)
# Process each row and create ChatSession objects
for _, row in df.iterrows():
# Handle datetime fields
start_time = None
end_time = None
if "start_time" in row and pd.notna(row["start_time"]):
try:
start_time = make_aware(pd.to_datetime(row["start_time"]))
except Exception:
pass
if "end_time" in row and pd.notna(row["end_time"]):
try:
end_time = make_aware(pd.to_datetime(row["end_time"]))
except Exception:
pass
# Convert boolean fields
escalated = str(row.get("escalated", "")).lower() in [
"true",
"yes",
"1",
"t",
"y",
]
forwarded_hr = str(row.get("forwarded_hr", "")).lower() in [
"true",
"yes",
"1",
"t",
"y",
]
# Create ChatSession object
session = ChatSession(
data_source=data_source,
session_id=str(row.get("session_id", "")),
start_time=start_time,
end_time=end_time,
ip_address=row.get("ip_address") if pd.notna(row.get("ip_address", np.nan)) else None,
country=str(row.get("country", "")),
language=str(row.get("language", "")),
messages_sent=int(row.get("messages_sent", 0)) if pd.notna(row.get("messages_sent", np.nan)) else 0,
sentiment=str(row.get("sentiment", "")),
escalated=escalated,
forwarded_hr=forwarded_hr,
full_transcript=str(row.get("full_transcript", "")),
avg_response_time=float(row.get("avg_response_time", 0))
if pd.notna(row.get("avg_response_time", np.nan))
else None,
tokens=int(row.get("tokens", 0)) if pd.notna(row.get("tokens", np.nan)) else 0,
tokens_eur=float(row.get("tokens_eur", 0)) if pd.notna(row.get("tokens_eur", np.nan)) else None,
category=str(row.get("category", "")),
initial_msg=str(row.get("initial_msg", "")),
user_rating=str(row.get("user_rating", "")),
)
session.save()
return True, f"Successfully processed {len(df)} records."
except Exception as e:
return False, f"Error processing CSV file: {str(e)}"
def generate_dashboard_data(data_sources):
"""
Generate aggregated data for dashboard visualization
Args:
data_sources: QuerySet of DataSource objects
Returns:
dict: Dictionary containing aggregated data for various charts
"""
# Get all chat sessions for the selected data sources
chat_sessions = ChatSession.objects.filter(data_source__in=data_sources)
if not chat_sessions.exists():
return {
"total_sessions": 0,
"avg_response_time": 0,
"total_tokens": 0,
"total_cost": 0,
"sentiment_data": [],
"country_data": [],
"category_data": [],
"time_series_data": [],
}
# Basic statistics
total_sessions = chat_sessions.count()
avg_response_time = (
chat_sessions.filter(avg_response_time__isnull=False).aggregate(avg=models.Avg("avg_response_time"))["avg"] or 0
)
total_tokens = chat_sessions.aggregate(sum=models.Sum("tokens"))["sum"] or 0
total_cost = chat_sessions.filter(tokens_eur__isnull=False).aggregate(sum=models.Sum("tokens_eur"))["sum"] or 0
# Sentiment distribution
sentiment_data = (
chat_sessions.exclude(sentiment="").values("sentiment").annotate(count=models.Count("id")).order_by("-count")
)
# Country distribution
country_data = (
chat_sessions.exclude(country="")
.values("country")
.annotate(count=models.Count("id"))
.order_by("-count")[:10] # Top 10 countries
)
# Category distribution
category_data = (
chat_sessions.exclude(category="").values("category").annotate(count=models.Count("id")).order_by("-count")
)
# Time series data (sessions per day)
time_series_query = (
chat_sessions.filter(start_time__isnull=False)
.annotate(date=models.functions.TruncDate("start_time"))
.values("date")
.annotate(count=models.Count("id"))
.order_by("date")
)
time_series_data = [
{"date": entry["date"].strftime("%Y-%m-%d"), "count": entry["count"]} for entry in time_series_query
]
return {
"total_sessions": total_sessions,
"avg_response_time": round(avg_response_time, 2),
"total_tokens": total_tokens,
"total_cost": round(total_cost, 2),
"sentiment_data": list(sentiment_data),
"country_data": list(country_data),
"category_data": list(category_data),
"time_series_data": time_series_data,
}

View File

@ -0,0 +1,452 @@
# dashboard/views.py
import json
from datetime import timedelta
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Avg, Q
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from .forms import DashboardForm, DataSourceUploadForm
from .models import ChatSession, Dashboard, DataSource
from .utils import generate_dashboard_data, process_csv_file
@login_required
def dashboard_view(request):
"""Main dashboard view"""
user = request.user
company = user.company
if not company:
messages.warning(
request,
"You are not associated with any company. Please contact an administrator.",
)
return render(request, "dashboard/no_company.html")
# Get the user's dashboards or create a default one
dashboards = Dashboard.objects.filter(company=company)
if not dashboards.exists():
# Create a default dashboard if none exists
data_sources = DataSource.objects.filter(company=company)
if data_sources.exists():
default_dashboard = Dashboard.objects.create(
name="Default Dashboard",
description="Automatically created dashboard",
company=company,
)
default_dashboard.data_sources.set(data_sources)
dashboards = [default_dashboard]
else:
# No data sources available
return redirect("upload_data")
# Use the first dashboard by default or the one specified in the request
selected_dashboard_id = request.GET.get("dashboard_id")
if selected_dashboard_id:
selected_dashboard = get_object_or_404(Dashboard, id=selected_dashboard_id, company=company)
else:
selected_dashboard = dashboards.first()
# Generate dashboard data
dashboard_data = generate_dashboard_data(selected_dashboard.data_sources.all())
# Convert dashboard data to JSON for use in JavaScript
dashboard_data_json = json.dumps(
{
"sentiment_data": dashboard_data["sentiment_data"],
"country_data": dashboard_data["country_data"],
"category_data": dashboard_data["category_data"],
"time_series_data": dashboard_data["time_series_data"],
}
)
context = {
"dashboards": dashboards,
"selected_dashboard": selected_dashboard,
"dashboard_data": dashboard_data,
"dashboard_data_json": dashboard_data_json,
}
return render(request, "dashboard/dashboard.html", context)
@login_required
def upload_data_view(request):
"""View for uploading CSV files"""
user = request.user
company = user.company
if not company:
messages.warning(
request,
"You are not associated with any company. Please contact an administrator.",
)
return redirect("dashboard")
if request.method == "POST":
form = DataSourceUploadForm(request.POST, request.FILES, company=company)
if form.is_valid():
data_source = form.save()
# Process the uploaded CSV file
success, message = process_csv_file(data_source)
if success:
messages.success(request, f"File uploaded successfully. {message}")
# Add the new data source to all existing dashboards
dashboards = Dashboard.objects.filter(company=company)
for dashboard in dashboards:
dashboard.data_sources.add(data_source)
return redirect("dashboard")
else:
# If processing failed, delete the data source
data_source.delete()
messages.error(request, message)
else:
messages.error(request, "Form is invalid. Please correct the errors.")
else:
form = DataSourceUploadForm()
# List existing data sources
data_sources = DataSource.objects.filter(company=company).order_by("-uploaded_at")
context = {
"form": form,
"data_sources": data_sources,
}
return render(request, "dashboard/upload.html", context)
@login_required
def data_source_detail_view(request, data_source_id):
"""View for viewing details of a data source"""
user = request.user
company = user.company
if not company:
messages.warning(
request,
"You are not associated with any company. Please contact an administrator.",
)
return redirect("dashboard")
data_source = get_object_or_404(DataSource, id=data_source_id, company=company)
# Get all chat sessions for this data source
chat_sessions = ChatSession.objects.filter(data_source=data_source).order_by("-start_time")
# Pagination
paginator = Paginator(chat_sessions, 20) # Show 20 records per page
page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number)
context = {
"data_source": data_source,
"page_obj": page_obj,
}
return render(request, "dashboard/data_source_detail.html", context)
@login_required
def chat_session_detail_view(request, session_id):
"""View for viewing details of a chat session"""
user = request.user
company = user.company
if not company:
messages.warning(
request,
"You are not associated with any company. Please contact an administrator.",
)
return redirect("dashboard")
chat_session = get_object_or_404(ChatSession, session_id=session_id, data_source__company=company)
context = {
"session": chat_session,
}
return render(request, "dashboard/chat_session_detail.html", context)
@login_required
def create_dashboard_view(request):
"""View for creating a custom dashboard"""
user = request.user
company = user.company
if not company:
messages.warning(
request,
"You are not associated with any company. Please contact an administrator.",
)
return redirect("dashboard")
if request.method == "POST":
form = DashboardForm(request.POST, company=company)
if form.is_valid():
dashboard = form.save()
messages.success(request, f"Dashboard '{dashboard.name}' created successfully.")
return redirect("dashboard")
else:
messages.error(request, "Failed to create dashboard. Please correct the errors.")
else:
form = DashboardForm(company=company)
context = {
"form": form,
"is_create": True,
}
return render(request, "dashboard/dashboard_form.html", context)
@login_required
def edit_dashboard_view(request, dashboard_id):
"""View for editing a dashboard"""
user = request.user
company = user.company
if not company:
messages.warning(
request,
"You are not associated with any company. Please contact an administrator.",
)
return redirect("dashboard")
dashboard = get_object_or_404(Dashboard, id=dashboard_id, company=company)
if request.method == "POST":
form = DashboardForm(request.POST, instance=dashboard, company=company)
if form.is_valid():
dashboard = form.save()
messages.success(request, f"Dashboard '{dashboard.name}' updated successfully.")
return redirect("dashboard")
else:
messages.error(request, "Failed to update dashboard. Please correct the errors.")
else:
form = DashboardForm(instance=dashboard, company=company)
context = {
"form": form,
"dashboard": dashboard,
"is_create": False,
}
return render(request, "dashboard/dashboard_form.html", context)
@login_required
def delete_dashboard_view(request, dashboard_id):
"""View for deleting a dashboard"""
user = request.user
company = user.company
if not company:
messages.warning(
request,
"You are not associated with any company. Please contact an administrator.",
)
return redirect("dashboard")
dashboard = get_object_or_404(Dashboard, id=dashboard_id, company=company)
if request.method == "POST":
dashboard_name = dashboard.name
dashboard.delete()
messages.success(request, f"Dashboard '{dashboard_name}' deleted successfully.")
return redirect("dashboard")
context = {
"dashboard": dashboard,
}
return render(request, "dashboard/dashboard_confirm_delete.html", context)
@login_required
def delete_data_source_view(request, data_source_id):
"""View for deleting a data source"""
user = request.user
company = user.company
if not company:
messages.warning(
request,
"You are not associated with any company. Please contact an administrator.",
)
return redirect("dashboard")
data_source = get_object_or_404(DataSource, id=data_source_id, company=company)
if request.method == "POST":
data_source_name = data_source.name
data_source.delete()
messages.success(request, f"Data source '{data_source_name}' deleted successfully.")
return redirect("upload_data")
context = {
"data_source": data_source,
}
return render(request, "dashboard/data_source_confirm_delete.html", context)
# API views for dashboard data
@login_required
def dashboard_data_api(request, dashboard_id):
"""API endpoint for dashboard data"""
user = request.user
company = user.company
if not company:
return JsonResponse({"error": "User not associated with a company"}, status=403)
dashboard = get_object_or_404(Dashboard, id=dashboard_id, company=company)
dashboard_data = generate_dashboard_data(dashboard.data_sources.all())
return JsonResponse(dashboard_data)
@login_required
def search_chat_sessions(request):
"""View for searching chat sessions"""
user = request.user
company = user.company
if not company:
messages.warning(
request,
"You are not associated with any company. Please contact an administrator.",
)
return redirect("dashboard")
query = request.GET.get("q", "")
data_source_id = request.GET.get("data_source_id")
# Base queryset
chat_sessions = ChatSession.objects.filter(data_source__company=company)
# Filter by data source if provided
if data_source_id:
chat_sessions = chat_sessions.filter(data_source_id=data_source_id)
# Apply search query if provided
if query:
chat_sessions = chat_sessions.filter(
Q(session_id__icontains=query)
| Q(country__icontains=query)
| Q(language__icontains=query)
| Q(sentiment__icontains=query)
| Q(category__icontains=query)
| Q(initial_msg__icontains=query)
| Q(full_transcript__icontains=query)
)
# Order by most recent first
chat_sessions = chat_sessions.order_by("-start_time")
# Pagination
paginator = Paginator(chat_sessions, 20) # Show 20 records per page
page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number)
# Get data source for context if filtered by data source
data_source = None
if data_source_id:
data_source = get_object_or_404(DataSource, id=data_source_id, company=company)
context = {
"query": query,
"page_obj": page_obj,
"data_source": data_source,
}
return render(request, "dashboard/search_results.html", context)
@login_required
def data_view(request):
"""View for viewing all data with filtering options"""
user = request.user
company = user.company
if not company:
messages.warning(
request,
"You are not associated with any company. Please contact an administrator.",
)
return redirect("dashboard")
# Get available data sources
data_sources = DataSource.objects.filter(company=company)
# Get selected data source if any
data_source_id = request.GET.get("data_source_id")
selected_data_source = None
if data_source_id:
selected_data_source = get_object_or_404(DataSource, id=data_source_id, company=company)
# Base queryset
chat_sessions = ChatSession.objects.filter(data_source__company=company)
# Apply data source filter if selected
if selected_data_source:
chat_sessions = chat_sessions.filter(data_source=selected_data_source)
# Apply view filter if any
view = request.GET.get("view", "all")
if view == "recent":
# Sessions from the last 7 days
seven_days_ago = timezone.now() - timedelta(days=7)
chat_sessions = chat_sessions.filter(start_time__gte=seven_days_ago)
elif view == "positive":
# Sessions with positive sentiment
chat_sessions = chat_sessions.filter(Q(sentiment__icontains="positive"))
elif view == "negative":
# Sessions with negative sentiment
chat_sessions = chat_sessions.filter(Q(sentiment__icontains="negative"))
elif view == "escalated":
# Escalated sessions
chat_sessions = chat_sessions.filter(escalated=True)
# Order by most recent first
chat_sessions = chat_sessions.order_by("-start_time")
# Calculate some statistics
total_sessions = chat_sessions.count()
avg_response_time = (
chat_sessions.filter(avg_response_time__isnull=False).aggregate(avg=Avg("avg_response_time"))["avg"] or 0
)
avg_messages = chat_sessions.filter(messages_sent__gt=0).aggregate(avg=Avg("messages_sent"))["avg"] or 0
escalated_count = chat_sessions.filter(escalated=True).count()
escalation_rate = (escalated_count / total_sessions * 100) if total_sessions > 0 else 0
# Pagination
paginator = Paginator(chat_sessions, 20) # Show 20 records per page
page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number)
context = {
"data_sources": data_sources,
"selected_data_source": selected_data_source,
"page_obj": page_obj,
"view": view,
"avg_response_time": avg_response_time,
"avg_messages": avg_messages,
"escalation_rate": escalation_rate,
}
return render(request, "dashboard/data_view.html", context)