From aa45c08ad50fb4e0b04784fa0df6d139cbfcfe6d Mon Sep 17 00:00:00 2001 From: Ramez Ashraf Date: Mon, 30 Jan 2023 19:48:34 +0200 Subject: [PATCH 1/4] Fix using a method as a column in a ReportGenerator --- .travis.yml | 2 ++ slick_reporting/generator.py | 7 +++++-- tests/report_generators.py | 17 +++++++++++------ tests/test_generator.py | 18 +++--------------- tests/tests.py | 8 +++++--- 5 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2456eaa..3c879fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ language: python python: - "3.8" - "3.9" + - "3.10" + - "3.10" env: - DJANGO=django==3.2.15 diff --git a/slick_reporting/generator.py b/slick_reporting/generator.py index 4c169b3..40f5934 100644 --- a/slick_reporting/generator.py +++ b/slick_reporting/generator.py @@ -369,7 +369,10 @@ def _get_record_data(self, obj, columns): name = col_data['name'] - if (col_data.get('source', '') == 'magic_field' and self.group_by) or ( + if col_data.get('source', '') == 'attribute_field': + data[name] = col_data['ref'](self, obj, data) + + elif (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: @@ -457,7 +460,7 @@ def check_columns(cls, columns, group_by, report_model, ): if attribute_field: col_data = {'name': col, 'verbose_name': getattr(attribute_field, 'verbose_name', col), - # 'type': 'method', + 'source': 'attribute_field', 'ref': attribute_field, 'type': 'text' } diff --git a/tests/report_generators.py b/tests/report_generators.py index a48d527..4463c07 100644 --- a/tests/report_generators.py +++ b/tests/report_generators.py @@ -28,7 +28,7 @@ class GeneratorWithAttrAsColumn(GenericGenerator): def get_data(self, obj): return '' - get_data.verbose_name = 'get_data_verbose_name' + get_data.verbose_name = 'My Verbose Name' class CrosstabOnClient(GenericGenerator): @@ -79,7 +79,16 @@ class ProductTotalSales(ReportGenerator): report_model = SimpleSales date_field = 'doc_date' group_by = 'product' - columns = ['slug', 'name', '__balance__', '__balance_quantity__'] + columns = ['slug', 'name', '__balance__', '__balance_quantity__', 'get_object_sku', 'average_value'] + + + def get_object_sku(self, obj, data): + return obj['sku'].upper() + get_object_sku.verbose_name = 'SKU ALL CAPS' + + def average_value(self, obj, data): + return data['__balance__'] / data['__balance_quantity__'] + average_value.verbose_name = 'Average Value' class ProductTotalSalesProductWithCustomID(ReportGenerator): @@ -248,10 +257,6 @@ class ProductClientSalesMatrix2(ReportGenerator): crosstab_columns = [SlickReportField.create(Sum, 'value', name='value__sum', verbose_name=_('Sales'))] -class GeneratorClassWithAttrsAs(ReportGenerator): - columns = ['get_icon', 'slug', 'name'] - - class ClientTotalBalancesWithShowEmptyFalse(ClientTotalBalance): report_slug = None default_order_by = '-__balance__' diff --git a/tests/test_generator.py b/tests/test_generator.py index 2c8333d..69ef60c 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -115,7 +115,6 @@ def not_known_pattern(): self.assertRaises(Exception, not_known_pattern) - def test_time_series_custom_pattern(self): # report = ReportGenerator(OrderLine, date_field='order__date_placed', group_by='client', # columns=['name', '__time_series__'], @@ -140,11 +139,7 @@ def test_attr_as_column(self): report = GeneratorWithAttrAsColumn() columns_data = report.get_list_display_columns() self.assertEqual(len(columns_data), 3) - - self.assertEqual(columns_data[0]['verbose_name'], 'get_data_verbose_name') - data = report.get_report_data() - self.assertIsNot(data, []) - # todo + self.assertEqual(columns_data[0]['verbose_name'], 'My Verbose Name') def test_improper_group_by(self): def load(): @@ -177,11 +172,6 @@ def load(): self.assertRaises(Exception, load) - def _test_attr_called(self): - report = GeneratorWithAttrAsColumn() - data = report.get_report_data() - # self.assertEqual(len(report.get_list_display_columns()), 3) - def test_gather_dependencies_for_time_series(self): report = ReportGenerator(report_model=SimpleSales, group_by='client', columns=['slug', 'name'], @@ -202,15 +192,14 @@ def test_group_by_traverse(self): 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_group_by_and_foreign_key_field(self): report = ReportGenerator(report_model=SimpleSales, group_by='client', - columns=['name', 'contact_id', 'contact__address', SlickReportField.create(Sum, 'value'), '__total__'], + columns=['name', 'contact_id', 'contact__address', + SlickReportField.create(Sum, 'value'), '__total__'], # time_series_pattern='monthly', date_field='doc_date', # time_series_columns=['__debit__', '__credit__', '__balance__', '__total__'] @@ -236,7 +225,6 @@ def test_group_by_and_foreign_key_field(self): self.assertEqual(data[1]['contact__address'], 'Street 2') self.assertEqual(data[2]['contact__address'], 'Street 3') - def test_db_field_column_verbose_name(self): report = GenericGenerator() field_list = report.get_list_display_columns() diff --git a/tests/tests.py b/tests/tests.py index bf7d4a5..c26aa45 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -46,9 +46,9 @@ def setUpTestData(cls): cls.client3.save() cls.clientIdle = Client.objects.create(name='Client Idle') - 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') + cls.product1 = Product.objects.create(name='Product 1', category='small', sku='a1b1') + cls.product2 = Product.objects.create(name='Product 2', category='medium', sku='a2b2') + cls.product3 = Product.objects.create(name='Product 3', category='big', sku='3333') cls.product_w_custom_id1 = ProductCustomID.objects.create(name='Product 1', category='small') cls.product_w_custom_id2 = ProductCustomID.objects.create(name='Product 2', category='medium') @@ -143,6 +143,8 @@ def test_product_total_sales_product_custom_id(self): report = report_generators.ProductTotalSales() data = report.get_report_data() self.assertEqual(data[0]['__balance__'], 1800) + self.assertEqual(data[0]['get_object_sku'], 'A1B1') + self.assertEqual(data[0]['average_value'], data[0]['__balance__']/data[0]['__balance_quantity__']) def test_product_total_sales_with_percentage(self): report = report_generators.ProductTotalSalesWithPercentage() From 04be7c91b01fed30e134ff44115781dccf78f563 Mon Sep 17 00:00:00 2001 From: Ramez Ashraf Date: Mon, 30 Jan 2023 21:09:32 +0200 Subject: [PATCH 2/4] Use correct model when traversing on group by --- CHANGELOG.md | 6 ++++++ slick_reporting/generator.py | 14 +++++++++----- tests/models.py | 6 ++++++ tests/test_generator.py | 30 ++++++++++++++++++++++++++++++ tests/tests.py | 10 ++++++---- 5 files changed, 57 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c832b9..90f0258 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## [0.6.6] + +- Now a method on a generator can be effectively used as column +- Use correct model when traversing on group by + + ## [0.6.5] - Fix Issue with group_by field pointing to model with custom primary key Issue #58 diff --git a/slick_reporting/generator.py b/slick_reporting/generator.py index 40f5934..411a692 100644 --- a/slick_reporting/generator.py +++ b/slick_reporting/generator.py @@ -198,12 +198,10 @@ 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.get_fields() if x.name == search_field][0] + self.group_by_field = get_field_from_query_text(self.group_by, self.report_model) - except IndexError: + except (IndexError, AttributeError): raise ImproperlyConfigured( f'Can not find group_by field:{self.group_by} in report_model {self.report_model} ') if '__' not in self.group_by: @@ -245,7 +243,7 @@ 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 and '__' not in self.group_by: + if type(self.group_by_field) is ForeignKey: ids = self.main_queryset.values_list(self.group_by_field_attname).distinct() # uses the same logic that is in Django's query.py when fields is empty in values() call concrete_fields = [f.name for f in self.group_by_field.related_model._meta.concrete_fields] @@ -423,6 +421,7 @@ def check_columns(cls, columns, group_by, report_model, ): :param report_model: the report model :return: List of dict, each dict contains relevant data to the respective field in `columns` """ + group_by_field = '' group_by_model = None if group_by: try: @@ -476,6 +475,11 @@ def check_columns(cls, columns, group_by, report_model, ): else: # A database field model_to_use = group_by_model if group_by and '__' not in group_by else report_model + group_by_str = str(group_by) + if '__' in group_by_str: + related_model = get_field_from_query_text(group_by, model_to_use).related_model + model_to_use = related_model if related_model else model_to_use + try: if '__' in col: # A traversing link order__client__email diff --git a/tests/models.py b/tests/models.py index a36c11b..a10f4ad 100644 --- a/tests/models.py +++ b/tests/models.py @@ -42,8 +42,14 @@ class Meta: verbose_name_plural = _('Products') +class Agent(models.Model): + name = models.CharField(max_length=200, verbose_name=_('Name')) + + class Contact(models.Model): address = models.CharField(max_length=200, verbose_name=_('Name')) + po_box = models.CharField(max_length=200, verbose_name=_('po_box'), null=True, blank=True) + agent = models.ForeignKey(Agent, on_delete=models.CASCADE) class Client(models.Model): diff --git a/tests/test_generator.py b/tests/test_generator.py index 69ef60c..1f8676a 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -225,6 +225,36 @@ def test_group_by_and_foreign_key_field(self): self.assertEqual(data[1]['contact__address'], 'Street 2') self.assertEqual(data[2]['contact__address'], 'Street 3') + def test_traversing_group_by_and_foreign_key_field(self): + report = ReportGenerator(report_model=SimpleSales, group_by='client__contact', + columns=[ + 'po_box', 'address', 'agent__name', + 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() + self.assertNotEqual(data, []) + # self.assertTrue(False) + self.assertEqual(data[0]['address'], 'Street 1') + self.assertEqual(data[1]['address'], 'Street 2') + self.assertEqual(data[1]['agent__name'], 'John') + self.assertEqual(data[2]['agent__name'], 'Frank') + + def test_traversing_group_by_sanity(self): + report = ReportGenerator(report_model=SimpleSales, group_by='client__contact__agent', + columns=['name', SlickReportField.create(Sum, 'value'), '__total__'], + date_field='doc_date', ) + + self.assertTrue(report._report_fields_dependencies) + data = report.get_report_data() + self.assertNotEqual(data, []) + # self.assertTrue(False) + self.assertEqual(len(data), 2) + def test_db_field_column_verbose_name(self): report = GenericGenerator() field_list = report.get_list_display_columns() diff --git a/tests/tests.py b/tests/tests.py index c26aa45..246dec8 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -13,7 +13,7 @@ GroupByCharFieldPlusTimeSeries, TimeSeriesWithOutGroupBy from . import report_generators from .models import Client, Contact, Product, SimpleSales, UserJoined, SalesWithFlag, ComplexSales, TaxCode, \ - ProductCustomID, SalesProductWithCustomID + ProductCustomID, SalesProductWithCustomID, Agent from .views import SlickReportView User = get_user_model() @@ -35,14 +35,16 @@ def setUpTestData(cls): password='password') cls.user = user cls.limited_user = limited_user + agent = Agent.objects.create(name='John') + agent2 = Agent.objects.create(name='Frank') cls.client1 = Client.objects.create(name='Client 1') - cls.client1.contact = Contact.objects.create(address='Street 1') + cls.client1.contact = Contact.objects.create(address='Street 1', agent=agent) cls.client1.save() cls.client2 = Client.objects.create(name='Client 2') - cls.client2.contact = Contact.objects.create(address='Street 2') + cls.client2.contact = Contact.objects.create(address='Street 2', agent=agent) cls.client2.save() cls.client3 = Client.objects.create(name='Client 3') - cls.client3.contact = Contact.objects.create(address='Street 3') + cls.client3.contact = Contact.objects.create(address='Street 3' , agent=agent2) cls.client3.save() cls.clientIdle = Client.objects.create(name='Client Idle') From b343f5412ff40275665d6dec10ea213ec55d8a07 Mon Sep 17 00:00:00 2001 From: Ramez Ashraf Date: Mon, 30 Jan 2023 21:17:20 +0200 Subject: [PATCH 3/4] remove duplicated line --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3c879fa..7b124aa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,6 @@ python: - "3.8" - "3.9" - "3.10" - - "3.10" env: - DJANGO=django==3.2.15 From 28a997a72ecc7aa750a5bbba2c6562ba6defc97b Mon Sep 17 00:00:00 2001 From: Ramez Ashraf Date: Fri, 10 Feb 2023 09:31:33 +0200 Subject: [PATCH 4/4] Version bump --- docs/source/conf.py | 2 +- slick_reporting/__init__.py | 4 ++-- tests/test_generator.py | 11 +++-------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 3462e81..605721c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -27,7 +27,7 @@ master_doc = 'index' # The full version, including alpha/beta/rc tags -release = '0.6.5' +release = '0.6.6' # -- General configuration --------------------------------------------------- diff --git a/slick_reporting/__init__.py b/slick_reporting/__init__.py index 1c1030f..5c250d2 100644 --- a/slick_reporting/__init__.py +++ b/slick_reporting/__init__.py @@ -1,5 +1,5 @@ default_app_config = 'slick_reporting.apps.ReportAppConfig' -VERSION = (0, 6, 5) +VERSION = (0, 6, 6) -__version__ = '0.6.5' +__version__ = '0.6.6' diff --git a/tests/test_generator.py b/tests/test_generator.py index 1f8676a..438544c 100644 --- a/tests/test_generator.py +++ b/tests/test_generator.py @@ -227,13 +227,9 @@ def test_group_by_and_foreign_key_field(self): def test_traversing_group_by_and_foreign_key_field(self): report = ReportGenerator(report_model=SimpleSales, group_by='client__contact', - columns=[ - 'po_box', 'address', 'agent__name', - SlickReportField.create(Sum, 'value'), '__total__'], - # time_series_pattern='monthly', - date_field='doc_date', - # time_series_columns=['__debit__', '__credit__', '__balance__', '__total__'] - ) + columns=['po_box', 'address', 'agent__name', + SlickReportField.create(Sum, 'value'), '__total__'], + date_field='doc_date') self.assertTrue(report._report_fields_dependencies) data = report.get_report_data() @@ -252,7 +248,6 @@ def test_traversing_group_by_sanity(self): self.assertTrue(report._report_fields_dependencies) data = report.get_report_data() self.assertNotEqual(data, []) - # self.assertTrue(False) self.assertEqual(len(data), 2) def test_db_field_column_verbose_name(self):