Skip to content

Commit

Permalink
Merge branch 'release/v0.8.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Ramez Ashraf committed May 28, 2023
2 parents 70efb4b + c3172ba commit 121348f
Show file tree
Hide file tree
Showing 16 changed files with 211 additions and 59 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to this project will be documented in this file.

## [0.8.0]

- Breaking: [Only if you use Crosstab reports] renamed crosstab_compute_reminder to crosstab_compute_remainder
- Breaking : [Only if you set the templates statics by hand] renamed slick_reporting to ra.hightchart.js and ra.chartjs.js to
erp_framework.highchart.js and erp_framework.chartjs.js respectively

## [0.7.0]

- Added SlickReportingListView: a Report Class to display content of the model (like a ModelAdmin ChangeList)
Expand Down
3 changes: 2 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ Django Slick Reporting

A one stop reports engine with batteries included.

This is project is an extract of the reporting engine of `Django ERP Framework <https://github.com/RamezIssac/django-erp-framework>`_

Features
--------

- Effortlessly create Simple, Grouped, Time series and Crosstab reports in a handful of code lines.
- Create your Custom Calculation easily, which will be integrated with the above reports types
- Optimized for speed.
- Batteries included! Chart.js , DataTable.net & a Bootstrap form.
- Batteries included! Highcharts & Chart.js charting capabilities , DataTable.net & easily customizable Bootstrap form.

Installation
------------
Expand Down
2 changes: 2 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ Next step :ref:`structure`

concept
the_view
view_options
time_series_options
report_generator
computation_field

Expand Down
62 changes: 62 additions & 0 deletions docs/source/time_series_options.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
Time Series Reports
==================


Here is a quick recipe to what you want to do

