Skip to content

Commit

Permalink
Enhance API
Browse files Browse the repository at this point in the history
  • Loading branch information
RamezIssac committed Oct 2, 2023
1 parent 03207ad commit 744346e
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 56 deletions.
21 changes: 11 additions & 10 deletions docs/source/topics/computation_field.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,21 +87,24 @@ Sometime you want to stack values on top of each other. For example: Net revenue
total = debit - credit
return (obj_balance / total) * 100
We need to override ``final_calculation`` to do the needed calculation. The ``required_results`` is a dictionary of the results of the required fields, where the keys are the names.

How it works ?
--------------
When the `ReportGenerator` is initialized, it generates a list of the needed fields to be displayed and computed.
Each computation field in the report is given the filters needed and asked to get all the results prepared.
Then for each record, the ReportGenerator again asks each ComputationField to get the data it has for each record and map it where it belongs.
Then for each record, the ReportGenerator again asks each ComputationField to get the data it has for each record and map it back.


Customizing the Calculation Flow:
---------------------------------

ReportGenerator call
The results are prepared in 2 main stages

1. Preparation: Where you can get the whole result set for the report. Example: Sum of all the values in a model group by the products.
2. resolve: Where you get the value for each record.


1. prepare
2. resolve


.. code-block:: python
Expand All @@ -111,8 +114,11 @@ ReportGenerator call
def prepare(self, q_filters: list | object = None, kwargs_filters: dict = None, queryset=None, **kwargs):
# do all you calculation here for the whole set if any and return the prepared results
# The main implementation for example
pass
def resolve(self, prepared_results, required_computation_results: dict, current_pk, current_row=None) -> float:
# does the calculation for each record, return a value
pass
Bundled Report Fields
---------------------
Expand All @@ -138,8 +144,3 @@ Case: You have a client that buys 10 in Jan, 12 in Feb and 13 in March.



Two side calculation
--------------------

# todo:
# Document how a single field can be computed like a debit and credit.
82 changes: 36 additions & 46 deletions slick_reporting/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ def init_preparation(self, q_filters=None, kwargs_filters=None, **kwargs):
self.prevent_group_by,
**kwargs,
)
self._cache = debit_results, credit_results, required_prepared_results
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 = [], []
Expand Down Expand Up @@ -307,23 +308,23 @@ def _prepare_required_computations(
values[dep.name] = {"results": results, "instance": dep}
return values

def resolve(self, prepared_results, 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 prepared_results: the returned data from prepare
:param current_obj: he value of group by id
: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(prepared_results, current_obj)
dependencies_value = self._resolve_dependencies(current_obj)

return self.final_calculation(debit_value, credit_value, dependencies_value)
debit_value, credit_value = self.extract_data(prepared_results, current_pk)
value = debit_value or 0 - credit_value or 0
return value

def do_resolve(self, current_obj, current_row=None):
prepared_result = self._cache
return self.resolve(prepared_result, current_obj, current_row)
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):
"""
Expand All @@ -340,7 +341,7 @@ def get_dependency_value(self, current_obj, 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 {}
needed_values = [name] if name else dependencies_value.keys()
for d in needed_values:
Expand All @@ -354,7 +355,7 @@ def extract_data(self, cached, current_obj):
credit_value = 0
annotation = self.get_annotation_name()

cached_debit, cached_credit, dependencies_value = cached
cached_debit, cached_credit = cached

if cached_debit or cached_credit:
debit = None
Expand Down Expand Up @@ -388,18 +389,6 @@ def extract_data(self, cached, current_obj):
credit_value = credit[annotation]
return debit_value, credit_value

def final_calculation(self, debit: float, credit: float, required_results: dict):
"""
Gets the extracted values and the required values to make the last step
:param debit:
:param credit:
:param required_results:
:return:
"""
debit = debit or 0
credit = credit or 0
return debit - credit

@classmethod
def get_full_dependency_list(cls):
"""
Expand Down Expand Up @@ -499,12 +488,11 @@ class BalanceReportField(ComputationField):
verbose_name = _("Closing Total")
requires = ["__fb__"]

def final_calculation(self, debit, credit, required_results):
fb = required_results.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)
Expand All @@ -517,18 +505,18 @@ class PercentageToTotalBalance(ComputationField):

prevent_group_by = True

def final_calculation(self, debit, credit, required_results):
obj_balance = required_results.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, required_results):
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)
Expand All @@ -539,8 +527,9 @@ class DebitReportField(ComputationField):
name = "__debit__"
verbose_name = _("Debit")

def final_calculation(self, debit, credit, required_results):
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
Expand All @@ -550,8 +539,9 @@ class CreditQuantityReportField(ComputationField):
calculation_field = "quantity"
is_summable = False

def final_calculation(self, debit, credit, required_results):
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
Expand All @@ -561,8 +551,9 @@ class DebitQuantityReportField(ComputationField):
verbose_name = _("Debit QTY")
is_summable = False

def final_calculation(self, debit, credit, required_results):
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):
Expand Down Expand Up @@ -592,11 +583,10 @@ class BalanceQTYReportField(ComputationField):
requires = ["__fb_quantity__"]
is_summable = False

def final_calculation(self, debit, credit, required_results):
# Use `get` so it fails loud if its not there
fb = required_results.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)
Expand Down

0 comments on commit 744346e

Please sign in to comment.