+
>
-
+
@@ -65,9 +60,10 @@ This code above will be actually rendered as this in the html page:
-The ``data-report-widget`` attribute is used by the javascript to find the
-widget and render the report.
-you can add [data-no-auto-load] to the widget to prevent report loader to get the widget data automatically.
+The ``data-report-widget`` attribute is used by the javascript to find the widget and render the report.
+The ``data-report-chart`` attribute is used by the javascript to find the chart container and render the chart and the chart selector.
+The ``data-report-table`` attribute is used by the javascript to find the table container and render the table.
+
Customization Example
---------------------
@@ -90,3 +86,9 @@ The success call-back function will receive the report data as a parameter
console.log(data);
}
+
+
+Live example:
+-------------
+
+You can see a live example of the widgets in the `Demo project- Dashboard Page
`_.
diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst
index 1c69103..112c612 100644
--- a/docs/source/tutorial.rst
+++ b/docs/source/tutorial.rst
@@ -75,8 +75,12 @@ In Slick Reporting, you can do the same thing by creating a report view looking
group_by = "product"
columns = [
"name",
- ComputationField.create(Sum, "quantity", verbose_name="Total quantity sold", is_summable=False),
- ComputationField.create(Sum, "value", name="sum__value", verbose_name="Total Value sold $"),
+ ComputationField.create(
+ Sum, "quantity", verbose_name="Total quantity sold", is_summable=False
+ ),
+ ComputationField.create(
+ Sum, "value", name="sum__value", verbose_name="Total Value sold $"
+ ),
]
chart_settings = [
@@ -132,7 +136,12 @@ You can also export the report to CSV.
group_by = "client__country" # notice the double underscore
columns = [
"client__country",
- ComputationField.create(Sum, "value", name="sum__value", verbose_name="Total Value sold by country $"),
+ ComputationField.create(
+ Sum,
+ "value",
+ name="sum__value",
+ verbose_name="Total Value sold by country $",
+ ),
]
chart_settings = [
@@ -164,7 +173,6 @@ A time series report is a report that computes the data for each period of time.
name = "my_value_sum"
-
class MonthlyProductSales(ReportView):
report_model = SalesTransaction
date_field = "date"
@@ -266,9 +274,10 @@ A list report is a report that shows a list of records. For example, if you want
report_model = SalesTransaction
report_title = "Last 10 sales"
date_field = "date"
- filters = ["client"]
+ filters = ["product", "client", "date"]
columns = [
- "product",
+ "product__name",
+ "client__name",
"date",
"quantity",
"price",
@@ -279,6 +288,7 @@ A list report is a report that shows a list of records. For example, if you want
+
Then again in your urls.py add the following:
.. code-block:: python
@@ -312,7 +322,7 @@ The system expect that the form used with the ``ReportView`` to implement the ``
The interface is simple, only 3 mandatory methods to implement, The rest are mandatory only if you are working with a crosstab report or a time series report.
-* ``get_filters``: Mandatory, return a tuple (Q_filers , kwargs filter) to be used in filtering.
+* ``get_filters``: Mandatory, return a tuple (Q_filters , kwargs filter) to be used in filtering.
q_filter: can be none or a series of Django's Q queries
kwargs_filter: None or a dictionary of filters
@@ -351,7 +361,10 @@ Example
required=False, label="End Date", widget=forms.DateInput(attrs={"type": "date"})
)
product_size = forms.ChoiceField(
- choices=PRODUCT_SIZE_CHOICES, required=False, label="Product Size", initial="all"
+ choices=PRODUCT_SIZE_CHOICES,
+ required=False,
+ label="Product Size",
+ initial="all",
)
def get_filters(self):
@@ -369,6 +382,13 @@ Example
q_filters.append(~Q(product__size__in=["extra_big", "big"]))
return q_filters, kw_filters
+ def get_start_date(self):
+ return self.cleaned_data["start_date"]
+
+ def get_end_date(self):
+ return self.cleaned_data["end_date"]
+
+
Recap
=====
diff --git a/slick_reporting/__init__.py b/slick_reporting/__init__.py
index 1c16be3..343aaab 100644
--- a/slick_reporting/__init__.py
+++ b/slick_reporting/__init__.py
@@ -1,5 +1,5 @@
default_app_config = "slick_reporting.apps.ReportAppConfig"
-VERSION = (1, 1, 1)
+VERSION = (1, 2, 0)
-__version__ = "1.1.1"
+__version__ = "1.2.0"
diff --git a/slick_reporting/app_settings.py b/slick_reporting/app_settings.py
index 30c1876..1a5a656 100644
--- a/slick_reporting/app_settings.py
+++ b/slick_reporting/app_settings.py
@@ -41,6 +41,25 @@ def get_end_date():
"JQUERY_URL": SLICK_REPORTING_JQUERY_URL,
"DEFAULT_START_DATE_TIME": get_start_date(),
"DEFAULT_END_DATE_TIME": get_end_date(),
+ "DEFAULT_CHARTS_ENGINE": SLICK_REPORTING_DEFAULT_CHARTS_ENGINE,
+ "MEDIA": {
+ "override": False,
+ "js": (
+ "https://cdn.jsdelivr.net/momentjs/latest/moment.min.js",
+ "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js",
+ "https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js",
+ "https://cdn.datatables.net/1.13.4/js/dataTables.bootstrap5.min.js",
+ "slick_reporting/slick_reporting.js",
+ "slick_reporting/slick_reporting.report_loader.js",
+ "slick_reporting/slick_reporting.datatable.js",
+ ),
+ "css": {
+ "all": (
+ "https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css",
+ "https://cdn.datatables.net/1.13.4/css/dataTables.bootstrap5.min.css",
+ )
+ },
+ },
"FONT_AWESOME": {
"CSS_URL": "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css",
"ICONS": {
@@ -52,17 +71,34 @@ def get_end_date():
},
},
"CHARTS": {
- "highcharts": "$.slick_reporting.highcharts.displayChart",
- "chartjs": "$.slick_reporting.chartjs.displayChart",
+ "highcharts": {
+ "entryPoint": "$.slick_reporting.highcharts.displayChart",
+ "js": ("https://code.highcharts.com/highcharts.js", "slick_reporting/slick_reporting.highchart.js"),
+ },
+ "chartsjs": {
+ "entryPoint": "$.slick_reporting.chartsjs.displayChart",
+ "js": ("https://cdn.jsdelivr.net/npm/chart.js", "slick_reporting/slick_reporting.chartsjs.js"),
+ },
},
"MESSAGES": {
"total": _("Total"),
+ "export_to_csv": _("Export to CSV"),
},
}
def get_slick_reporting_settings():
- slick_settings = {**SLICK_REPORTING_SETTINGS_DEFAULT, **getattr(settings, "SLICK_REPORTING_SETTINGS", {})}
+ slick_settings = SLICK_REPORTING_SETTINGS_DEFAULT.copy()
+ slick_chart_settings = slick_settings["CHARTS"].copy()
+
+ user_settings = getattr(settings, "SLICK_REPORTING_SETTINGS", {})
+ user_chart_settings = user_settings.get("CHARTS", {})
+
+ slick_chart_settings.update(user_chart_settings)
+ slick_settings.update(user_settings)
+ slick_settings["CHARTS"] = slick_chart_settings
+
+ # slick_settings = {**SLICK_REPORTING_SETTINGS_DEFAULT, **getattr(settings, "SLICK_REPORTING_SETTINGS", {})}
start_date = getattr(settings, "SLICK_REPORTING_DEFAULT_START_DATE", False)
end_date = getattr(settings, "SLICK_REPORTING_DEFAULT_END_DATE", False)
# backward compatibility, todo remove in next major release
@@ -75,3 +111,7 @@ def get_slick_reporting_settings():
SLICK_REPORTING_SETTINGS = lazy(get_slick_reporting_settings, dict)()
+
+
+def get_media():
+ return SLICK_REPORTING_SETTINGS["MEDIA"]
diff --git a/slick_reporting/fields.py b/slick_reporting/fields.py
index f0f461c..269136d 100644
--- a/slick_reporting/fields.py
+++ b/slick_reporting/fields.py
@@ -1,10 +1,11 @@
+from __future__ import annotations
+
from warnings import warn
from django.db.models import Sum, Q
from django.template.defaultfilters import date as date_filter
from django.utils.translation import gettext_lazy as _
-from .helpers import get_calculation_annotation
from .registry import field_registry
@@ -126,27 +127,22 @@ def __init__(
self.group_by = self.group_by or group_by
self._cache = None, None, None
self._require_classes = self._get_required_classes()
+ self._required_prepared_results = None
- if not self.plus_side_q and not self.minus_side_q:
- self._debit_and_credit = False
+ self._debit_and_credit = self.plus_side_q or self.minus_side_q
@classmethod
def _get_required_classes(cls):
requires = cls.requires or []
return [field_registry.get_field_by_name(x) if isinstance(x, str) else x for x in requires]
- def apply_q_plus_filter(self, qs):
- return qs.filter(*self.plus_side_q)
-
- def apply_q_minus_filter(self, qs):
- return qs.filter(*self.minus_side_q)
-
def apply_aggregation(self, queryset, group_by=""):
annotation = self.calculation_method(self.calculation_field)
if self.group_by_custom_querysets:
return queryset.aggregate(annotation)
elif group_by:
queryset = queryset.values(group_by).annotate(annotation)
+ queryset = {str(x[self.group_by]): x for x in queryset}
else:
queryset = queryset.aggregate(annotation)
return queryset
@@ -161,12 +157,21 @@ def init_preparation(self, q_filters=None, kwargs_filters=None, **kwargs):
"""
kwargs_filters = kwargs_filters or {}
- dep_values = self._prepare_dependencies(q_filters, kwargs_filters.copy())
+ required_prepared_results = self._prepare_required_computations(q_filters, kwargs_filters.copy())
+ queryset = self.get_queryset()
if self.group_by_custom_querysets:
debit_results, credit_results = self.prepare_custom_group_by_queryset(q_filters, kwargs_filters, **kwargs)
else:
- debit_results, credit_results = self.prepare(q_filters, kwargs_filters, **kwargs)
- self._cache = debit_results, credit_results, dep_values
+ debit_results, credit_results = self.prepare(
+ q_filters,
+ kwargs_filters,
+ queryset,
+ self.group_by,
+ self.prevent_group_by,
+ **kwargs,
+ )
+ self._cache = debit_results, credit_results
+ self._required_prepared_results = required_prepared_results
def prepare_custom_group_by_queryset(self, q_filters=None, kwargs_filters=None, **kwargs):
debit_output, credit_output = [], []
@@ -178,20 +183,34 @@ def prepare_custom_group_by_queryset(self, q_filters=None, kwargs_filters=None,
credit_output.append(credit)
return debit_output, credit_output
- def prepare(self, q_filters=None, kwargs_filters=None, queryset=None, **kwargs):
+ def prepare(
+ self,
+ q_filters: list | object = None,
+ kwargs_filters: dict = None,
+ main_queryset=None,
+ group_by: str = None,
+ prevent_group_by=None,
+ **kwargs,
+ ):
"""
This is the first hook where you can customize the calculation away from the Django Query aggregation method
- This method et called with all available parameters , so you can prepare the results for the whole set and save
+ This method is called with all available arguments, so you can prepare the results for the whole set and save
it in a local cache (like self._cache) .
The flow will later call the method `resolve`, giving you the id, for you to return it respective calculation
:param q_filters:
:param kwargs_filters:
+ :param main_queryset:
+ :param group_by:
+ :param prevent_group_by:
:param kwargs:
:return:
"""
- queryset = queryset or self.get_queryset()
- group_by = "" if self.prevent_group_by else self.group_by
+
+ queryset = main_queryset.all()
+ group_by = "" if prevent_group_by else group_by
+ credit_results = None
+
if q_filters:
if type(q_filters) is Q:
q_filters = [q_filters]
@@ -200,19 +219,17 @@ def prepare(self, q_filters=None, kwargs_filters=None, queryset=None, **kwargs):
queryset = queryset.filter(**kwargs_filters)
if self.plus_side_q:
- queryset = self.apply_q_plus_filter(queryset)
+ queryset = queryset.filter(*self.plus_side_q)
debit_results = self.apply_aggregation(queryset, group_by)
- credit_results = None
if self._debit_and_credit:
- queryset = self.get_queryset()
+ queryset = main_queryset.all()
if kwargs_filters:
queryset = queryset.filter(**kwargs_filters)
if q_filters:
queryset = queryset.filter(*q_filters)
if self.minus_side_q:
- queryset = self.apply_q_minus_filter(queryset)
-
+ queryset = queryset.filter(*self.minus_side_q)
credit_results = self.apply_aggregation(queryset, group_by)
return debit_results, credit_results
@@ -225,21 +242,14 @@ def get_queryset(self):
queryset = queryset.filter(**self.base_kwargs_filters)
return queryset.order_by()
- def get_annotation_name(self):
- """
- Get the annotation per the database
- :return: string used ex:
- """
- return get_calculation_annotation(self.calculation_field, self.calculation_method)
-
- def _prepare_dependencies(
+ def _prepare_required_computations(
self,
q_filters=None,
extra_filters=None,
):
values = {}
- for dep_class in self._require_classes:
- dep = dep_class(
+ for required_klass in self._require_classes:
+ dep = required_klass(
self.plus_side_q,
self.minus_side_q,
self.report_model,
@@ -252,90 +262,67 @@ def _prepare_dependencies(
values[dep.name] = {"results": results, "instance": dep}
return values
- def resolve(self, current_obj, current_row=None):
+ def resolve(self, prepared_results, required_computation_results: dict, current_pk, current_row=None) -> float:
"""
Reponsible for getting the exact data from the prepared value
- :param cached: the returned data from prepare
- :param current_obj: he value of group by id
+ :param prepared_results: the returned data from prepare
+ :param required_computation_results: the returned data from prepare
+ :param current_pk: he value of group by id
:param current_row: the row in iteration
:return: a solid number or value
"""
- cached = self._cache
- debit_value, credit_value = self.extract_data(cached, current_obj)
- dependencies_value = self._resolve_dependencies(current_obj)
+ debit_value, credit_value = self.extract_data(prepared_results, current_pk)
+ value = debit_value or 0 - credit_value or 0
+ return value
- return self.final_calculation(debit_value, credit_value, dependencies_value)
+ def do_resolve(self, current_obj, current_row=None):
+ prepared_result = self._cache
+ dependencies_value = self._resolve_dependencies(current_obj)
+ return self.resolve(prepared_result, dependencies_value, current_obj, current_row)
- def get_dependency_value(self, current_obj, name=None):
+ def get_dependency_value(self, current_obj, name):
"""
Get the values of the ReportFields specified in `requires`
:param current_obj: the current object which we want the calculation for
- :param name: Optional, the name of the specific dependency you want.
+ :param name: the name of the specific dependency you want.
:return: a dict containing dependencies names as keys and their calculation as values
or a specific value if name is specified.
"""
- values = self._resolve_dependencies(current_obj, name=None)
- if name:
- return values.get(name)
- return values
+ values = self._resolve_dependencies(current_obj, name=name)
+ return values.get(name)
def _resolve_dependencies(self, current_obj, name=None):
dep_results = {}
- cached_debit, cached_credit, dependencies_value = self._cache
+ dependencies_value = self._required_prepared_results
dependencies_value = dependencies_value or {}
- for d in dependencies_value.keys():
- if name and d != name:
- continue
+ needed_values = [name] if name else dependencies_value.keys()
+ for d in needed_values:
d_instance = dependencies_value[d]["instance"]
- dep_results[d] = d_instance.resolve(current_obj)
+ dep_results[d] = d_instance.do_resolve(current_obj)
return dep_results
- def extract_data(self, cached, current_obj):
+ def extract_data(self, prepared_results, current_obj):
group_by = "" if self.prevent_group_by else (self.group_by or self.group_by_custom_querysets)
- debit_value = 0
- credit_value = 0
- annotation = self.get_annotation_name()
+ annotation = "__".join([self.calculation_field.lower(), self.calculation_method.name.lower()])
- cached_debit, cached_credit, dependencies_value = cached
+ cached_debit, cached_credit = prepared_results
- if cached_debit or cached_credit:
- debit = None
- if cached_debit is not None:
+ cached = [cached_debit, cached_credit]
+ output = []
+ for results in cached:
+ value = 0
+ if results:
if not group_by:
- x = list(cached_debit.keys())[0]
- debit_value = cached_debit[x]
+ x = list(results.keys())[0]
+ value = results[x]
elif self.group_by_custom_querysets:
- debit = cached_debit[int(current_obj)]
- debit_value = debit[annotation]
+ value = results[int(current_obj)][annotation]
else:
- for i, x in enumerate(cached_debit):
- if str(x[group_by]) == current_obj:
- debit = cached_debit[i]
- break
- if debit:
- debit_value = debit[annotation]
-
- if cached_credit is not None:
- credit = None
- if cached_credit is not None:
- if not group_by:
- x = list(cached_credit.keys())[0]
- credit_value = cached_credit[x]
- else:
- for i, x in enumerate(cached_credit):
- if str(x[group_by]) == current_obj:
- credit = cached_credit[i]
- break
- if credit:
- credit_value = credit[annotation]
- return debit_value, credit_value
-
- def final_calculation(self, debit, credit, dep_dict):
- debit = debit or 0
- credit = credit or 0
- return debit - credit
+ value = results.get(str(current_obj), {}).get(annotation, 0)
+ output.append(value)
+ return output
@classmethod
def get_full_dependency_list(cls):
@@ -399,13 +386,23 @@ class FirstBalanceField(ComputationField):
name = "__fb__"
verbose_name = _("opening balance")
- def prepare(self, q_filters=None, extra_filters=None, **kwargs):
- extra_filters = extra_filters or {}
+ def prepare(
+ self,
+ q_filters: list | object = None,
+ kwargs_filters: dict = None,
+ main_queryset=None,
+ group_by: str = None,
+ prevent_group_by=None,
+ **kwargs,
+ ):
+ extra_filters = kwargs_filters or {}
from_date_value = extra_filters.get(f"{self.date_field}__gte")
extra_filters.pop(f"{self.date_field}__gte", None)
extra_filters[f"{self.date_field}__lt"] = from_date_value
- return super(FirstBalanceField, self).prepare(q_filters, extra_filters)
+ return super(FirstBalanceField, self).prepare(
+ q_filters, kwargs_filters, main_queryset, group_by, prevent_group_by, **kwargs
+ )
field_registry.register(FirstBalanceField)
@@ -425,36 +422,35 @@ class BalanceReportField(ComputationField):
verbose_name = _("Closing Total")
requires = ["__fb__"]
- def final_calculation(self, debit, credit, dep_dict):
- fb = dep_dict.get("__fb__")
- debit = debit or 0
- credit = credit or 0
- fb = fb or 0
- return fb + debit - credit
+ def resolve(self, prepared_results, required_computation_results: dict, current_pk, current_row=None) -> float:
+ result = super().resolve(prepared_results, required_computation_results, current_pk, current_row)
+ fb = required_computation_results.get("__fb__") or 0
+
+ return result + fb
field_registry.register(BalanceReportField)
-class PercentageToBalance(ComputationField):
+class PercentageToTotalBalance(ComputationField):
requires = [BalanceReportField]
- name = "PercentageToBalance"
+ name = "__percent_to_total_balance__"
verbose_name = _("%")
prevent_group_by = True
- def final_calculation(self, debit, credit, dep_dict):
- obj_balance = dep_dict.get("__balance__")
- total = debit - credit
- return (obj_balance / total) * 100
+ def resolve(self, prepared_results, required_computation_results: dict, current_pk, current_row=None) -> float:
+ result = super().resolve(prepared_results, required_computation_results, current_pk, current_row)
+ return required_computation_results.get("__balance__") / result * 100
class CreditReportField(ComputationField):
name = "__credit__"
verbose_name = _("Credit")
- def final_calculation(self, debit, credit, dep_dict):
- return credit
+ def resolve(self, prepared_results, required_computation_results: dict, current_pk, current_row=None) -> float:
+ debit_value, credit_value = self.extract_data(prepared_results, current_pk)
+ return credit_value
field_registry.register(CreditReportField)
@@ -465,8 +461,9 @@ class DebitReportField(ComputationField):
name = "__debit__"
verbose_name = _("Debit")
- def final_calculation(self, debit, credit, dep_dict):
- return debit
+ def resolve(self, prepared_results, required_computation_results: dict, current_pk, current_row=None) -> float:
+ debit_value, credit_value = self.extract_data(prepared_results, current_pk)
+ return debit_value
@field_registry.register
@@ -476,8 +473,9 @@ class CreditQuantityReportField(ComputationField):
calculation_field = "quantity"
is_summable = False
- def final_calculation(self, debit, credit, dep_dict):
- return credit
+ def resolve(self, prepared_results, required_computation_results: dict, current_pk, current_row=None) -> float:
+ debit_value, credit_value = self.extract_data(prepared_results, current_pk)
+ return credit_value
@field_registry.register
@@ -487,8 +485,9 @@ class DebitQuantityReportField(ComputationField):
verbose_name = _("Debit QTY")
is_summable = False
- def final_calculation(self, debit, credit, dep_dict):
- return debit
+ def resolve(self, prepared_results, required_computation_results: dict, current_pk, current_row=None) -> float:
+ debit_value, credit_value = self.extract_data(prepared_results, current_pk)
+ return debit_value
class TotalQTYReportField(ComputationField):
@@ -518,11 +517,10 @@ class BalanceQTYReportField(ComputationField):
requires = ["__fb_quantity__"]
is_summable = False
- def final_calculation(self, debit, credit, dep_dict):
- # Use `get` so it fails loud if its not there
- fb = dep_dict.get("__fb_quantity__")
- fb = fb or 0
- return fb + debit - credit
+ def resolve(self, prepared_results, required_computation_results: dict, current_pk, current_row=None) -> float:
+ result = super().resolve(prepared_results, required_computation_results, current_pk, current_row)
+ fb = required_computation_results.get("__fb_quantity__") or 0
+ return result + fb
field_registry.register(BalanceQTYReportField)
diff --git a/slick_reporting/forms.py b/slick_reporting/forms.py
index 07e0544..b81e6f8 100644
--- a/slick_reporting/forms.py
+++ b/slick_reporting/forms.py
@@ -275,14 +275,14 @@ def report_form_factory(
fields["start_date"] = forms.DateTimeField(
required=False,
label=_("From date"),
- initial=initial.get("start_date", app_settings.SLICK_REPORTING_DEFAULT_START_DATE),
+ initial=initial.get("start_date", "") or app_settings.SLICK_REPORTING_SETTINGS["DEFAULT_START_DATE_TIME"],
widget=forms.DateTimeInput(attrs={"autocomplete": "off"}),
)
fields["end_date"] = forms.DateTimeField(
required=False,
label=_("To date"),
- initial=initial.get("end_date", app_settings.SLICK_REPORTING_DEFAULT_END_DATE),
+ initial=initial.get("end_date", "") or app_settings.SLICK_REPORTING_SETTINGS["DEFAULT_END_DATE_TIME"],
widget=forms.DateTimeInput(attrs={"autocomplete": "off"}),
)
@@ -303,6 +303,7 @@ def report_form_factory(
field_attrs = foreign_key_widget_func(f_field)
if name in required:
field_attrs["required"] = True
+ field_attrs["initial"] = initial.get(name, "")
fields[name] = f_field.formfield(**field_attrs)
if crosstab_model:
diff --git a/slick_reporting/generator.py b/slick_reporting/generator.py
index c8bab46..8c9aafb 100644
--- a/slick_reporting/generator.py
+++ b/slick_reporting/generator.py
@@ -10,6 +10,7 @@
from .fields import ComputationField
from .helpers import get_field_from_query_text
from .registry import field_registry
+from . import app_settings
logger = logging.getLogger(__name__)
@@ -22,6 +23,7 @@ class Chart:
title_source: list
plot_total: bool = False
engine: str = ""
+ entryPoint: str = ""
COLUMN = "column"
LINE = "line"
PIE = "pie"
@@ -36,6 +38,7 @@ def to_dict(self):
title_source=self.title_source,
plot_total=self.plot_total,
engine=self.engine,
+ entryPoint=self.entryPoint,
)
@@ -507,13 +510,14 @@ def _get_record_data(self, obj, columns):
if source:
computation_class = self.report_fields_classes[source]
+ # the computation field is being asked from another computation field that requires it.
value = computation_class.get_dependency_value(group_by_val, col_data["ref"].name)
else:
try:
computation_class = self.report_fields_classes[name]
except KeyError:
continue
- value = computation_class.resolve(group_by_val, data)
+ value = computation_class.do_resolve(group_by_val, data)
if self.swap_sign:
value = -value
data[name] = value
@@ -928,7 +932,8 @@ def get_full_response(
}
return data
- def get_chart_settings(self, chart_settings=None, default_chart_title=None, chart_engine=None):
+ @staticmethod
+ def get_chart_settings(chart_settings=None, default_chart_title=None, chart_engine=None):
"""
Ensure the sane settings are passed to the front end.
"""
@@ -939,7 +944,6 @@ def get_chart_settings(self, chart_settings=None, default_chart_title=None, char
for i, chart in enumerate(chart_settings):
if type(chart) is Chart:
chart = chart.to_dict()
-
chart["id"] = chart.get("id", f"{i}")
chart_type = chart.get("type", "line")
if chart_type == "column" and SLICK_REPORTING_DEFAULT_CHARTS_ENGINE == "chartsjs":
@@ -948,6 +952,11 @@ def get_chart_settings(self, chart_settings=None, default_chart_title=None, char
if not chart.get("title", False):
chart["title"] = report_title
chart["engine_name"] = chart.get("engine_name", chart_engine)
+ chart["entryPoint"] = (
+ chart.get("entryPoint")
+ or app_settings.SLICK_REPORTING_SETTINGS["CHARTS"][chart["engine_name"]]["entryPoint"]
+ )
+
output.append(chart)
return output
@@ -1018,7 +1027,7 @@ def _get_record_data(self, obj, columns):
computation_class = self.report_fields_classes[name]
except KeyError:
continue
- value = computation_class.resolve(group_by_val, data)
+ value = computation_class.do_resolve(group_by_val, data)
if self.swap_sign:
value = -value
data[name] = value
diff --git a/slick_reporting/static/slick_reporting/slick_reporting.chartsjs.js b/slick_reporting/static/slick_reporting/slick_reporting.chartsjs.js
index f8f3447..304de13 100644
--- a/slick_reporting/static/slick_reporting/slick_reporting.chartsjs.js
+++ b/slick_reporting/static/slick_reporting/slick_reporting.chartsjs.js
@@ -17,8 +17,8 @@
return response['metadata']['time_series_column_names'];
}
- function createChartObject(response, chartId, extraOptions) {
- let chartOptions = $.slick_reporting.getObjFromArray(response.chart_settings, 'id', chartId, true);
+ function createChartObject(response, chartOptions, extraOptions) {
+ // let chartOptions = $.slick_reporting.getObjFromArray(response.chart_settings, 'id', chartId, true);
let extractedData = extractDataFromResponse(response, chartOptions);
let chartObject = {
@@ -69,6 +69,27 @@
return chartObject
}
+ function getGroupByLabelAndSeries(response, chartOptions) {
+
+ let legendResults = [];
+ let datasetData = [];
+ let dataFieldName = chartOptions['data_source'];
+ let titleFieldName = chartOptions['title_source'];
+
+ for (let i = 0; i < response.data.length; i++) {
+ let row = response.data[i];
+ if (titleFieldName !== '') {
+ let txt = row[titleFieldName];
+ txt = $(txt).text() || txt; // the title is an
");
}
@@ -162,7 +175,7 @@
console.error(e)
}
- let chartObject = $.slick_reporting.chartsjs.createChartObject(data, chart_id);
+ let chartObject = $.slick_reporting.chartsjs.createChartObject(data, chartOptions);
let $chart = $elem.find('canvas');
try {
_chart_cache[cache_key] = new Chart($chart, chartObject);
@@ -178,6 +191,7 @@
$.slick_reporting = {}
}
$.slick_reporting.chartsjs = {
+ getGroupByLabelAndSeries: getGroupByLabelAndSeries,
createChartObject: createChartObject,
displayChart: displayChart,
defaults: {
diff --git a/slick_reporting/static/slick_reporting/slick_reporting.highchart.js b/slick_reporting/static/slick_reporting/slick_reporting.highchart.js
index 219d917..c7a3c1e 100644
--- a/slick_reporting/static/slick_reporting/slick_reporting.highchart.js
+++ b/slick_reporting/static/slick_reporting/slick_reporting.highchart.js
@@ -1,5 +1,5 @@
/**
- * Created by ramez on 11/20/14.
+ * Created by Ramez on 11/20/14.
*/
(function ($) {
@@ -19,39 +19,7 @@
return output
}
- function getObjFromArray(objList, obj_key, key_value, failToFirst) {
- failToFirst = typeof (failToFirst) !== 'undefined';
- if (key_value !== '') {
- for (let i = 0; i < objList.length; i++) {
- if (objList[i][obj_key] === key_value) {
- return objList[i];
- }
- }
- }
- if (failToFirst && objList.length > 0) {
- return objList[0]
- }
-
- return false;
- }
-
- var ra_chart_settings = {
-
-
- //exporting: {
- // allowHTML:true,
- // enabled: faelse,
- //},
-
- func2: function () {
- var tooltip = '' + this.point.key + '' + this.series.name + ': | ' +
- this.point.y + ' |
' +
- '{Percentage}: | ' +
- '' + this.point.percentage + ' % |
'
- }
- };
let _chart_cache = {};
function normalStackedTooltipFormatter() {
@@ -82,20 +50,14 @@
}
}
- function createChartObject(response, chart_id, extraOptions) {
+ function createChartObject(response, chartOptions, extraOptions) {
// Create the chart Object
- // First specifying the global default
- // second, Get the data from the serponse
- // Adjust the Chart Object accordingly
- let chartOptions = getObjFromArray(response.chart_settings, 'id', chart_id, true)
+ // First specifying the global defaults then apply teh specification from the response
try {
-
-
$.extend(chartOptions, {
'sub_title': '',
});
- // chartOptions = getChartOptions(isGroup, response, chartOptions);
chartOptions.data = response.data;
@@ -103,7 +65,7 @@
let is_crosstab = is_crosstab_support(response, chartOptions);
let chart_type = chartOptions.type;
- var enable3d = false;
+ let enable3d = false;
let chart_data = {};
let rtl = false; // $.slick_reporting.highcharts.defaults.rtl;
@@ -120,25 +82,18 @@
let highchart_object = {
chart: {
type: '',
- //renderTo: 'container',
- //printWidth: 600
},
title: {
text: chartOptions.title,
- // useHTML: Highcharts.hasBidiBug
- //useHTML: true
},
subtitle: {
text: chartOptions.sub_title,
useHTML: Highcharts.hasBidiBug
- //useHTML: true
},
yAxis: {
- // title: {text: chartyAxisTitle},
opposite: rtl,
},
xAxis: {
- // title: {text: chartxAxisTitle},
labels: {enabled: true},
reversed: rtl,
},
@@ -150,7 +105,6 @@
allowHTML: true,
enabled: true,
- //scale:2,
}
};
@@ -163,9 +117,6 @@
if (chart_type === 'bar' || chart_type === 'column') {
highchart_object['xAxis'] = {
categories: chart_data['titles'],
- // title: {
- // text: null
- // }
};
}
highchart_object['yAxis']['labels'] = {overflow: 'justify'};
@@ -188,18 +139,6 @@
}
};
- // highchart_object.tooltip = {
- // useHTML: true,
- // headerFormat: '
{point.key}',
- // valueDecimals: 2
- // };
-
highchart_object['legend'] = {
layout: 'vertical',
align: 'right',
@@ -415,10 +354,7 @@
'name': col_dict[col].verbose_name,
'data': [totalValues[col]]
})
-
})
-
-
})
}
return {
@@ -438,16 +374,13 @@
return response.metadata.crosstab_model || ''
}
- function displayChart(data, $elem, chart_id) {
- chart_id = chart_id || $elem.attr('data-report-default-chart') || '';
+ function displayChart(data, $elem, chartOptions) {
if ($elem.find("div[data-inner-chart-container]").length === 0) {
$elem.append('
')
}
let chart = $elem.find("div[data-inner-chart-container]")
- // chart.append("
");
- // let chartObject = getObjFromArray(data.chart_settings, 'id', chart_id, true);
- let cache_key = data.report_slug + ':' + chart_id
+ let cache_key = data.report_slug + ':' + chartOptions.id;
try {
let existing_chart = _chart_cache[cache_key];
if (typeof (existing_chart) !== 'undefined') {
@@ -457,7 +390,7 @@
console.error(e)
}
- chartObject = $.slick_reporting.highcharts.createChartObject(data, chart_id);
+ let chartObject = $.slick_reporting.highcharts.createChartObject(data, chartOptions);
_chart_cache[cache_key] = chart.highcharts(chartObject);
}
@@ -473,15 +406,12 @@
percent: 'Percent',
},
credits: {
- // text: 'RaSystems.io',
- // href: 'https://rasystems.io'
+ // text: '',
+ // href: ''
},
- // notify_error: notify_error,
enable3d: false,
-
}
};
-
}
(jQuery)
diff --git a/slick_reporting/static/slick_reporting/slick_reporting.js b/slick_reporting/static/slick_reporting/slick_reporting.js
index 5442699..bc575d9 100644
--- a/slick_reporting/static/slick_reporting/slick_reporting.js
+++ b/slick_reporting/static/slick_reporting/slick_reporting.js
@@ -10,11 +10,11 @@
try {
func = context[func];
if (typeof func == 'undefined') {
- throw 'Function {0} is not found the context {1}'.format(functionName, context);
+ throw `Function ${functionName} is not found in the context ${context}`
}
} catch (err) {
- console.error('Function {0} is not found the context {1}'.format(functionName, context), err)
+ console.error(`Function ${functionName} is not found in the context ${context}`, err)
}
return func.apply(context, args);
}
diff --git a/slick_reporting/static/slick_reporting/slick_reporting.report_loader.js b/slick_reporting/static/slick_reporting/slick_reporting.report_loader.js
index 105265d..f36f28b 100644
--- a/slick_reporting/static/slick_reporting/slick_reporting.report_loader.js
+++ b/slick_reporting/static/slick_reporting/slick_reporting.report_loader.js
@@ -37,16 +37,9 @@
function displayChart(data, $elem, chart_id) {
let engine = "highcharts";
- try {
- if (chart_id === '' || typeof (chart_id) === "undefined") {
- engine = data.chart_settings[0]['engine_name'];
- } else {
- engine = data.chart_settings.find(x => x.id === chart_id).engine_name;
- }
- } catch (e) {
- console.error(e);
- }
- $.slick_reporting.executeFunctionByName($.slick_reporting.report_loader.chart_engines[engine], window, data, $elem, chart_id);
+ let chartOptions = $.slick_reporting.getObjFromArray(data.chart_settings, 'id', chart_id, true);
+ let entryPoint = chartOptions.entryPoint || $.slick_reporting.report_loader.chart_engines[engine];
+ $.slick_reporting.executeFunctionByName(entryPoint, window, data, $elem, chartOptions);
}
@@ -86,14 +79,26 @@
function initialize() {
settings = JSON.parse(document.getElementById('slick_reporting_settings').textContent);
+ let chartSettings = {};
$('[data-report-widget]').not('[data-no-auto-load]').each(function (i, elem) {
refreshReportWidget($(elem));
});
+
+ Object.keys(settings["CHARTS"]).forEach(function (key) {
+ chartSettings[key] = settings.CHARTS[key].entryPoint;
+ })
+ $.slick_reporting.report_loader.chart_engines = chartSettings;
+ try {
+ $("select").select2();
+ } catch (e) {
+ console.error(e);
+ }
+ $.slick_reporting.defaults.total_label = settings["MESSAGES"]["TOTAL_LABEL"];
}
function _get_chart_icon(chart_type) {
try {
- return "
";
+ return "
";
} catch (e) {
console.error(e);
}
@@ -136,17 +141,33 @@
});
+ $('[data-export-btn]').on('click', function (e) {
+ let $elem = $(this);
+ e.preventDefault()
+ let form = $($elem.attr('data-form-selector'));
+ window.location = '?' + form.serialize() + '&_export=' + $elem.attr('data-export-parameter');
+ });
+ $('[data-get-results-button]').not(".vanilla-btn-flag").on('click', function (event) {
+ event.preventDefault();
+ let $elem = $('[data-report-widget]')
+ $.slick_reporting.report_loader.refreshReportWidget($elem)
+ });
+
+ jQuery(document).ready(function () {
+ $.slick_reporting.report_loader.initialize();
+ });
+
+
+
$.slick_reporting.report_loader = {
cache: $.slick_reporting.cache,
+ // "extractDataFromResponse": extractDataFromResponse,
initialize: initialize,
refreshReportWidget: refreshReportWidget,
failFunction: failFunction,
displayChart: displayChart,
createChartsUIfromResponse: createChartsUIfromResponse,
successCallback: loadComponents,
- "chart_engines": {
- 'highcharts': '$.slick_reporting.highcharts.displayChart',
- "chartsjs": '$.slick_reporting.chartsjs.displayChart',
- }
+
}
})(jQuery);
\ No newline at end of file
diff --git a/slick_reporting/templates/slick_reporting/base.html b/slick_reporting/templates/slick_reporting/base.html
index 6795984..6464070 100644
--- a/slick_reporting/templates/slick_reporting/base.html
+++ b/slick_reporting/templates/slick_reporting/base.html
@@ -32,9 +32,11 @@
{{ report_title }}