diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ee0348..1f55a4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog All notable changes to this project will be documented in this file. +## [0.4.0] - 2020-11-24 [BREAKING] + +- Renamed `SampleReportView` to `SlickReportView` +- Renamed `BaseReportField` to `SlickReportField` +- Added `SlickReportViewBase` leaving sanity checks for the `SlickReportView` + ## [0.3.0] - 2020-11-23 - Add Sanity checks against incorrect entries in columns or date_field diff --git a/README.rst b/README.rst index 6d5f1a5..bb02b58 100644 --- a/README.rst +++ b/README.rst @@ -55,17 +55,17 @@ You can simply use a code like this # in views.py from django.db.models import Sum - from slick_reporting.views import SampleReportView + from slick_reporting.views import SlickReportView from .models import MySalesItems - class TotalProductSales(SampleReportView): + class TotalProductSales(SlickReportView): report_model = MySalesItems date_field = 'date_placed' group_by = 'product' columns = ['title', - BaseReportField.create(Sum, 'quantity') , - BaseReportField.create(Sum, 'value', name='sum__value) ] + SlickReportField.create(Sum, 'quantity') , + SlickReportField.create(Sum, 'value', name='sum__value) ] chart_settings = [{ 'type': 'column', @@ -90,10 +90,10 @@ You can do a monthly time series : .. code-block:: python # in views.py - from slick_reporting.views import SampleReportView + from slick_reporting.views import SlickReportView from .models import MySalesItems - class MonthlyProductSales(SampleReportView): + class MonthlyProductSales(SlickReportView): report_model = MySalesItems date_field = 'date_placed' group_by = 'product' @@ -124,7 +124,7 @@ This would return a table looking something like this: **On a low level** -You can interact with the `ReportGenerator` using same syntax as used with the `SampleReportView` . +You can interact with the `ReportGenerator` using same syntax as used with the `SlickReportView` . .. code-block:: python diff --git a/docs/source/computation_field.rst b/docs/source/computation_field.rst index 587a9a7..a7b541d 100644 --- a/docs/source/computation_field.rst +++ b/docs/source/computation_field.rst @@ -16,12 +16,12 @@ Let's see how it's written in `slick_reporting.fields` .. code-block:: python - from slick_reporting.fields import BaseReportField + from slick_reporting.fields import SlickReportField from slick_reporting.decorators import report_field_register @report_field_register - class TotalQTYReportField(BaseReportField): + class TotalQTYReportField(SlickReportField): # The name to use when using this field in the generator name = '__total_quantity__' @@ -43,7 +43,7 @@ If you want AVG to the field `price` then the ReportField would look like this from django.db.models import Avg @report_field_register - class TotalQTYReportField(BaseReportField): + class TotalQTYReportField(SlickReportField): name = '__avg_price__' calculation_field = 'price' @@ -91,10 +91,10 @@ Two side calculation # Document how a single field can be computed like a debit and credit. -BaseReportField API +SlickReportField API ------------------- -.. autoclass:: slick_reporting.fields.BaseReportField +.. autoclass:: slick_reporting.fields.SlickReportField .. autoattribute:: name .. autoattribute:: calculation_field diff --git a/docs/source/customization.rst b/docs/source/customization.rst index eeec0b6..d4cda8d 100644 --- a/docs/source/customization.rst +++ b/docs/source/customization.rst @@ -15,7 +15,7 @@ then for each report_model record, the ReportGenerator again asks each Computati 1. View Customization --------------------- -The SampleReportView is a wrapper around FormView. It +The SlickReportView is a wrapper around FormView. It 1. Passes the needed reporting attributes to the ReportGenerator and get the results into the context. 2. Work on GET as well as on POST. diff --git a/docs/source/index.rst b/docs/source/index.rst index de52d57..a5e169d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -19,15 +19,15 @@ To install django-slick-reporting: Quickstart ---------- -You can start by using ``SampleReportView`` which is a subclass of ``django.views.generic.FormView`` +You can start by using ``SlickReportView`` which is a subclass of ``django.views.generic.FormView`` .. code-block:: python # in views.py - from slick_reporting.views import SampleReportView + from slick_reporting.views import SlickReportView from .models import MySalesItems - class MonthlyProductSales(SampleReportView): + class MonthlyProductSales(SlickReportView): # The model where you have the data report_model = MySalesItems diff --git a/docs/source/tour.rst b/docs/source/tour.rst index 7d82283..81febf3 100644 --- a/docs/source/tour.rst +++ b/docs/source/tour.rst @@ -43,16 +43,16 @@ This can be written like this .. code-block:: python # in your views.py - from slick_reporting.views import SampleReportView + from slick_reporting.views import SlickReportView - class TransactionsReport(SampleReportView): + class TransactionsReport(SlickReportView): report_model = MySalesItem columns = ['order_date', 'product__name' , 'client__name', 'quantity', 'price', 'value] # in your urls.py path('to-report', TransactionsReport.as_view()) -Worth Noting here that the ``SampleReportView`` calls a form generator which return a form containing +Worth Noting here that the ``SlickReportView`` calls a form generator which return a form containing all foreign keys in the report_model + start and end date filter. 2. Group By report @@ -72,7 +72,7 @@ which can be written like this: .. code-block:: python - class TotalQuanAndValueReport(SampleReportView): + class TotalQuanAndValueReport(SlickReportView): report_model = MySalesItem group_by = 'product' columns = ['name', '__total_quantity__', '__total__' ] @@ -99,7 +99,7 @@ can be written like this .. code-block:: python - class TotalQuantityMonthly(SampleReportView): + class TotalQuantityMonthly(SlickReportView): report_model = MySalesItem group_by = 'product' columns = ['name', 'sku'] @@ -128,7 +128,7 @@ Which can be written like this .. code-block:: python - class CrosstabProductClientValue(SampleReportView): + class CrosstabProductClientValue(SlickReportView): report_model = MySalesItem group_by = 'product' columns = ['name', 'sku'] @@ -150,11 +150,11 @@ Which can be written like this Charts ------- -To create a report we need to a dictionary to a ``chart_settings`` to the SampleReportView. +To create a report we need to a dictionary to a ``chart_settings`` to the SlickReportView. .. code-block:: python - class MonthlySalesReport(SampleReportView): + class MonthlySalesReport(SlickReportView): # .... charts_settings = [{ diff --git a/slick_reporting/__init__.py b/slick_reporting/__init__.py index 781845d..1823624 100644 --- a/slick_reporting/__init__.py +++ b/slick_reporting/__init__.py @@ -1,6 +1,6 @@ default_app_config = 'slick_reporting.apps.ReportAppConfig' -VERSION = (0, 3, 0) +VERSION = (0, 4, 0) -__version__ = '0.3.0' +__version__ = '0.4.0' diff --git a/slick_reporting/decorators.py b/slick_reporting/decorators.py index fe3ab16..c4e3f8d 100644 --- a/slick_reporting/decorators.py +++ b/slick_reporting/decorators.py @@ -10,12 +10,12 @@ class AuthorAdmin(admin.ModelAdmin): A kwarg of `site` can be passed as the admin site, otherwise the default admin site will be used. """ - from .fields import BaseReportField + from .fields import SlickReportField from .registry import field_registry def _model_admin_wrapper(admin_class): - if not issubclass(admin_class, BaseReportField): - raise ValueError('Wrapped class must subclass BaseReportField.') + if not issubclass(admin_class, SlickReportField): + raise ValueError('Wrapped class must subclass SlickReportField.') field_registry.register(report_field) diff --git a/slick_reporting/fields.py b/slick_reporting/fields.py index d49b64b..d5e5555 100644 --- a/slick_reporting/fields.py +++ b/slick_reporting/fields.py @@ -8,7 +8,7 @@ from .registry import field_registry -class BaseReportField(object): +class SlickReportField(object): """ Computation field responsible for making the calculation unit """ @@ -63,7 +63,7 @@ def create(cls, method, field, name=None, verbose_name=None, is_summable=True): assert name not in cls._field_registry.get_all_report_fields_names() verbose_name = verbose_name or f'{method.name} {field}' - report_klass = type(f'ReportField_{name}', (BaseReportField,), { + report_klass = type(f'ReportField_{name}', (SlickReportField,), { 'name': name, 'verbose_name': verbose_name, 'calculation_field': field, @@ -76,7 +76,7 @@ def __init__(self, plus_side_q=None, minus_side_q=None, report_model=None, qs=None, calculation_field=None, calculation_method=None, date_field='', group_by=None): - super(BaseReportField, self).__init__() + super(SlickReportField, self).__init__() self.date_field = date_field self.report_model = self.report_model or report_model self.calculation_field = calculation_field if calculation_field else self.calculation_field @@ -296,7 +296,7 @@ def get_time_series_field_verbose_name(cls, date_period): return f'{cls.verbose_name} {date_period[0].strftime(dt_format)} - {date_period[1].strftime(dt_format)}' -class FirstBalanceField(BaseReportField): +class FirstBalanceField(SlickReportField): name = '__fb__' verbose_name = _('first balance') @@ -312,7 +312,7 @@ def prepare(self, q_filters=None, extra_filters=None, **kwargs): field_registry.register(FirstBalanceField) -class TotalReportField(BaseReportField): +class TotalReportField(SlickReportField): name = '__total__' verbose_name = _('Sum of value') requires = ['__debit__', '__credit__'] @@ -321,7 +321,7 @@ class TotalReportField(BaseReportField): field_registry.register(TotalReportField) -class BalanceReportField(BaseReportField): +class BalanceReportField(SlickReportField): name = '__balance__' verbose_name = _('Cumulative Total') requires = ['__fb__'] @@ -337,7 +337,7 @@ def final_calculation(self, debit, credit, dep_dict): field_registry.register(BalanceReportField) -class CreditReportField(BaseReportField): +class CreditReportField(SlickReportField): name = '__credit__' verbose_name = _('Credit') @@ -348,7 +348,7 @@ def final_calculation(self, debit, credit, dep_dict): field_registry.register(CreditReportField) -class DebitReportField(BaseReportField): +class DebitReportField(SlickReportField): name = '__debit__' verbose_name = _('Debit') @@ -359,7 +359,7 @@ def final_calculation(self, debit, credit, dep_dict): field_registry.register(DebitReportField) -class TotalQTYReportField(BaseReportField): +class TotalQTYReportField(SlickReportField): name = '__total_quantity__' verbose_name = _('Total QTY') calculation_field = 'quantity' @@ -379,7 +379,7 @@ class FirstBalanceQTYReportField(FirstBalanceField): field_registry.register(FirstBalanceQTYReportField) -class BalanceQTYReportField(BaseReportField): +class BalanceQTYReportField(SlickReportField): name = '__balance_quantity__' verbose_name = _('Cumulative QTY') calculation_field = 'quantity' diff --git a/slick_reporting/generator.py b/slick_reporting/generator.py index e55503d..bd55327 100644 --- a/slick_reporting/generator.py +++ b/slick_reporting/generator.py @@ -7,7 +7,7 @@ from django.core.exceptions import ImproperlyConfigured, FieldDoesNotExist from django.db.models import Q -from .fields import BaseReportField +from .fields import SlickReportField from .helpers import get_field_from_query_text from .registry import field_registry @@ -266,7 +266,7 @@ def _construct_crosstab_filter(self, col_data): return filters def _prepare_report_dependencies(self): - from .fields import BaseReportField + from .fields import SlickReportField all_columns = ( ('normal', self._parsed_columns), ('time_series', self._time_series_parsed_columns), @@ -276,7 +276,7 @@ def _prepare_report_dependencies(self): for col_data in window_cols: klass = col_data['ref'] - if isclass(klass) and issubclass(klass, BaseReportField): + if isclass(klass) and issubclass(klass, SlickReportField): dependencies_names = klass.get_full_dependency_list() # check if any of this dependencies is on the report @@ -291,7 +291,7 @@ def _prepare_report_dependencies(self): name = col_data['name'] # if column has a dependency then skip it - if not (isclass(klass) and issubclass(klass, BaseReportField)): + if not (isclass(klass) and issubclass(klass, SlickReportField)): continue if self._report_fields_dependencies[window].get(name, False): continue @@ -389,7 +389,7 @@ def check_columns(cls, columns, group_by, report_model, ): if type(col) is str: attr = getattr(cls, col, None) - elif issubclass(col, BaseReportField): + elif issubclass(col, SlickReportField): magic_field_class = col try: @@ -491,7 +491,7 @@ def get_time_series_parsed_columns(self): if type(col) is str: magic_field_class = field_registry.get_field_by_name(col) - elif issubclass(col, BaseReportField): + elif issubclass(col, SlickReportField): magic_field_class = col _values.append({ @@ -576,7 +576,7 @@ def get_crosstab_parsed_columns(self): magic_field_class = None if type(col) is str: magic_field_class = field_registry.get_field_by_name(col) - elif issubclass(col, BaseReportField): + elif issubclass(col, SlickReportField): magic_field_class = col output_cols.append({ diff --git a/slick_reporting/views.py b/slick_reporting/views.py index 4383c65..a31a94c 100644 --- a/slick_reporting/views.py +++ b/slick_reporting/views.py @@ -12,7 +12,7 @@ from .generator import ReportGenerator -class SampleReportView(FormView): +class SlickReportViewBase(FormView): group_by = None columns = None @@ -223,6 +223,9 @@ def get_initial(self): 'end_date': SLICK_REPORTING_DEFAULT_END_DATE } + +class SlickReportView(SlickReportViewBase): + def __init_subclass__(cls) -> None: date_field = getattr(cls, 'date_field', '') if not date_field: diff --git a/tests/tests.py b/tests/tests.py index 4082796..9edd281 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -8,11 +8,11 @@ from django.utils.timezone import now from slick_reporting.generator import ReportGenerator -from slick_reporting.fields import BaseReportField, BalanceReportField +from slick_reporting.fields import SlickReportField, BalanceReportField from tests.report_generators import ClientTotalBalance from .models import Client, Product, SimpleSales, OrderLine from slick_reporting.registry import field_registry -from .views import SampleReportView +from .views import SlickReportView User = get_user_model() SUPER_LOGIN = dict(username='superlogin', password='password') @@ -260,7 +260,7 @@ def _test_column_names_are_always_strings(self): def test_error_on_missing_date_field(self): def test_function(): - class TotalClientSales(SampleReportView): + class TotalClientSales(SlickReportView): report_model = SimpleSales self.assertRaises(TypeError, test_function) @@ -276,7 +276,7 @@ def test_unregister(self): def test_registering_new(self): def register(): - class ReportFieldWDuplicatedName(BaseReportField): + class ReportFieldWDuplicatedName(SlickReportField): name = '__total_field__' calculation_field = 'field' @@ -287,7 +287,7 @@ class ReportFieldWDuplicatedName(BaseReportField): def test_already_registered(self): def register(): - class ReportFieldWDuplicatedName(BaseReportField): + class ReportFieldWDuplicatedName(SlickReportField): name = '__total__' field_registry.register(ReportFieldWDuplicatedName) @@ -313,10 +313,10 @@ def register(): def test_creating_a_report_field_on_the_fly(self): from django.db.models import Sum - name = BaseReportField.create(Sum, 'value', '__sum_of_value__') + name = SlickReportField.create(Sum, 'value', '__sum_of_value__') self.assertNotIn(name, field_registry.get_all_report_fields_names()) def test_creating_a_report_field_on_the_fly_wo_name(self): from django.db.models import Sum - name = BaseReportField.create(Sum, 'value') + name = SlickReportField.create(Sum, 'value') self.assertNotIn(name, field_registry.get_all_report_fields_names()) diff --git a/tests/views.py b/tests/views.py index c0f7de8..440921a 100644 --- a/tests/views.py +++ b/tests/views.py @@ -1,8 +1,8 @@ -from slick_reporting.views import SampleReportView +from slick_reporting.views import SlickReportView from .models import SimpleSales -class MonthlyProductSales(SampleReportView): +class MonthlyProductSales(SlickReportView): report_model = SimpleSales date_field = 'doc_date' group_by = 'client' @@ -11,7 +11,7 @@ class MonthlyProductSales(SampleReportView): time_series_columns = ['__total__', '__balance__'] -class ProductClientSalesMatrix(SampleReportView): +class ProductClientSalesMatrix(SlickReportView): report_title = 'awesome report title' report_model = SimpleSales date_field = 'doc_date'