Skip to content

Commit

Permalink
Merge branch 'feature/field_api_enhancement' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
RamezIssac committed Dec 24, 2020
2 parents 4b78ee1 + fddccc0 commit 6a99d23
Show file tree
Hide file tree
Showing 11 changed files with 114 additions and 36 deletions.
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ python:
- "3.8"

env:
- DJANGO=django==2.2.12
- DJANGO=django==3.0.5
- DJANGO=django==2.2.17
- DJANGO=django==3.0.11
- DJANGO=django==3.1.4


# command to install dependencies
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

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

## [0.5.3]

- Enhanced Field prepare flow
- Add traversing for group_by
- Allowed tests to run specific tests instead of the whole suit
- Enhanced templates structure for easier override/customization

## [0.5.2]

- Enhanced Time Series Plot total HighChart by accenting the categories
Expand Down
15 changes: 14 additions & 1 deletion runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,28 @@
import os
import sys

import argparse
import django
from django.conf import settings
from django.test.utils import get_runner

if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run the Django Slick Reporting test suite.")
parser.add_argument(
'modules', nargs='*', metavar='module',
help='Optional path(s) to test modules; e.g. "i18n" or '
'"i18n.tests.TranslationTests.test_lazy_objects".',
)
options = parser.parse_args()

options.modules = [os.path.normpath(labels) for labels in options.modules]


os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.test_settings'
django.setup()
TestRunner = get_runner(settings)
test_runner = TestRunner()
failures = test_runner.run_tests(["tests"])
failures = test_runner.run_tests(options.modules)
# failures = test_runner.run_tests(["tests"])
sys.exit(bool(failures))

29 changes: 20 additions & 9 deletions slick_reporting/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ def apply_aggregation(self, queryset, group_by=''):
queryset = queryset.aggregate(annotation)
return queryset

def init_preparation(self, q_filters=None, kwargs_filters=None, **kwargs):
"""
Called by the generator to preparet he calculation of this field + it's requirements
:param q_filters:
:param kwargs_filters:
:param kwargs:
:return:
"""
kwargs_filters = kwargs_filters or {}

dep_values = self._prepare_dependencies(q_filters, kwargs_filters.copy())

debit_results, credit_results = self.prepare(q_filters, kwargs_filters, **kwargs)
self._cache = debit_results, credit_results, dep_values

def prepare(self, q_filters=None, kwargs_filters=None, **kwargs):
"""
This is the first hook where you can customize the calculation away from the Django Query aggregation method
Expand All @@ -122,10 +137,6 @@ def prepare(self, q_filters=None, kwargs_filters=None, **kwargs):
:param kwargs:
:return:
"""
kwargs_filters = kwargs_filters or {}

dep_values = self._prepare_dependencies(q_filters, kwargs_filters.copy())

queryset = self.get_queryset()
if q_filters:
queryset = queryset.filter(*q_filters)
Expand All @@ -148,8 +159,7 @@ def prepare(self, q_filters=None, kwargs_filters=None, **kwargs):

credit_results = self.apply_aggregation(queryset, self.group_by)

self._cache = debit_results, credit_results, dep_values
return debit_results, credit_results, dep_values
return debit_results, credit_results

