Skip to content

Commit

Permalink
Merge branch 'release/v0.5.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
RamezIssac committed Dec 16, 2020
2 parents 7ae4173 + b23c697 commit 5d20886
Show file tree
Hide file tree
Showing 15 changed files with 317 additions and 80 deletions.
52 changes: 35 additions & 17 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
# Changelog

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

## [0.5.1] -

- Allow for time series to operate on a non-group by report
- Allow setting time series custom dates on ReportGenerator attr and init
- Fix a bug with setting the queryset (but not the report model) on SlickReportView
- Fixed an issue if GenericForeignKey is on the report model
- Fixed an issue with Time series annual pattern

## [0.5.0] - 2020-12-11

- Created the demo site https://django-slick-reporting.com/
- Add support to group by date field
- Add `format_row` hook to SlickReportingView
- Add support for several chart engine per same report
- Add support for several chart engine per same report
- Add `SLICK_REPORTING_FORM_MEDIA` &`SLICK_REPORTING_DEFAULT_CHARTS_ENGINE` setting.
- Documenting SlickReportView response structure.
- Fix issue with special column names `__time_series__` and `__crosstab__`
- Fix issue with Crosstab reminder option.


## [0.4.2] - 2020-11-29

- Properly initialize Datepicker (#12 @squio)
- Use previous date-range for initialization if it exists

- Use previous date-range for initialization if it exists

## [0.4.1] - 2020-11-26

- Bring back calculateTotalOnObjectArray (#11)
- Bypassing default ordering by when generating the report (#10)
- Fix in dates in template and view

- Bypassing default ordering by when generating the report (#10)
- Fix in dates in template and view

## [0.4.0] - 2020-11-24 [BREAKING]

Expand All @@ -36,64 +42,76 @@ All notable changes to this project will be documented in this file.

- Add Sanity checks against incorrect entries in columns or date_field
- Add support to create ReportField on the fly in all report types
- Enhance exception verbosity.
- Enhance exception verbosity.
- Removed `doc_date` field reference .

## [0.2.9] - 2020-10-22

### Updated

- Fixed an issue getting a db field verbose column name
- Fixed an issue with the report demo page's filter button not working correctly.

## [0.2.8] - 2020-10-05

### Updated
- Fixed an error with ManyToOne Relation not being able to get its verbose name (@mswastik)

- Fixed an error with ManyToOne Relation not being able to get its verbose name (@mswastik)

## [0.2.7] - 2020-07-24

### Updates

- Bring back crosstab capability
- Rename `quan` to the more verbose `quantity`
- Minor enhancements around templates
- Rename `quan` to the more verbose `quantity`
- Minor enhancements around templates

## [0.2.6] - 2020-06-06

### Added

- Adds `is_summable` option for ReportFields, and pass it to response
- Add option to override a report fields while registering it.
- Test ajax Request

### Updates and fixes
- Fix a bug with time series adding one extra period.

- Fix a bug with time series adding one extra period.
- Fix a bug with Crosstab data not passed to `report_form_factory`
- Enhance Time series default column verbose name
- testing: brought back ReportField after unregister test
- Fix Pypi package not including statics.

- Fix Pypi package not including statics.

## [0.2.5] - 2020-06-04

### Added

- Crosstab support
- Crosstab support
- Chart title defaults to report_title
- Enhance fields naming

## [0.2.4] - 2020-05-27

### Added

- Fix a naming issue with license (@iLoveTux)

## [0.2.3] - 2020-05-13

### Added

- Ability to create a ReportField on the fly.
- Document SLICK_REPORTING_DEFAULT_START_DATE & SLICK_REPORTING_DEFAULT_START_DATE settings
- Test Report Field Registry
- Lift the assumption that a Report field name should start and end with "__". This is only a convention now.


## [0.2.2] - 2020-04-26

- Port Charting from [Ra Framework](https://github.com/ra-systems/RA)
- Enhance ReportView HTML response


## [0.0.1] - 2020-04-24

### Added

- Ported from [Ra Framework](https://github.com/ra-systems/RA)
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
master_doc = 'index'

# The full version, including alpha/beta/rc tags
release = '0.5.0'
release = '0.5.1'

# -- General configuration ---------------------------------------------------

Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ To install django-slick-reporting:
2. Add ``slick_reporting'`` to ``INSTALLED_APPS``.
3. For the shipped in View, add ``'crispy_forms'`` to ``INSTALLED_APPS`` and add ``CRISPY_TEMPLATE_PACK = 'bootstrap4'``
to your ``settings.py``
4. Execute `python manage.py collectstatic` so the JS helpers are collected and served.

Demo site
----------
Expand Down
4 changes: 2 additions & 2 deletions slick_reporting/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

default_app_config = 'slick_reporting.apps.ReportAppConfig'

VERSION = (0, 5, 0)
VERSION = (0, 5, 1)

__version__ = '0.5.0'
__version__ = '0.5.1'
6 changes: 3 additions & 3 deletions slick_reporting/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def create(cls, method, field, name=None, verbose_name=None, is_summable=True):
"""
if not name:
identifier = str(uuid.uuid4()).split('-')[-1]
name = name or f"{method.name}__{field}"
name = name or f"{method.name.lower()}__{field}"
assert name not in cls._field_registry.get_all_report_fields_names()

verbose_name = verbose_name or f'{method.name} {field}'
Expand Down Expand Up @@ -221,7 +221,7 @@ def extract_data(self, cached, current_obj):
debit = None
if cached_debit is not None:
if not group_by:
x = cached_debit.keys()[0]
x = list(cached_debit.keys())[0]
debit_value = cached_debit[x]
else:
for i, x in enumerate(cached_debit):
Expand All @@ -235,7 +235,7 @@ def extract_data(self, cached, current_obj):
credit = None
if cached_credit is not None:
if not group_by:
x = cached_credit.keys()[0]
x = list(cached_credit.keys())[0]
credit_value = cached_credit[x]
else:
for i, x in enumerate(cached_credit):
Expand Down
13 changes: 4 additions & 9 deletions slick_reporting/form_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def report_form_factory(model, crosstab_model=None, display_compute_reminder=Tru
: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
display the rest.
:param fkeys_filter_func: a receives the list of Foreign found on the model, return the list of ForeignKeys to be used
: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, }
:return:
"""
Expand All @@ -125,21 +125,16 @@ def report_form_factory(model, crosstab_model=None, display_compute_reminder=Tru

fields['start_date'] = forms.DateTimeField(required=False, label=_('From date'),
initial=app_settings.SLICK_REPORTING_DEFAULT_START_DATE,
widget=forms.DateTimeInput(
attrs={'autocomplete': "off"}),
)
widget=forms.DateTimeInput(attrs={'autocomplete': "off"}))

fields['end_date'] = forms.DateTimeField(required=False, label=_('To date'),
initial=app_settings.SLICK_REPORTING_DEFAULT_END_DATE,
widget=forms.DateTimeInput(
attrs={'autocomplete': "off"})
)
widget=forms.DateTimeInput(attrs={'autocomplete': "off"}))

for name, f_field in fkeys_map.items():
fkeys_list.append(name)

fields[name] = f_field.formfield(
**foreign_key_widget_func(f_field))
fields[name] = f_field.formfield(**foreign_key_widget_func(f_field))

if crosstab_model and display_compute_reminder:
fields['crosstab_compute_reminder'] = forms.BooleanField(required=False,
Expand Down
35 changes: 24 additions & 11 deletions slick_reporting/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ class ReportGenerator(object):
"""

time_series_custom_dates = None
"""
Used with `time_series_pattern` set to 'custom'
It's a list of tuple, each tuple represent start date & end date
Example: [ (start_date_1, end_date_1), (start_date_2, end_date_2), ....]
"""

crosstab_model = None
"""
If set, a cross tab over this model selected ids (via `crosstab_ids`)
Expand Down Expand Up @@ -108,7 +115,7 @@ class ReportGenerator(object):
def __init__(self, report_model=None, main_queryset=None, start_date=None, end_date=None, date_field=None,
q_filters=None, kwargs_filters=None,
group_by=None, columns=None,
time_series_pattern=None, time_series_columns=None,
time_series_pattern=None, time_series_columns=None, time_series_custom_dates=None,
crosstab_model=None, crosstab_columns=None, crosstab_ids=None, crosstab_compute_reminder=None,
swap_sign=False, show_empty_records=None,
print_flag=False,
Expand Down Expand Up @@ -173,6 +180,7 @@ def __init__(self, report_model=None, main_queryset=None, start_date=None, end_d

self.time_series_pattern = self.time_series_pattern or time_series_pattern
self.time_series_columns = self.time_series_columns or time_series_columns
self.time_series_custom_dates = self.time_series_custom_dates or time_series_custom_dates

self._prepared_results = {}
self.report_fields_classes = {}
Expand Down Expand Up @@ -229,14 +237,16 @@ def __init__(self, report_model=None, main_queryset=None, start_date=None, end_d

else:
self.main_queryset = self._apply_queryset_options(main_queryset)

if type(self.group_by_field) is ForeignKey:
ids = self.main_queryset.values_list(self.group_by_field.attname).distinct()
self.main_queryset = self.group_by_field.related_model.objects.filter(pk__in=ids).values()
else:
self.main_queryset = self.main_queryset.distinct().values(self.group_by_field.attname)
else:
self.main_queryset = self._apply_queryset_options(main_queryset, self.get_database_columns())
if self.time_series_pattern:
self.main_queryset = [{}]
else:
self.main_queryset = self._apply_queryset_options(main_queryset, self.get_database_columns())

self._prepare_report_dependencies()

Expand Down Expand Up @@ -341,7 +351,8 @@ def _get_record_data(self, obj, columns):

name = col_data['name']

if col_data.get('source', '') == 'magic_field' and self.group_by:
if (col_data.get('source', '') == 'magic_field' and self.group_by) or (
self.time_series_pattern and not self.group_by):
source = self._report_fields_dependencies[window].get(name, False)
if source:
computation_class = self.report_fields_classes[source]
Expand Down Expand Up @@ -506,7 +517,7 @@ def get_time_series_parsed_columns(self):
_values = []

cols = self.time_series_columns or []
series = self._get_time_series_dates()
series = self._get_time_series_dates(self.time_series_pattern)

for dt in series:
for col in cols:
Expand Down Expand Up @@ -546,12 +557,15 @@ def get_custom_time_series_dates(self):
Hook to get custom , maybe separated date periods
:return: [ (date1,date2) , (date3,date4), .... ]
"""
return []
return self.time_series_custom_dates or []

def _get_time_series_dates(self):
def _get_time_series_dates(self, series=None, start_date=None, end_date=None):
from dateutil.relativedelta import relativedelta
series = series or self.time_series_pattern
start_date = start_date or self.start_date
end_date = end_date or self.end_date
_values = []
series = self.time_series_pattern

if series:
if series == 'daily':
time_delta = datetime.timedelta(days=1)
Expand All @@ -566,20 +580,19 @@ def _get_time_series_dates(self):
elif series == 'semiannually':
time_delta = relativedelta(months=6)
elif series == 'annually':
time_delta = relativedelta(year=1)
time_delta = relativedelta(years=1)
elif series == 'custom':
return self.get_custom_time_series_dates()
else:
raise NotImplementedError(f'"{series}" is not implemented for time_series_pattern')

done = False
start_date = self.start_date

while not done:
to_date = start_date + time_delta
_values.append((start_date, to_date))
start_date = to_date
if to_date >= self.end_date:
if to_date >= end_date:
done = True
return _values

Expand Down
6 changes: 5 additions & 1 deletion slick_reporting/helpers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from collections import OrderedDict

from django.contrib.contenttypes.fields import GenericForeignKey


def get_calculation_annotation(calculation_field, calculation_method):
"""
Expand All @@ -23,7 +25,9 @@ def get_foreign_keys(model):
fkeys = OrderedDict()
for f in fields:
if f.is_relation and type(f) is not models.OneToOneRel \
and type(f) is not models.ManyToOneRel and type(f) is not models.ManyToManyRel:
and type(f) is not models.ManyToOneRel \
and type(f) is not models.ManyToManyRel \
and type(f) is not GenericForeignKey:
fkeys[f.attname] = f
return fkeys

Expand Down
12 changes: 8 additions & 4 deletions slick_reporting/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ def get(self, request, *args, **kwargs):

return self.render_to_response(self.get_context_data())

@classmethod
def get_report_model(cls):
return cls.report_model or cls.queryset.model

def ajax_render_to_response(self, report_data):
return HttpResponse(self.serialize_to_json(report_data),
content_type="application/json")
Expand All @@ -86,7 +90,7 @@ def get_form_class(self):
Automatically instantiate a form based on details provided
:return:
"""
return self.form_class or report_form_factory(self.report_model, crosstab_model=self.crosstab_model,
return self.form_class or report_form_factory(self.get_report_model(), crosstab_model=self.crosstab_model,
display_compute_reminder=self.crosstab_compute_reminder)

def get_form_kwargs(self):
Expand Down Expand Up @@ -120,7 +124,7 @@ def get_report_generator(self, queryset, for_print):
crosstab_compute_reminder = self.form.get_crosstab_compute_reminder() if self.request.GET or self.request.POST \
else self.crosstab_compute_reminder

return self.report_generator_class(self.report_model,
return self.report_generator_class(self.get_report_model(),
start_date=self.form.cleaned_data['start_date'],
end_date=self.form.cleaned_data['end_date'],
q_filters=q_filters,
Expand Down Expand Up @@ -258,7 +262,7 @@ def __init_subclass__(cls) -> None:
raise TypeError(f'`date_field` is not set on {cls}')

# sanity check, raises error if the columns or date fields is not mapped
cls.report_generator_class.check_columns([cls.date_field], False, cls.report_model)
cls.report_generator_class.check_columns(cls.columns, cls.group_by, cls.report_model)
cls.report_generator_class.check_columns([cls.date_field], False, cls.get_report_model())
cls.report_generator_class.check_columns(cls.columns, cls.group_by, cls.get_report_model())

super().__init_subclass__()
Loading

0 comments on commit 5d20886

Please sign in to comment.