399 lines
15 KiB
Python
399 lines
15 KiB
Python
"""
|
|
Analytics app forms for CRUD operations.
|
|
"""
|
|
|
|
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils import timezone
|
|
from datetime import datetime, date
|
|
|
|
from .models import (
|
|
Dashboard, DashboardWidget, DataSource, Report,
|
|
MetricDefinition
|
|
)
|
|
|
|
|
|
class DashboardForm(forms.ModelForm):
|
|
"""
|
|
Form for creating and updating dashboards.
|
|
"""
|
|
|
|
class Meta:
|
|
model = Dashboard
|
|
fields = [
|
|
'name', 'description', 'dashboard_type', 'is_public',
|
|
'layout_config', 'refresh_interval', 'is_active'
|
|
]
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Enter dashboard name'
|
|
}),
|
|
'description': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Enter dashboard description'
|
|
}),
|
|
'dashboard_type': forms.Select(attrs={'class': 'form-control'}),
|
|
'is_public': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
|
'layout_config': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 5,
|
|
'placeholder': 'Enter JSON layout configuration'
|
|
}),
|
|
'refresh_interval': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 30,
|
|
'max': 3600,
|
|
'step': 30
|
|
}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
}
|
|
help_texts = {
|
|
'name': 'Unique name for this dashboard',
|
|
'dashboard_type': 'Dashboard type for organization',
|
|
'is_public': 'Whether this dashboard is publicly accessible',
|
|
'layout_config': 'JSON configuration for dashboard layout',
|
|
'refresh_interval': 'Auto-refresh interval in seconds (30-3600)',
|
|
}
|
|
|
|
def clean_name(self):
|
|
name = self.cleaned_data['name']
|
|
if len(name) < 3:
|
|
raise ValidationError('Dashboard name must be at least 3 characters long.')
|
|
return name
|
|
|
|
def clean_refresh_interval(self):
|
|
interval = self.cleaned_data['refresh_interval']
|
|
if interval and (interval < 30 or interval > 3600):
|
|
raise ValidationError('Refresh interval must be between 30 and 3600 seconds.')
|
|
return interval
|
|
|
|
|
|
class DashboardWidgetForm(forms.ModelForm):
|
|
"""
|
|
Form for creating and updating dashboard widgets.
|
|
"""
|
|
|
|
class Meta:
|
|
model = DashboardWidget
|
|
fields = [
|
|
'dashboard', 'name', 'widget_type', 'position_x', 'position_y',
|
|
'width', 'height', 'display_config', 'query_config', 'is_active'
|
|
]
|
|
widgets = {
|
|
'dashboard': forms.Select(attrs={'class': 'form-control'}),
|
|
'name': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Enter widget name'
|
|
}),
|
|
'widget_type': forms.Select(attrs={'class': 'form-control'}),
|
|
'position_x': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 0,
|
|
'max': 12
|
|
}),
|
|
'position_y': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 0
|
|
}),
|
|
'width': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 1,
|
|
'max': 12
|
|
}),
|
|
'height': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 1,
|
|
'max': 20
|
|
}),
|
|
'display_config': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 4,
|
|
'placeholder': 'Enter JSON display configuration'
|
|
}),
|
|
'query_config': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Enter query configuration'
|
|
}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
}
|
|
help_texts = {
|
|
'widget_type': 'Type of widget (chart, table, KPI, etc.)',
|
|
'position_x': 'Horizontal position (0-12 grid system)',
|
|
'position_y': 'Vertical position',
|
|
'width': 'Widget width (1-12 grid columns)',
|
|
'height': 'Widget height in grid rows',
|
|
'display_config': 'JSON configuration for widget display',
|
|
'query_config': 'Query configuration for widget content',
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
user = kwargs.pop('user', None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
if user:
|
|
self.fields['dashboard'].queryset = Dashboard.objects.filter(
|
|
tenant=user.tenant
|
|
).order_by('name')
|
|
|
|
def clean_position_x(self):
|
|
x = self.cleaned_data['position_x']
|
|
if x < 0 or x > 12:
|
|
raise ValidationError('Position X must be between 0 and 12.')
|
|
return x
|
|
|
|
def clean_width(self):
|
|
width = self.cleaned_data['width']
|
|
if width < 1 or width > 12:
|
|
raise ValidationError('Width must be between 1 and 12.')
|
|
return width
|
|
|
|
|
|
class DataSourceForm(forms.ModelForm):
|
|
"""
|
|
Form for creating and updating data sources.
|
|
"""
|
|
|
|
class Meta:
|
|
model = DataSource
|
|
fields = [
|
|
'name', 'description', 'source_type', 'connection_config',
|
|
'authentication_config', 'query_template', 'cache_duration',
|
|
'is_active'
|
|
]
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Enter data source name'
|
|
}),
|
|
'description': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Enter data source description'
|
|
}),
|
|
'source_type': forms.Select(attrs={'class': 'form-control'}),
|
|
'connection_config': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 4,
|
|
'placeholder': 'Enter JSON connection configuration'
|
|
}),
|
|
'authentication_config': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Enter JSON authentication configuration'
|
|
}),
|
|
'query_template': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 4,
|
|
'placeholder': 'Enter query template'
|
|
}),
|
|
'cache_duration': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'min': 0,
|
|
'max': 86400,
|
|
'value': 300
|
|
}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
}
|
|
help_texts = {
|
|
'name': 'Unique name for this data source',
|
|
'source_type': 'Type of data source (database, API, file, etc.)',
|
|
'connection_config': 'JSON configuration for connection settings',
|
|
'authentication_config': 'JSON configuration for authentication',
|
|
'cache_duration': 'Cache duration in seconds (0-86400)',
|
|
}
|
|
|
|
def clean_name(self):
|
|
name = self.cleaned_data['name']
|
|
if len(name) < 3:
|
|
raise ValidationError('Data source name must be at least 3 characters long.')
|
|
return name
|
|
|
|
def clean_cache_duration(self):
|
|
cache_duration = self.cleaned_data['cache_duration']
|
|
if cache_duration < 0 or cache_duration > 86400:
|
|
raise ValidationError('Cache duration must be between 0 and 86400 seconds.')
|
|
return cache_duration
|
|
|
|
|
|
class ReportForm(forms.ModelForm):
|
|
"""
|
|
Form for creating and updating reports.
|
|
"""
|
|
|
|
class Meta:
|
|
model = Report
|
|
fields = [
|
|
'name', 'description', 'report_type', 'data_source',
|
|
'query_config', 'output_format', 'template_config',
|
|
'schedule_config', 'is_active'
|
|
]
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Enter report name'
|
|
}),
|
|
'description': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Enter report description'
|
|
}),
|
|
'report_type': forms.Select(attrs={'class': 'form-control'}),
|
|
'data_source': forms.Select(attrs={'class': 'form-control'}),
|
|
'query_config': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 8,
|
|
'placeholder': 'Enter query configuration'
|
|
}),
|
|
'template_config': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 4,
|
|
'placeholder': 'Enter JSON template configuration'
|
|
}),
|
|
'output_format': forms.Select(attrs={'class': 'form-control'}),
|
|
'schedule_config': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Enter JSON schedule configuration'
|
|
}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
}
|
|
help_texts = {
|
|
'name': 'Unique name for this report',
|
|
'report_type': 'Report type for organization',
|
|
'query_config': 'Query configuration for report data',
|
|
'template_config': 'Template configuration for report formatting',
|
|
'output_format': 'Default output format for report',
|
|
'schedule_config': 'JSON configuration for automated scheduling',
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
user = kwargs.pop('user', None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
if user:
|
|
self.fields['data_source'].queryset = DataSource.objects.filter(
|
|
tenant=user.tenant,
|
|
is_active=True
|
|
).order_by('name')
|
|
|
|
def clean_report_name(self):
|
|
name = self.cleaned_data['report_name']
|
|
if len(name) < 3:
|
|
raise ValidationError('Report name must be at least 3 characters long.')
|
|
return name
|
|
|
|
def clean_query_definition(self):
|
|
query = self.cleaned_data['query_definition']
|
|
if len(query.strip()) < 10:
|
|
raise ValidationError('Query definition must be at least 10 characters long.')
|
|
return query
|
|
|
|
|
|
class MetricDefinitionForm(forms.ModelForm):
|
|
"""
|
|
Form for creating and updating metric definitions.
|
|
"""
|
|
|
|
class Meta:
|
|
model = MetricDefinition
|
|
fields = [
|
|
'name', 'description', 'metric_type', 'data_source',
|
|
'calculation_config', 'aggregation_period', 'unit_of_measure',
|
|
'target_value', 'warning_threshold', 'critical_threshold',
|
|
'aggregation_config', 'is_active'
|
|
]
|
|
widgets = {
|
|
'name': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'Enter metric name'
|
|
}),
|
|
'description': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Enter metric description'
|
|
}),
|
|
'metric_type': forms.Select(attrs={'class': 'form-control'}),
|
|
'data_source': forms.Select(attrs={'class': 'form-control'}),
|
|
'calculation_config': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 6,
|
|
'placeholder': 'Enter calculation configuration'
|
|
}),
|
|
'aggregation_period': forms.Select(attrs={'class': 'form-control'}),
|
|
'unit_of_measure': forms.TextInput(attrs={
|
|
'class': 'form-control',
|
|
'placeholder': 'e.g., %, count, minutes, dollars'
|
|
}),
|
|
'target_value': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'step': 'any',
|
|
'placeholder': 'Target value for this metric'
|
|
}),
|
|
'warning_threshold': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'step': 'any',
|
|
'placeholder': 'Warning threshold value'
|
|
}),
|
|
'critical_threshold': forms.NumberInput(attrs={
|
|
'class': 'form-control',
|
|
'step': 'any',
|
|
'placeholder': 'Critical threshold value'
|
|
}),
|
|
'aggregation_config': forms.Textarea(attrs={
|
|
'class': 'form-control',
|
|
'rows': 3,
|
|
'placeholder': 'Enter aggregation configuration'
|
|
}),
|
|
'is_active': forms.CheckboxInput(attrs={'class': 'form-check-input'})
|
|
}
|
|
help_texts = {
|
|
'name': 'Unique name for this metric',
|
|
'metric_type': 'Metric type for organization',
|
|
'calculation_config': 'Configuration for metric calculation',
|
|
'aggregation_period': 'Period for metric aggregation',
|
|
'unit_of_measure': 'Unit of measurement for metric values',
|
|
'target_value': 'Target or goal value for this metric',
|
|
'warning_threshold': 'Value that triggers warning alerts',
|
|
'critical_threshold': 'Value that triggers critical alerts',
|
|
'aggregation_config': 'Configuration for metric aggregation',
|
|
}
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
user = kwargs.pop('user', None)
|
|
super().__init__(*args, **kwargs)
|
|
|
|
if user:
|
|
self.fields['data_source'].queryset = DataSource.objects.filter(
|
|
tenant=user.tenant,
|
|
is_active=True
|
|
).order_by('source_name')
|
|
|
|
def clean_metric_name(self):
|
|
name = self.cleaned_data['metric_name']
|
|
if len(name) < 3:
|
|
raise ValidationError('Metric name must be at least 3 characters long.')
|
|
return name
|
|
|
|
def clean_query_definition(self):
|
|
query = self.cleaned_data['query_definition']
|
|
if len(query.strip()) < 5:
|
|
raise ValidationError('Query definition must be at least 5 characters long.')
|
|
return query
|
|
|
|
def clean(self):
|
|
cleaned_data = super().clean()
|
|
target = cleaned_data.get('target_value')
|
|
warning = cleaned_data.get('threshold_warning')
|
|
critical = cleaned_data.get('threshold_critical')
|
|
|
|
# Validate threshold relationships
|
|
if target and warning and critical:
|
|
if warning == critical:
|
|
raise ValidationError('Warning and critical thresholds must be different.')
|
|
|
|
return cleaned_data
|
|
|