.. code-block:: python
from django.utils.translation import gettext_lazy as _
from django.db.models import Sum
from slick_reporting.views import SlickReportView
class MyReport(SlickReportView):
time_series_pattern = "monthly"
# options are : "daily", "weekly", "monthly", "yearly", "custom"
# if time_series_pattern is "custom", then you can specify the dates like so
# time_series_custom_dates = [
# (datetime.date(2020, 1, 1), datetime.date(2020, 1, 14)),
# (datetime.date(2020, 2, 1), datetime.date(2020, 2, 14)),
# (datetime.date(2020, 3, 1), datetime.date(2020, 3,14)),
]
time_series_columns = [
SlickReportField.create(Sum, "value", verbose_name=_("Value")),
]
# These columns will be calculated for each period in the time series.
columns = ['some_optional_field',
'__time_series__',
# You can customize where the time series columns are displayed in relation to the other columns
SlickReportField.create(Sum, "value", verbose_name=_("Value")),
# This is the same as the time_series_columns, but this one will be on the whole set
]
time_series_selector = True
# This will display a selector to change the time series pattern
# settings for the time series selector
# ----------------------------------
time_series_selector_choices=None # A list Choice tuple [(value, label), ...]
time_series_selector_default = "monthly" # The initial value for the time series selector
time_series_selector_label = _("Period Pattern) # The label for the time series selector
time_series_selector_allow_empty = False # Allow the user to select an empty time series
Links to demo
-------------
Time series Selector pattern Demo `Demo <https://my-shop.django-erp-framework.com/reports/profitability/profitabilityreportmonthly/>`_
and here is the `Code on github <https://github.com/RamezIssac/my-shop/blob/main/general_reports/reports.py#L44>`_ for the report.
49 changes: 49 additions & 0 deletions docs/source/view_options.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@

Report View Options
===================

We can categorize the output of a report into 4 sections:

* List report: Similar to a django changelist, it's a direct view of the report model records with some extra features like sorting, filtering, pagination, etc.
* Grouped report: similar to what you'd expect from a SQL group by query, it's a list of records grouped by a certain field
* Time series report: a step up from the grouped report, where the results are computed for each time period (day, week, month, year, etc) or you can specify a custom periods.
* Crosstab report: It's a report where a table showing the relationship between two or more variables. (like Client sales of each product comparison)



General Options
---------------

* columns

Columns can be a list of column names , or a tuple of (column name, options dictionary) pairs.

example:

.. code-block:: python
class MyReport()
columns = [
'id',
('name', {'verbose_name': "My verbose name", is_summable=False}),
'description',
]
* date_field: the date field to be used in filtering and computing (ie: the time series report).
* report_model: the model where the relevant data is stored, in more complex reports, it's usually a database view / materialized view.

* report_title: the title of the report to be displayed in the report page.

* group_by : the group by field, if not specified, the report will be a list report.

* excluded_fields

* chart_settings : a list of dictionary (or Chart object) of charts you want to attach to the report.





4 changes: 2 additions & 2 deletions slick_reporting/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
default_app_config = "slick_reporting.apps.ReportAppConfig"

VERSION = (0, 7, 0)
VERSION = (0, 8, 0)

__version__ = "0.7.0"
__version__ = "0.8.0"
38 changes: 29 additions & 9 deletions slick_reporting/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def get_crosstab_field_verbose_name(cls, model, id):
:return: a verbose string
"""
if id == "----":
return _("The reminder")
return _("The remainder")
return f"{cls.verbose_name} {model} {id}"

@classmethod
Expand Down Expand Up @@ -376,7 +376,7 @@ def get_time_series_field_verbose_name(cls, date_period, index, dates, pattern):

class FirstBalanceField(SlickReportField):
name = "__fb__"
verbose_name = _("first balance")
verbose_name = _("opening balance")

def prepare(self, q_filters=None, extra_filters=None, **kwargs):
extra_filters = extra_filters or {}
Expand All @@ -401,7 +401,7 @@ class TotalReportField(SlickReportField):

class BalanceReportField(SlickReportField):
name = "__balance__"
verbose_name = _("Cumulative Total")
verbose_name = _("Closing Total")
requires = ["__fb__"]

def final_calculation(self, debit, credit, dep_dict):
Expand Down Expand Up @@ -439,6 +439,7 @@ def final_calculation(self, debit, credit, dep_dict):
field_registry.register(CreditReportField)


@field_registry.register
class DebitReportField(SlickReportField):
name = "__debit__"
verbose_name = _("Debit")
Expand All @@ -447,7 +448,26 @@ def final_calculation(self, debit, credit, dep_dict):
return debit


field_registry.register(DebitReportField)
@field_registry.register
class CreditQuantityReportField(SlickReportField):
name = "__credit_quantity__"
verbose_name = _("Credit QTY")
calculation_field = "quantity"
is_summable = False

def final_calculation(self, debit, credit, dep_dict):
return credit


@field_registry.register
class DebitQuantityReportField(SlickReportField):
name = "__debit_quantity__"
calculation_field = "quantity"
verbose_name = _("Debit QTY")
is_summable = False

def final_calculation(self, debit, credit, dep_dict):
return debit


class TotalQTYReportField(SlickReportField):
Expand All @@ -461,8 +481,8 @@ class TotalQTYReportField(SlickReportField):


class FirstBalanceQTYReportField(FirstBalanceField):
name = "__fb_quan__"
verbose_name = _("starting QTY")
name = "__fb_quantity__"
verbose_name = _("Opening QTY")
calculation_field = "quantity"
is_summable = False

Expand All @@ -472,14 +492,14 @@ class FirstBalanceQTYReportField(FirstBalanceField):

class BalanceQTYReportField(SlickReportField):
name = "__balance_quantity__"
verbose_name = _("Cumulative QTY")
verbose_name = _("Closing QTY")
calculation_field = "quantity"
requires = ["__fb_quan__"]
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_quan__")
fb = dep_dict.get("__fb_quantity__")
fb = fb or 0
return fb + debit - credit

Expand Down
27 changes: 14 additions & 13 deletions slick_reporting/form_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def get_crispy_helper(
foreign_keys_map=None,
crosstab_model=None,
crosstab_key_name=None,
crosstab_display_compute_reminder=False,
crosstab_display_compute_remainder=False,
add_date_range=True,
):
from crispy_forms.helper import FormHelper
Expand All @@ -52,8 +52,8 @@ def get_crispy_helper(
# first add the crosstab model and its display reimder then the rest of the fields
if crosstab_model:
filters_container.append(Field(crosstab_key_name))
if crosstab_display_compute_reminder:
filters_container.append(Field("crosstab_compute_reminder"))
if crosstab_display_compute_remainder:
filters_container.append(Field("crosstab_compute_remainder"))

for k in foreign_keys_map:
if k != crosstab_key_name:
Expand Down Expand Up @@ -97,6 +97,7 @@ def get_filters(self):

@cached_property
def crosstab_key_name(self):
# todo get the model more accurately
"""
return the actual foreignkey field name by simply adding an '_id' at the end.
This is hook is to customize this naieve approach.
Expand All @@ -114,16 +115,16 @@ def get_crosstab_ids(self):
return [x for x in qs.values_list("pk", flat=True)]
return []

def get_crosstab_compute_reminder(self):
return self.cleaned_data.get("crosstab_compute_reminder", True)
def get_crosstab_compute_remainder(self):
return self.cleaned_data.get("crosstab_compute_remainder", True)

def get_crispy_helper(self, foreign_keys_map=None, crosstab_model=None, **kwargs):
return get_crispy_helper(
self.foreign_keys,
crosstab_model=getattr(self, "crosstab_model", None),
crosstab_key_name=getattr(self, "crosstab_key_name", None),
crosstab_display_compute_reminder=getattr(
self, "crosstab_display_compute_reminder", False
crosstab_display_compute_remainder=getattr(
self, "crosstab_display_compute_remainder", False
),
**kwargs,
)
Expand All @@ -139,7 +140,7 @@ def _default_foreign_key_widget(f_field):
def report_form_factory(
model,
crosstab_model=None,
display_compute_reminder=True,
display_compute_remainder=True,
fkeys_filter_func=None,
foreign_key_widget_func=None,
excluded_fields=None,
Expand All @@ -158,7 +159,7 @@ def report_form_factory(
:param model: the report_model
:param crosstab_model: crosstab model if any
:param display_compute_reminder: relevant only if crosstab_model is specified. Control if we show the check to
:param display_compute_remainder: relevant only if crosstab_model is specified. Control if we show the check to
display the rest.
:param fkeys_filter_func: a receives an OrderedDict of Foreign Keys names and their model field instances found on the model, return the OrderedDict that would be used
:param foreign_key_widget_func: receives a Field class return the used widget like this {'form_class': forms.ModelMultipleChoiceField, 'required': False, }
Expand Down Expand Up @@ -218,9 +219,9 @@ def report_form_factory(
field_attrs["required"] = True
fields[name] = f_field.formfield(**field_attrs)

if crosstab_model and display_compute_reminder:
fields["crosstab_compute_reminder"] = forms.BooleanField(
required=False, label=_("Display the crosstab reminder"), initial=True
if crosstab_model and display_compute_remainder:
fields["crosstab_compute_remainder"] = forms.BooleanField(
required=False, label=_("Display the crosstab remainder"), initial=True
)

bases = (
Expand All @@ -235,7 +236,7 @@ def report_form_factory(
"_fkeys": fkeys_list,
"foreign_keys": fkeys_map,
"crosstab_model": crosstab_model,
"crosstab_display_compute_reminder": display_compute_reminder,
"crosstab_display_compute_remainder": display_compute_remainder,
},
)
return new_form
Loading

0 comments on commit 121348f

Please sign in to comment.