def get_queryset(self):
queryset = self.report_model.objects
Expand All @@ -167,15 +177,16 @@ def _prepare_dependencies(self, q_filters=None, extra_filters=None, ):
for dep_class in self._require_classes:
dep = dep_class(self.plus_side_q, self.minus_side_q, self.report_model,
date_field=self.date_field, group_by=self.group_by)
values[dep.name] = {'results': dep.prepare(q_filters, extra_filters),
values[dep.name] = {'results': dep.init_preparation(q_filters, extra_filters),
'instance': dep}
return values

def resolve(self, current_obj):
def resolve(self, current_obj, current_row=None):
'''
Reponsible for getting the exact data from the prepared value
:param cached: the returned data from prepare
:param current_obj:
:param current_obj: he value of group by id
:param current_row: the row in iteration
:return: a solid number or value
'''
cached = self._cache
Expand Down
28 changes: 16 additions & 12 deletions slick_reporting/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,20 @@ def __init__(self, report_model=None, main_queryset=None, start_date=None, end_d
# todo validate columns is not empty (if no time series / cross tab)

if self.group_by:
group_by_split = self.group_by.split('__')
search_field = group_by_split[0]
try:
self.group_by_field = [x for x in self.report_model._meta.fields if x.name == self.group_by][0]
self.group_by_field = [x for x in self.report_model._meta.fields if x.name == search_field][0]

except IndexError:
raise ImproperlyConfigured(
f'Can not find group_by field:{self.group_by} in report_model {self.report_model} ')
self.focus_field_as_key = self.group_by_field
if '__' not in self.group_by:
self.group_by_field_attname = self.group_by_field.attname
else:
self.group_by_field_attname = self.group_by

self.focus_field_as_key = self.group_by
self.group_by_field_attname = self.group_by_field.attname
else:
self.focus_field_as_key = None
self.group_by_field_attname = None
Expand Down Expand Up @@ -236,17 +242,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()
if type(self.group_by_field) is ForeignKey and '__' not in self.group_by:
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)
self.main_queryset = self.main_queryset.distinct().values(self.group_by_field_attname)
else:
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()

def _apply_queryset_options(self, query, fields=None):
Expand Down Expand Up @@ -322,8 +327,7 @@ def _prepare_report_dependencies(self):
if window == 'crosstab':
q_filters = self._construct_crosstab_filter(col_data)

# print(f'preparing {report_class} for {window}')
report_class.prepare(q_filters, date_filter)
report_class.init_preparation(q_filters, date_filter)
self.report_fields_classes[name] = report_class

def _get_record_data(self, obj, columns):
Expand Down Expand Up @@ -359,7 +363,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)
value = computation_class.resolve(group_by_val, data)
if self.swap_sign: value = -value
data[name] = value

