Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 72 additions & 14 deletions django-kpi/django_kpi/admin.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,88 @@
from django.contrib import admin
from .models import KPI, KpiCard
from .models import KPI, KpiCard, ComponentPosition, KpiComponent
from .forms import KPIAdminForm, CardAdminForm

admin.site.index_template = "kpi/kpi_dashboards.html"


@admin.register(KPI)
class KPIAdmin(admin.ModelAdmin):
form = KPIAdminForm
list_display = ('name', 'model_field')
list_filter = ('model_field',)
search_fields = ('name',)
list_display = ("name", "model_field")
list_filter = ("model_field",)
search_fields = ("name",)

# @admin.register(ComponentPosition)
# class ComponentPositionAdmin(admin.ModelAdmin):
# list_display = ('component', 'x', 'y', 'w', 'h')
# list_filter = ('component',)
# search_fields = ('component__name',)


@admin.register(KpiCard)
class CardAdmin(admin.ModelAdmin):
form = CardAdminForm
list_display = ['svg_icon', 'name', 'kpi', 'operation', 'target_field', 'condition', 'target_value', 'result']
list_filter = ['kpi', 'operation', 'condition']
search_fields = ['name', 'kpi__name', 'description']
list_display = [
"svg_icon",
"name",
"kpi",
"operation",
"target_field",
"condition",
"target_value",
"result",
"published",
]
list_filter = ["kpi", "operation", "condition", "published"]
search_fields = ["name", "kpi__name", "description"]
actions = [
"publish_cards",
"unpublish_cards",
"reset_positions",
"duplicate_cards",
]
fieldsets = (
(None, {'fields': ('kpi', 'name', 'description', 'icon')}),
('Value Settings', {'fields': ('value_suffix', 'operation')}),
('Target Settings', {'fields': ('target_type', 'target_field', 'condition', 'target_value')}),
(
None,
{"fields": ("kpi", "name", "description", "icon", "published")},
),
("Value Settings", {"fields": ("value_suffix", "operation")}),
(
"Target Settings",
{
"fields": (
"target_type",
"target_field",
"condition",
"target_value",
)
},
),
)

def result(self, instance: KpiCard):
return instance.value


def publish_cards(self, request, queryset):
queryset.update(published=True)

publish_cards.short_description = "Publish selected cards"

def unpublish_cards(self, request, queryset):
queryset.update(published=False)

unpublish_cards.short_description = "Unpublish selected cards"

def reset_positions(self, request, queryset):
for card in queryset:
position = card.position
position.x = 0
position.y = 0
position.w = 2
position.h = 1
position.save()

reset_positions.short_description = "Reset card positions"

class Media:
js = (
'js/kpi_admin.js',
)
js = ("js/kpi_admin.js",)
93 changes: 61 additions & 32 deletions django-kpi/django_kpi/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,88 +3,117 @@

from .utils import KPIService
from .models import KPI, KpiCard

# from core.widgets import IconPicker
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _


class KPIAdminForm(forms.ModelForm):
"""
Custom form for KPIAdmin
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Set choices dynamically in form initialization
self.fields['model_field'] = forms.ChoiceField(
choices=[('', '--- Select Model ---')] + KPIService.get_available_models(),
self.fields["model_field"] = forms.ChoiceField(
choices=[("", "--- Select Model ---")] + KPIService.get_available_models(),
required=True,
widget=forms.Select
widget=forms.Select,
)

class Meta:
model = KPI
fields = '__all__'
fields = "__all__"


class CardAdminForm(forms.ModelForm):
"""
Custom form for CardAdmin
"""

# icon = forms.CharField(widget=IconPicker())

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['target_type'].widget = forms.HiddenInput()
self.fields['target_field'] = forms.CharField(
self.fields["target_type"].widget = forms.HiddenInput()
self.fields["target_field"] = forms.CharField(
required=True,
widget=forms.Select(choices=[(self.instance.target_field, self.instance.target_field)],),
widget=forms.Select(
choices=[(self.instance.target_field, self.instance.target_field)],
),
)
self.fields['target_value'] = forms.CharField(
self.fields["target_value"] = forms.CharField(
required=False,
widget=forms.Select(choices=[(self.instance.target_value, self.instance.target_value)],),
widget=forms.Select(
choices=[(self.instance.target_value, self.instance.target_value)],
),
)
self.fields["icon"].widget.attrs["model"] = self._meta.model.__name__
if self.instance and self.instance.pk:
object_id = self.instance.pk
self.fields["icon"].widget.attrs["objectid"] = object_id
else:
last_item_id = (
KpiCard.objects.last().id if KpiCard.objects.exists() else 1
)
last_item_id = KpiCard.objects.last().id if KpiCard.objects.exists() else 1
next_id = last_item_id + 1
self.fields["icon"].widget.attrs["objectid"] = next_id

def clean(self):
cleaned_data = super().clean()
target_type = cleaned_data.get('target_type')
target_value = cleaned_data.get('target_value')
operation = cleaned_data.get('operation')
condition = cleaned_data.get('condition')
target_type = cleaned_data.get("target_type")
target_value = cleaned_data.get("target_value")
operation = cleaned_data.get("operation")
condition = cleaned_data.get("condition")

# Validate that target_value is present if condition is not NONE
if condition != "NONE" and not target_value:
raise ValidationError(_("Target value is required when a condition is selected."))
raise ValidationError(
_("Target value is required when a condition is selected.")
)

# Validate operation against target_type
if target_type and operation:
is_numeric_field = issubclass(getattr(models, target_type), (models.IntegerField, models.FloatField, models.DecimalField))
if not is_numeric_field and operation in ['sum', 'avg', 'max', 'min']:
raise ValidationError(_(
f"Invalid operation '{operation}' for field type '{target_type}'. "
f"Choose 'count' or 'count_distinct' for non-numeric fields."
))

# Validate condition against target_type
if target_type and condition.lower() in ['gt', 'gte', 'lt', 'lte', 'eq', 'between']:
is_numeric_field = issubclass(
getattr(models, target_type),
(models.IntegerField, models.FloatField, models.DecimalField),
)
if not is_numeric_field and operation in ["sum", "avg", "max", "min"]:
raise ValidationError(
_(
f"Invalid operation '{operation}' for field type '{target_type}'. "
f"Choose 'count' or 'count_distinct' for non-numeric fields."
)
)

# Validate condition against target_type
if target_type and condition.lower() in [
"gt",
"gte",
"lt",
"lte",
"eq",
"between",
]:
is_numeric_or_datetime_field = issubclass(
getattr(models, target_type),
(models.IntegerField, models.FloatField, models.DecimalField, models.DateTimeField)
getattr(models, target_type),
(
models.IntegerField,
models.FloatField,
models.DecimalField,
models.DateTimeField,
),
)
if not is_numeric_or_datetime_field:
raise ValidationError(_(f"Comparison operations are not supported for '{target_type}' fields."))
raise ValidationError(
_(
f"Comparison operations are not supported for '{target_type}' fields."
)
)

return cleaned_data


class Meta:
model = KpiCard
fields = '__all__'
fields = "__all__"
Loading