Skip to content

Commit 121348f

Browse files
author
Ramez Ashraf
committed
Merge branch 'release/v0.8.0'
2 parents 70efb4b + c3172ba commit 121348f

File tree

16 files changed

+211
-59
lines changed

16 files changed

+211
-59
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

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

5+
## [0.8.0]
6+
7+
- Breaking: [Only if you use Crosstab reports] renamed crosstab_compute_reminder to crosstab_compute_remainder
8+
- Breaking : [Only if you set the templates statics by hand] renamed slick_reporting to ra.hightchart.js and ra.chartjs.js to
9+
erp_framework.highchart.js and erp_framework.chartjs.js respectively
10+
511
## [0.7.0]
612

713
- Added SlickReportingListView: a Report Class to display content of the model (like a ModelAdmin ChangeList)

README.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,15 @@ Django Slick Reporting
2121

2222
A one stop reports engine with batteries included.
2323

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

2526
Features
2627
--------
2728

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

3334
Installation
3435
------------

docs/source/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ Next step :ref:`structure`
7575

7676
concept
7777
the_view
78+
view_options
79+
time_series_options
7880
report_generator
7981
computation_field
8082

docs/source/time_series_options.rst

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
Time Series Reports
2+
==================
3+
4+
5+
Here is a quick recipe to what you want to do
6+
7+
.. code-block:: python
8+
9+
from django.utils.translation import gettext_lazy as _
10+
from django.db.models import Sum
11+
from slick_reporting.views import SlickReportView
12+
13+
class MyReport(SlickReportView):
14+
15+
time_series_pattern = "monthly"
16+
# options are : "daily", "weekly", "monthly", "yearly", "custom"
17+
18+
# if time_series_pattern is "custom", then you can specify the dates like so
19+
# time_series_custom_dates = [
20+
# (datetime.date(2020, 1, 1), datetime.date(2020, 1, 14)),
21+
# (datetime.date(2020, 2, 1), datetime.date(2020, 2, 14)),
22+
# (datetime.date(2020, 3, 1), datetime.date(2020, 3,14)),
23+
]
24+
25+
26+
time_series_columns = [
27+
SlickReportField.create(Sum, "value", verbose_name=_("Value")),
28+
]
29+
# These columns will be calculated for each period in the time series.
30+
31+
32+
33+
columns = ['some_optional_field',
34+
'__time_series__',
35+
# You can customize where the time series columns are displayed in relation to the other columns
36+
37+
SlickReportField.create(Sum, "value", verbose_name=_("Value")),
38+
# This is the same as the time_series_columns, but this one will be on the whole set
39+
40+
]
41+
42+
43+
44+
45+
time_series_selector = True
46+
# This will display a selector to change the time series pattern
47+
48+
# settings for the time series selector
49+
# ----------------------------------
50+
51+
time_series_selector_choices=None # A list Choice tuple [(value, label), ...]
52+
time_series_selector_default = "monthly" # The initial value for the time series selector
53+
time_series_selector_label = _("Period Pattern) # The label for the time series selector
54+
time_series_selector_allow_empty = False # Allow the user to select an empty time series
55+
56+
57+
58+
Links to demo
59+
-------------
60+
61+
Time series Selector pattern Demo `Demo <https://my-shop.django-erp-framework.com/reports/profitability/profitabilityreportmonthly/>`_
62+
and here is the `Code on github <https://github.com/RamezIssac/my-shop/blob/main/general_reports/reports.py#L44>`_ for the report.

docs/source/view_options.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
Report View Options
3+
===================
4+
5+
We can categorize the output of a report into 4 sections:
6+
7+
* 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.
8+
* 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
9+
* 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.
10+
* Crosstab report: It's a report where a table showing the relationship between two or more variables. (like Client sales of each product comparison)
11+
12+
13+
14+
General Options
15+
---------------
16+
17+
* columns
18+
19+
Columns can be a list of column names , or a tuple of (column name, options dictionary) pairs.
20+
21+
example:
22+
23+
.. code-block:: python
24+
25+
class MyReport()
26+
columns = [
27+
'id',
28+
('name', {'verbose_name': "My verbose name", is_summable=False}),
29+
'description',
30+
]
31+
32+
33+
34+
* date_field: the date field to be used in filtering and computing (ie: the time series report).
35+
36+
* report_model: the model where the relevant data is stored, in more complex reports, it's usually a database view / materialized view.
37+
38+
* report_title: the title of the report to be displayed in the report page.
39+
40+
* group_by : the group by field, if not specified, the report will be a list report.
41+
42+
* excluded_fields
43+
44+
* chart_settings : a list of dictionary (or Chart object) of charts you want to attach to the report.
45+
46+
47+
48+
49+

slick_reporting/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
default_app_config = "slick_reporting.apps.ReportAppConfig"
22

3-
VERSION = (0, 7, 0)
3+
VERSION = (0, 8, 0)
44

5-
__version__ = "0.7.0"
5+
__version__ = "0.8.0"

slick_reporting/fields.py

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ def get_crosstab_field_verbose_name(cls, model, id):
345345
:return: a verbose string
346346
"""
347347
if id == "----":
348-
return _("The reminder")
348+
return _("The remainder")
349349
return f"{cls.verbose_name} {model} {id}"
350350

351351
@classmethod
@@ -376,7 +376,7 @@ def get_time_series_field_verbose_name(cls, date_period, index, dates, pattern):
376376

377377
class FirstBalanceField(SlickReportField):
378378
name = "__fb__"
379-
verbose_name = _("first balance")
379+
verbose_name = _("opening balance")
380380

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

402402
class BalanceReportField(SlickReportField):
403403
name = "__balance__"
404-
verbose_name = _("Cumulative Total")
404+
verbose_name = _("Closing Total")
405405
requires = ["__fb__"]
406406

407407
def final_calculation(self, debit, credit, dep_dict):
@@ -439,6 +439,7 @@ def final_calculation(self, debit, credit, dep_dict):
439439
field_registry.register(CreditReportField)
440440

441441

442+
@field_registry.register
442443
class DebitReportField(SlickReportField):
443444
name = "__debit__"
444445
verbose_name = _("Debit")
@@ -447,7 +448,26 @@ def final_calculation(self, debit, credit, dep_dict):
447448
return debit
448449

449450

450-
field_registry.register(DebitReportField)
451+
@field_registry.register
452+
class CreditQuantityReportField(SlickReportField):
453+
name = "__credit_quantity__"
454+
verbose_name = _("Credit QTY")
455+
calculation_field = "quantity"
456+
is_summable = False
457+
458+
def final_calculation(self, debit, credit, dep_dict):
459+
return credit
460+
461+
462+
@field_registry.register
463+
class DebitQuantityReportField(SlickReportField):
464+
name = "__debit_quantity__"
465+
calculation_field = "quantity"
466+
verbose_name = _("Debit QTY")
467+
is_summable = False
468+
469+
def final_calculation(self, debit, credit, dep_dict):
470+
return debit
451471

452472

453473
class TotalQTYReportField(SlickReportField):
@@ -461,8 +481,8 @@ class TotalQTYReportField(SlickReportField):
461481

462482

463483
class FirstBalanceQTYReportField(FirstBalanceField):
464-
name = "__fb_quan__"
465-
verbose_name = _("starting QTY")
484+
name = "__fb_quantity__"
485+
verbose_name = _("Opening QTY")
466486
calculation_field = "quantity"
467487
is_summable = False
468488

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

473493
class BalanceQTYReportField(SlickReportField):
474494
name = "__balance_quantity__"
475-
verbose_name = _("Cumulative QTY")
495+
verbose_name = _("Closing QTY")
476496
calculation_field = "quantity"
477-
requires = ["__fb_quan__"]
497+
requires = ["__fb_quantity__"]
478498
is_summable = False
479499

480500
def final_calculation(self, debit, credit, dep_dict):
481501
# Use `get` so it fails loud if its not there
482-
fb = dep_dict.get("__fb_quan__")
502+
fb = dep_dict.get("__fb_quantity__")
483503
fb = fb or 0
484504
return fb + debit - credit
485505

slick_reporting/form_factory.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def get_crispy_helper(
2525
foreign_keys_map=None,
2626
crosstab_model=None,
2727
crosstab_key_name=None,
28-
crosstab_display_compute_reminder=False,
28+
crosstab_display_compute_remainder=False,
2929
add_date_range=True,
3030
):
3131
from crispy_forms.helper import FormHelper
@@ -52,8 +52,8 @@ def get_crispy_helper(
5252
# first add the crosstab model and its display reimder then the rest of the fields
5353
if crosstab_model:
5454
filters_container.append(Field(crosstab_key_name))
55-
if crosstab_display_compute_reminder:
56-
filters_container.append(Field("crosstab_compute_reminder"))
55+
if crosstab_display_compute_remainder:
56+
filters_container.append(Field("crosstab_compute_remainder"))
5757

5858
for k in foreign_keys_map:
5959
if k != crosstab_key_name:
@@ -97,6 +97,7 @@ def get_filters(self):
9797

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

117-
def get_crosstab_compute_reminder(self):
118-
return self.cleaned_data.get("crosstab_compute_reminder", True)
118+
def get_crosstab_compute_remainder(self):
119+
return self.cleaned_data.get("crosstab_compute_remainder", True)
119120

120121
def get_crispy_helper(self, foreign_keys_map=None, crosstab_model=None, **kwargs):
121122
return get_crispy_helper(
122123
self.foreign_keys,
123124
crosstab_model=getattr(self, "crosstab_model", None),
124125
crosstab_key_name=getattr(self, "crosstab_key_name", None),
125-
crosstab_display_compute_reminder=getattr(
126-
self, "crosstab_display_compute_reminder", False
126+
crosstab_display_compute_remainder=getattr(
127+
self, "crosstab_display_compute_remainder", False
127128
),
128129
**kwargs,
129130
)
@@ -139,7 +140,7 @@ def _default_foreign_key_widget(f_field):
139140
def report_form_factory(
140141
model,
141142
crosstab_model=None,
142-
display_compute_reminder=True,
143+
display_compute_remainder=True,
143144
fkeys_filter_func=None,
144145
foreign_key_widget_func=None,
145146
excluded_fields=None,
@@ -158,7 +159,7 @@ def report_form_factory(
158159
159160
:param model: the report_model
160161
:param crosstab_model: crosstab model if any
161-
:param display_compute_reminder: relevant only if crosstab_model is specified. Control if we show the check to
162+
:param display_compute_remainder: relevant only if crosstab_model is specified. Control if we show the check to
162163
display the rest.
163164
: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
164165
:param foreign_key_widget_func: receives a Field class return the used widget like this {'form_class': forms.ModelMultipleChoiceField, 'required': False, }
@@ -218,9 +219,9 @@ def report_form_factory(
218219
field_attrs["required"] = True
219220
fields[name] = f_field.formfield(**field_attrs)
220221

221-
if crosstab_model and display_compute_reminder:
222-
fields["crosstab_compute_reminder"] = forms.BooleanField(
223-
required=False, label=_("Display the crosstab reminder"), initial=True
222+
if crosstab_model and display_compute_remainder:
223+
fields["crosstab_compute_remainder"] = forms.BooleanField(
224+
required=False, label=_("Display the crosstab remainder"), initial=True
224225
)
225226

226227
bases = (
@@ -235,7 +236,7 @@ def report_form_factory(
235236
"_fkeys": fkeys_list,
236237
"foreign_keys": fkeys_map,
237238
"crosstab_model": crosstab_model,
238-
"crosstab_display_compute_reminder": display_compute_reminder,
239+
"crosstab_display_compute_remainder": display_compute_remainder,
239240
},
240241
)
241242
return new_form

0 commit comments

Comments
 (0)