Expand Down Expand Up @@ -402,7 +406,7 @@ def check_columns(cls, columns, group_by, report_model, ):
"""
group_by_model = None
if group_by:
group_by_field = [x for x in report_model._meta.fields if x.name == group_by][0]
group_by_field = [x for x in report_model._meta.fields if x.name == group_by.split('__')[0]][0]
if group_by_field.is_relation:
group_by_model = group_by_field.related_model
else:
Expand Down Expand Up @@ -450,7 +454,7 @@ def check_columns(cls, columns, group_by, report_model, ):
}
else:
# A database field
model_to_use = group_by_model if group_by else report_model
model_to_use = group_by_model if group_by and '__' not in group_by else report_model
try:
if '__' in col:
# A traversing link order__client__email
Expand Down
6 changes: 3 additions & 3 deletions slick_reporting/templates/slick_reporting/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@
integrity="JnbsSLBmv2/R0fUmF2XYIcAEMPHEAO51Gitn9IjL4l89uFTIgtLF1+jqIqqd9FSk"
crossorigin="anonymous"></script>

<script src="{% static 'slick_reporting/main.js' %}"></script>
<script src="{% static 'slick_reporting/ra.chartsjs.js' %}"></script>
<script src="{% static 'slick_reporting/ra.highchart.js' %}"></script>
<script>$(document).ready(function () {
$('select').select2();
})</script>
{% block js_script %}
{% endblock %}
</body>
Expand Down
6 changes: 4 additions & 2 deletions slick_reporting/templates/slick_reporting/simple_report.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends 'slick_reporting/base.html' %}
{% load crispy_forms_tags i18n slick_reporting_tags %}
{% load crispy_forms_tags i18n slick_reporting_tags static %}


{% block content %}
Expand Down Expand Up @@ -53,6 +53,9 @@ <h4 class="py-5">Results</h4>

{% endblock %}
{% block js_script %}
<script src="{% static 'slick_reporting/main.js' %}"></script>
<script src="{% static 'slick_reporting/ra.chartsjs.js' %}"></script>
<script src="{% static 'slick_reporting/ra.highchart.js' %}"></script>


<script>
Expand Down Expand Up @@ -167,7 +170,6 @@ <h4 class="py-5">Results</h4>

}

$('select').select2();
$('table').DataTable();
$('.nav-charts').find('a:first').trigger('click');

Expand Down
7 changes: 7 additions & 0 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@


class Product(models.Model):
CATEGORY_CHOICES = (
('tiny', 'tiny'),
('small', 'small'),
('medium', 'medium'),
('big', 'big'),
)
slug = models.CharField(max_length=200, verbose_name=_('Slug'))
name = models.CharField(max_length=200, verbose_name=_('Name'))
sku = models.CharField(max_length=200, default='', blank=True)
category = models.CharField(max_length=10, choices=CATEGORY_CHOICES)
notes = models.TextField()

class Meta:
Expand Down
33 changes: 31 additions & 2 deletions tests/test_generator.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from datetime import datetime

import pytz
from django.db.models import Sum
from django.test import TestCase

from slick_reporting.fields import SlickReportField
from slick_reporting.generator import ReportGenerator
from slick_reporting.helpers import get_foreign_keys
from .models import OrderLine

from .report_generators import GeneratorWithAttrAsColumn, CrosstabOnClient, GenericGenerator, GroupByCharField, \
TimeSeriesCustomDates

from .tests import BaseTestData
from .tests import BaseTestData, year
from .models import SimpleSales


Expand Down Expand Up @@ -44,7 +47,16 @@ def test_get_crosstab_parsed_columns(self):
self.assertTrue('is_summable' in col.keys(), col)


class GeneratorReportStructureTest(TestCase):
class GeneratorReportStructureTest(BaseTestData, TestCase):

@classmethod
def setUpTestData(cls):
super().setUpTestData()
SimpleSales.objects.create(
doc_date=datetime(year, 3, 2), client=cls.client3,
product=cls.product3, quantity=30, price=10)


def test_time_series_columns_inclusion(self):
x = ReportGenerator(OrderLine, date_field='order__date_placed', group_by='client',
columns=['name', '__time_series__'],
Expand Down Expand Up @@ -115,6 +127,7 @@ def test_attr_as_column(self):

self.assertEqual(columns_data[0]['verbose_name'], 'get_data_verbose_name')
data = report.get_report_data()
self.assertIsNot(data, [])
# todo

def test_improper_group_by(self):
Expand All @@ -138,6 +151,22 @@ def test_gather_dependencies_for_time_series(self):

self.assertTrue(report._report_fields_dependencies)

def test_group_by_traverse(self):
report = ReportGenerator(report_model=SimpleSales, group_by='product__category',
columns=['product__category', SlickReportField.create(Sum, 'value'), '__total__'],
# time_series_pattern='monthly',
date_field='doc_date',
# time_series_columns=['__debit__', '__credit__', '__balance__', '__total__']
)

self.assertTrue(report._report_fields_dependencies)
data = report.get_report_data()
# import pdb;
# pdb.set_trace()
self.assertNotEqual(data, [])
self.assertEqual(data[0]['product__category'], 'small')
self.assertEqual(data[1]['product__category'], 'big')

def test_db_field_column_verbose_name(self):
report = GenericGenerator()
field_list = report.get_list_display_columns()
Expand Down
7 changes: 6 additions & 1 deletion tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
'TEST': {
'NAME': 'tst_db.sqlite3',
'MIGRATE': False
},
},
}

Expand All @@ -25,7 +29,6 @@
'django.contrib.sessions',
'django.contrib.messages',


# 'django.contrib.admin.apps.SimpleAdminConfig',
'django.contrib.staticfiles',
'slick_reporting',
Expand Down Expand Up @@ -53,3 +56,5 @@
},
]
STATIC_URL = '/static/'

MIGRATION_MODULES = {'contenttypes': None, 'auth': None}
7 changes: 3 additions & 4 deletions tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ def setUpTestData(cls):
cls.client3 = Client.objects.create(name='Client 3')
cls.clientIdle = Client.objects.create(name='Client Idle')

cls.product1 = Product.objects.create(name='Product 1')
cls.product2 = Product.objects.create(name='Product 2')
cls.product3 = Product.objects.create(name='Product 3')
cls.product1 = Product.objects.create(name='Product 1', category='small')
cls.product2 = Product.objects.create(name='Product 2', category='medium')
cls.product3 = Product.objects.create(name='Product 3', category='big')

SimpleSales.objects.create(
doc_date=datetime.datetime(year, 1, 2), client=cls.client1,
Expand Down Expand Up @@ -126,7 +126,6 @@ def test_productclientsalesmatrix(self):
self.assertEqual(data[0]['__total__CT%s' % self.client2.pk], 600)
self.assertEqual(data[0]['__total__CT----'], 900)


def test_show_empty_records(self):
report = report_generators.ClientTotalBalance()
data = report.get_report_data()
Expand Down

0 comments on commit 6a99d23

Please sign in to comment.