Showing preview only (556K chars total). Download the full file or copy to clipboard to get everything.
Repository: RamezIssac/django-slick-reporting
Branch: develop
Commit: 513651ddf558
Files: 121
Total size: 520.2 KB
Directory structure:
gitextract_vuqer0hb/
├── .github/
│ └── workflows/
│ ├── django.yml
│ └── release.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE.md
├── MANIFEST.in
├── README.rst
├── demo_proj/
│ ├── demo_app/
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── forms.py
│ │ ├── helpers.py
│ │ ├── management/
│ │ │ └── commands/
│ │ │ └── create_entries.py
│ │ ├── migrations/
│ │ │ ├── 0001_initial.py
│ │ │ ├── 0002_salestransaction_price_salestransaction_quantity.py
│ │ │ ├── 0003_product_category.py
│ │ │ ├── 0004_client_country_product_sku.py
│ │ │ ├── 0005_product_size.py
│ │ │ ├── 0006_productcategory_remove_product_category_and_more.py
│ │ │ ├── 0007_monthlysalessummary.py
│ │ │ └── __init__.py
│ │ ├── models.py
│ │ ├── reports.py
│ │ ├── templatetags/
│ │ │ ├── __init__.py
│ │ │ └── slick_reporting_demo_tags.py
│ │ ├── tests.py
│ │ └── views.py
│ ├── demo_proj/
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── manage.py
│ ├── requirements.txt
│ └── templates/
│ ├── base.html
│ ├── dashboard.html
│ ├── demo/
│ │ └── apex_report.html
│ ├── home.html
│ ├── menu.html
│ ├── slick_reporting/
│ │ ├── base.html
│ │ └── report_form.html
│ └── widget_template_with_pre.html
├── docs/
│ ├── requirements.txt
│ └── source/
│ ├── concept.rst
│ ├── conf.py
│ ├── howto/
│ │ ├── customize_frontend.rst
│ │ └── index.rst
│ ├── index.rst
│ ├── ref/
│ │ ├── computation_field.rst
│ │ ├── dynamic_model.rst
│ │ ├── index.rst
│ │ ├── report_generator.rst
│ │ ├── settings.rst
│ │ └── view_options.rst
│ ├── topics/
│ │ ├── charts.rst
│ │ ├── computation_field.rst
│ │ ├── crosstab_options.rst
│ │ ├── dynamic_model.rst
│ │ ├── exporting.rst
│ │ ├── filter_form.rst
│ │ ├── group_by_report.rst
│ │ ├── index.rst
│ │ ├── integrating_slick_reporting.rst
│ │ ├── list_report_options.rst
│ │ ├── pivot_report.rst
│ │ ├── structure.rst
│ │ ├── time_series_options.rst
│ │ └── widgets.rst
│ ├── tour.rst
│ └── tutorial.rst
├── pyproject.toml
├── requirements.txt
├── runtests.py
├── scripts/
│ └── extract_changelog.py
├── setup.cfg
├── setup.py
├── slick_reporting/
│ ├── __init__.py
│ ├── app_settings.py
│ ├── apps.py
│ ├── decorators.py
│ ├── dynamic_model.py
│ ├── fields.py
│ ├── form_factory.py
│ ├── forms.py
│ ├── generator.py
│ ├── helpers.py
│ ├── locale/
│ │ ├── ar/
│ │ │ └── LC_MESSAGES/
│ │ │ └── django.po
│ │ └── de/
│ │ └── LC_MESSAGES/
│ │ └── django.po
│ ├── registry.py
│ ├── static/
│ │ └── slick_reporting/
│ │ ├── slick_reporting.chartsjs.js
│ │ ├── slick_reporting.datatable.js
│ │ ├── slick_reporting.highchart.js
│ │ ├── slick_reporting.js
│ │ └── slick_reporting.report_loader.js
│ ├── templates/
│ │ └── slick_reporting/
│ │ ├── base.html
│ │ ├── js_resources.html
│ │ ├── print_report.html
│ │ ├── print_report_controls.html
│ │ ├── print_report_footer.html
│ │ ├── print_report_header.html
│ │ ├── report.html
│ │ ├── report_form.html
│ │ └── widget_template.html
│ ├── templatetags/
│ │ ├── __init__.py
│ │ └── slick_reporting_tags.py
│ └── views.py
└── tests/
├── __init__.py
├── models.py
├── report_generators.py
├── requirements.txt
├── settings.py
├── templates/
│ └── base.html
├── test_dynamic_model.py
├── test_generator.py
├── test_pivot_generator.py
├── tests.py
├── urls.py
└── views.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/django.yml
================================================
name: Django CI
on:
push:
branches: [ "develop", "master" ]
pull_request:
branches: [ "develop", "master" ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
max-parallel: 4
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r tests/requirements.txt
- name: Lint (ruff)
run: |
pip install ruff
ruff check --line-length 120 slick_reporting/
- name: Run tests
run: python runtests.py
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
environment: release
permissions:
contents: write # GitHub Release + push back to develop
id-token: write # PyPI OIDC trusted publishing
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history needed for the merge-back step
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r tests/requirements.txt
- name: Run tests
run: python runtests.py
- name: Install build tools
run: pip install build
- name: Build package
run: python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Extract release notes from CHANGELOG
id: changelog
run: |
TAG=${GITHUB_REF#refs/tags/v}
python scripts/extract_changelog.py "$TAG" > release_notes.md
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
body_path: release_notes.md
files: dist/*
- name: Notify demo server
env:
DEMO_WEBHOOK_URL: ${{ secrets.DEMO_WEBHOOK_URL }}
DEMO_WEBHOOK_SECRET: ${{ secrets.DEMO_WEBHOOK_SECRET }}
run: |
curl -fsS -X POST "$DEMO_WEBHOOK_URL" \
-H "Authorization: Bearer $DEMO_WEBHOOK_SECRET" \
-H "Content-Type: application/json" \
-d '{"version": "${{ steps.changelog.outputs.tag }}"}'
- name: Merge master back into develop
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git checkout develop
git merge master --no-edit
git push origin develop
================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
fabfile.py
================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/adamchainz/blacken-docs
rev: "1.13.0"
hooks:
- id: blacken-docs
additional_dependencies:
- black==22.12.0
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
language_version: python3.9
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.0.287
hooks:
- id: ruff
================================================
FILE: .readthedocs.yaml
================================================
version: 2
build:
os: "ubuntu-22.04"
tools:
python: "3.11"
# Build from the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# Explicitly set the version of Python and its requirements
python:
install:
- requirements: docs/requirements.txt
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
## [1.4.0] - 2026-05-01
### New Features
- **Dynamic Model support** — generate reports from any database table without defining a Django model.
New ``get_dynamic_model(table_name, database, schema)`` utility introspects a live table and returns a
fully usable (unmanaged) Django model. Supports 20+ field types and PostgreSQL schemas
(``schema`` parameter generates ``"schema"."table_name"`` as the db table).
Models are cached after first introspection.
- **``table_name`` attribute on ``ReportGenerator`` and ``ReportView``** — shorthand for using a dynamic model.
Setting ``table_name = "my_table"`` is equivalent to setting ``report_model = get_dynamic_model("my_table")``.
Column validation is deferred to request time for ``table_name``-based views so imports do not trigger
database access.
- **Pre-computed crosstab reports** — new ``crosstab_precomputed = True`` flag on ``ReportGenerator``
switches from live aggregation to reading existing column values and pivoting them.
Use ``crosstab_columns`` to name the value columns and ``crosstab_field`` to identify the pivot field.
Distinct values for ``crosstab_field`` are auto-discovered from the database if ``crosstab_ids`` is not set.
Intended for materialized views, data-warehouse fact tables, and pre-aggregated ETL outputs.
- **Print / HTML export** — new ``PrintHTMLExport`` class renders a clean, RTL-aware, print-ready HTML
page and triggers ``window.print()`` automatically. Enabled by default on all ``ReportView`` subclasses
via the new ``print_export_class`` attribute. The print export button opens in a new browser tab.
Templates are split into ``print_report.html``, ``print_report_header.html``, ``print_report_footer.html``,
and ``print_report_controls.html`` for easy overriding.
- **Arabic (ar) and German (de) translations** added.
### Improvements
- **Static asset path resolution** — template tags now consistently apply Django's ``static()`` to any
relative URL in settings (jQuery URL, chart engine JS files, Font Awesome CSS URL). The
``slick_reporting_settings`` JSON block written to the page also contains fully resolved static URLs,
ensuring correct paths with ``ManifestStaticFilesStorage`` or CDN-backed storages.
- **Export action metadata** — ``get_export_actions()`` now includes a ``new_window`` flag per action;
the JS export handler opens a new tab when the flag is set, used by the print export.
- **Report form extracted** — the filter form markup is now in its own ``report_form.html`` template
(included by ``report.html``) making it easier to override just the form section.
- **Select2 removed from core assets** — Select2 was a demo-only dependency that made it into the default
``SLICK_REPORTING_SETTINGS["MEDIA"]`` JS/CSS lists and was auto-initialised in ``report_loader.js``.
It is now removed from the library defaults; demo project configures it separately.
- **``fkeys_filter_func_hook``** — new static method on ``ReportViewBase`` that receives the dict of
detected foreign-key fields before the filter form is built. Override it to exclude or rename fields
(e.g. remove internal FK columns such as ``polymorphic_ctype_id``).
- **Chart error recovery** — chart rendering errors are now caught in ``report_loader.js`` and displayed
as an inline message instead of silently breaking the rest of the page.
### Bug Fixes
- Fix ``crosstab_compute_remainder`` class-level attribute being ignored; the value was always read from
the request instead of falling back to the class default.
### Chart Engine Fixes
- Update Highcharts wrapper to use the modern ``Highcharts.chart()`` API (v11+), replacing the removed
jQuery plugin syntax. Switch default Highcharts CDN to jsDelivr (``cdn.jsdelivr.net/npm/highcharts@11``).
- Fix Chart.js wrapper: corrected inverted ``is_time_series`` check; update to Chart.js v3/v4 API
(``plugins.title``, ``plugins.tooltip``, ``scales.y`` / ``scales.x``).
- Add crosstab support to the Chart.js wrapper.
- Fix Chart.js ``area`` chart type to render as ``line`` with ``fill: true``.
- Fix Chart.js pie chart on time-series reports: automatically enables ``plot_total``.
- Fix Chart.js pie chart sizing with ``aspectRatio: 2``.
- Fix Chart.js chart cache key to include the element xpath, preventing chart destruction when
the same report appears multiple times on a dashboard.
## [1.3.1] - 2024-06-16
- Fix issue with Line Chart on highcharts engine
- Reintroduce the stacking option on highcharts engine.
- Fix issue with having different version of the same chart on the same page.
- Enhanced the demo dashboard to show more capabilities regarding the charts.
## [1.3.0] - 2023-11-08
- Implement Slick reporting media override feature + docs
- Add `Integrating reports into your Admin site` section to the docs
- Group by and crosstab reports do not need date_field set anymore. Only time series do.
- Fix in FirstBalance Computation field if no date is supplied
- Add `REPORT_VIEW_ACCESS_FUNCTION` to control access to the report view
## [1.2.0] - 2023-10-10
- Add ``get_slick_reporting_media`` and ``get_charts_media`` templatetags
- Add `get_group_by_custom_querysets` hook to ReportView
- Enhance and document adding export options and customizing the builtin export to csv button
- Enhance and document adding custom buttons to the report page
- Enhance and document adding a new chart engine
- Fix in SlickReportingListView
- Move all css and js resources to be handled by `Media` governed by `settings.SLICK_REPORTING_SETTINGS`
## [1.1.1] - 2023-09-25
- Change settings to be a dict , adding support JQUERY_URL and FONT AWESOME customization #79 & #81
- Fix issue with chartjs not being loaded #80
- Remove `SLICK_REPORTING_FORM_MEDIA`
## [1.1.0] -
- Breaking: changed ``report_title_context_key`` default value to `report_title`
- Breaking: Renamed simple_report.html to report.html
- Breaking: Renamed ``SlickReportField`` to ``ComputationField``. SlickReportField will continue to work till next release.
- Revised and renamed js files
- Add dashboard capabilities.
- Added auto_load option to ReportView
- Unified report loading to use the report loader
- Fix issue with group_by_custom_queryset with time series
- Fix issue with No group by report
- Fix issue with traversing fields not showing up on ListViewReport
- Fix issue with date filter not being respected in ListViewReport
## [1.0.2] - 2023-08-31
- Add a demo project for exploration and also containing all documentation code for proofing.
- Revise and Enhancing Tutorial , Group by and Time series documentation.
- Fix issue with error on dev console on report page due to resources duplication
- Fix issue with Custom querysets not being correctly connected in the view
- Fix issue with time series custom dates
- Fix issue with Crosstab on traversing fields
## [1.0.1] - 2023-07-03
- Added missing js files ported from erp_framework package.
- Document the need for "crispy_bootstrap4" in the docs and add it as a dependency in the setup.
## [1.0.0] - 2023-07-03
- Added crosstab_ids_custom_filters to allow custom filters on crosstab ids
- Added ``group_by_custom_querysets`` to allow custom querysets as group
- Added ability to have crosstab report in a time series report
- Enhanced Docs content and structure.
## [0.9.0] - 2023-06-07
- Deprecated ``form_factory`` in favor of ``forms``, to be removed next version.
- Deprecated `crosstab_model` in favor of ``crosstab_field``, to be removed next version.
- Deprecated ``slick_reporting.view.SlickReportView`` and ``slick_reporting.view.SlickReportViewBase`` in favor of ``slick_reporting.view.ReportView`` and ``slick_reporting.view.BaseReportView``, to be removed next version.
- Allowed cross tab on fields other than ForeignKey
- Added support for start_date_field_name and end_date_field_name
- Added support to crosstab on traversing fields
- Added support for document types / debit and credit calculations
- Added support for ordering via ``ReportView.default_order_by`` and/or passing the parameter ``order_by`` to the view
- Added return of Ajax response in case of error and request is Ajax
- Made it easy override to the search form. Create you own form and subclass BaseReportForm and implement the mandatory method(s).
- Consolidated the needed resources in ``slick_reporting/js_resource.html`` template, so to use your own template you just need to include it.
- Fixed an issue with report fields not respecting the queryset on the ReportView.
- Fixed an issue if a foreign key have a custom `to_field` set either in ``group_by`` and/or `crosstab_field` .
- Enhancing and adding to the documentation.
- Black format the code and the documentation
## [0.8.0]
- Breaking: [Only if you use Crosstab reports] renamed crosstab_compute_reminder to crosstab_compute_remainder
- Breaking : [Only if you set the templates statics by hand] renamed slick_reporting to ra.hightchart.js and ra.chartjs.js to
erp_framework.highchart.js and erp_framework.chartjs.js respectively
- Fix an issue with Crosstab when there crosstab_compute_remainder = False
## [0.7.0]
- Added SlickReportingListView: a Report Class to display content of the model (like a ModelAdmin ChangeList)
- Added `show_time_series_selector` capability to SlickReportView allowing User to change the time series pattern from
the UI.
- Added ability to export to CSV from UI, using `ExportToStreamingCSV` & `ExportToCSV`
- Now you can have a custom column defined on the SlickReportView (and not needing to customise the report generator).
- You don't need to set date_field if you don't have calculations on the report
- Easier customization of the crispy form layout
- Enhance weekly time series default column name
- Add `Chart` data class to hold chart data
## [0.6.8]
- Add report_title to context
- Enhance SearchForm to be easier to override. Still needs more enhancements.
## [0.6.7]
- Fix issue with `ReportField` when it has a `requires` in time series and crosstab reports
## [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
## [0.6.4]
- Fix highchart cache to target the specific chart
- Added initial and required to report_form_factory
- Added base_q_filters and base_kwargs_filters to SlickReportField to control the base queryset
- Add ability to customize ReportField on the fly
- Adds `prevent_group_by` option to SlickReportField Will prevent group by calculation for this specific field, serves
when you want to compute overall results.
- Support reference to SlickReportField class directly in `requires` instead of its "registered" name.
- Adds PercentageToBalance report field
## [0.6.3]
- Change the deprecated in Django 4 `request.is_ajax` .
## [0.6.2]
- Fix an issue with time series calculating first day of the month to be of the previous month #46
## [0.6.1]
- Fix Django 4 compatibility (@squio)
## [0.6.0]
- Breaking [ONLY] if you have overridden ReportView.get_report_results()
- Moved the collecting of total report data to the report generator to make easier low level usage.
- Fixed an issue with Charts.js `get_row_data`
- Added ChartsOption 'time_series_support',in both chart.js and highcharts
- Fixed `SlickReportField.create` to use the issuing class not the vanilla one.
## [0.5.8]
- Fix compatibility with Django 3.2
## [0.5.7]
- Add ability to refer to related fields in a group by report(@jrutila)
## [0.5.6]
- Add exclude_field to report_form_factory (@gr4n0t4)
- Added support for group by Many To Many field (@gr4n0t4)
## [0.5.5]
- Add datepicker initialization function call (@squio)
- Fixed an issue with default dates not being functional.
## [0.5.4]
- Added missing prefix on integrity hash (@squio)
## [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
- Enhanced the default verbose names of time series.
- Expanding test coverage
## [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 `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
## [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
## [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
- Add support to create ReportField on the fly in all report types
- 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)
## [0.2.7] - 2020-07-24
### Updates
- Bring back crosstab capability
- 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 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.
## [0.2.5] - 2020-06-04
### Added
- 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)
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project
Django Slick Reporting is a Django library for generating analytical reports: simple aggregations, group-by, time-series, and crosstab (matrix) reports with built-in chart support (Highcharts, Chart.js).
## Commands
### Run all tests
```bash
python runtests.py
```
### Run a specific test
```bash
python runtests.py tests.tests.TestClassName.test_method_name
```
### Run tests with coverage
```bash
coverage run --include=../* runtests.py && coverage html
```
### Format & lint
```bash
black --line-length 120 .
ruff check --line-length 120 .
```
## Architecture
The library is layered as: **ReportView** (Django CBV) → **ReportGenerator** (computation engine) → **ComputationField** (calculation definitions) → **ReportFieldRegistry** (field lookup).
### Key modules in `slick_reporting/`
- **generator.py** — Core engine. `ReportGenerator` handles group-by, time-series, and crosstab logic. `ListViewReportGenerator` handles ungrouped row-level reports. Entry point is `get_report_data()`.
- **fields.py** — `ComputationField` base class and built-ins (`TotalReportField`, `BalanceReportField`, etc.). Fields declare `calculation_method` (e.g. `Sum`), `calculation_field`, and `requires` for dependency chaining. Use `ComputationField.create()` factory or subclass + `@report_field_register` decorator.
- **views.py** — `ReportView` extends `FormView` with report generation, chart context, CSV export, and AJAX support. Access control via `test_func()`.
- **forms.py** — `ReportForm` auto-generates filter forms from model ForeignKeys with crispy-forms/Bootstrap layout. `report_form_factory` builds forms dynamically.
- **registry.py** — `field_registry` singleton. ComputationFields self-register to avoid naming collisions with factory-created fields.
- **app_settings.py** — Defaults and settings loaded from Django's `SLICK_REPORTING_SETTINGS` dict.
### Charts
Charts are configurable per report via `Chart` dataclass. Supported engines: Highcharts, Chart.js, and Apex Charts. New chart engines can be added by providing a JS integration file and registering it in settings.
### Report types
Controlled by `ReportGenerator` configuration:
- **Group-by**: set `group_by` field, get one row per distinct value
- **Time-series**: set `time_series_pattern` (daily/weekly/monthly/yearly/custom), columns repeat per period
- **Crosstab**: set `crosstab_field` + `crosstab_ids`, produces matrix layout
- **Crosstab + Time-series**: can be combined for a matrix over time periods
- **List view**: use `ListViewReportGenerator` for ungrouped row-level output
### Column duck typing
Columns in `columns` list can be: model field names (str), traversing field names on the group-by model (e.g. `"client__contact__name"`), `ComputationField` subclasses, or special markers (`__total__`, `__balance__`, `__time_series__`, `__crosstab__`).
## Test structure
Tests live in `tests/` with settings in `tests/settings.py` (SQLite, no migrations). Test models (`Product`, `Client`, `SimpleSales`, etc.) are defined in `tests/models.py`. `BaseTestData` in `tests/tests.py` creates fixture data across multiple dates for time-series testing.
## Code style
- Black + Ruff, line length 120
- CI runs on Python 3.9, 3.10, 3.11
================================================
FILE: LICENSE.md
================================================
BSD 3-Clause License
Copyright (c) 2020, Ra Systems
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: MANIFEST.in
================================================
include LICENSE.md
include README.md
recursive-include slick_reporting/static *
recursive-include slick_reporting/templates *
recursive-include slick_reporting/locale *
recursive-exclude tests/ *
================================================
FILE: README.rst
================================================
.. image:: https://img.shields.io/pypi/v/django-slick-reporting.svg
:target: https://pypi.org/project/django-slick-reporting
.. image:: https://img.shields.io/pypi/pyversions/django-slick-reporting.svg
:target: https://pypi.org/project/django-slick-reporting
.. image:: https://img.shields.io/readthedocs/django-slick-reporting
:target: https://django-slick-reporting.readthedocs.io/
.. image:: https://api.travis-ci.com/ra-systems/django-slick-reporting.svg?branch=master
:target: https://app.travis-ci.com/github/ra-systems/django-slick-reporting
.. image:: https://img.shields.io/codecov/c/github/ra-systems/django-slick-reporting
:target: https://codecov.io/gh/ra-systems/django-slick-reporting
Django Slick Reporting
======================
A one stop reports engine with batteries included.
Features
--------
- Effortlessly create Simple, Grouped, Time series and Crosstab reports in a handful of code lines.
- Create Chart(s) for your reports with a single line of code.
- Create Custom complex Calculation.
- Optimized for speed.
- Easily extendable.
Installation
------------
Use the package manager `pip <https://pip.pypa.io/en/stable/>`_ to install django-slick-reporting.
.. code-block:: console
pip install django-slick-reporting
Usage
-----
So we have a model `SalesTransaction` which contains typical data about a sale.
We can extract different kinds of information for that model.
Let's start by a "Group by" report. This will generate a report how much quantity and value was each product sold within a certain time.
.. code-block:: python
# in views.py
from django.db.models import Sum
from slick_reporting.views import ReportView, Chart
from slick_reporting.fields import ComputationField
from .models import MySalesItems
class TotalProductSales(ReportView):
report_model = SalesTransaction
date_field = "date"
group_by = "product"
columns = [
"name",
ComputationField.create(
Sum, "quantity", verbose_name="Total quantity sold", is_summable=False
),
ComputationField.create(
Sum, "value", name="sum__value", verbose_name="Total Value sold $"
),
]
chart_settings = [
Chart(
"Total sold $",
Chart.BAR,
data_source=["sum__value"],
title_source=["name"],
),
Chart(
"Total sold $ [PIE]",
Chart.PIE,
data_source=["sum__value"],
title_source=["name"],
),
]
# then, in urls.py
path("total-sales-report", TotalProductSales.as_view())
With this code, you will get something like this:
.. image:: https://i.ibb.co/SvxTM23/Selection-294.png
:target: https://i.ibb.co/SvxTM23/Selection-294.png
:alt: Shipped in View Page
Time Series
-----------
A Time series report is a report that is generated for a periods of time.
The period can be daily, weekly, monthly, yearly or custom. Calculations will be performed for each period in the time series.
Example: How much was sold in value for each product monthly within a date period ?
.. code-block:: python
# in views.py
from slick_reporting.views import ReportView
from slick_reporting.fields import ComputationField
from .models import SalesTransaction
class MonthlyProductSales(ReportView):
report_model = SalesTransaction
date_field = "date"
group_by = "product"
columns = ["name", "sku"]
time_series_pattern = "monthly"
# or "yearly" , "weekly" , "daily" , others and custom patterns
time_series_columns = [
ComputationField.create(
Sum, "value", verbose_name=_("Sales Value"), name="value"
) # what will be calculated for each month
]
chart_settings = [
Chart(
_("Total Sales Monthly"),
Chart.PIE,
data_source=["value"],
title_source=["name"],
plot_total=True,
),
Chart(
"Total Sales [Area chart]",
Chart.AREA,
data_source=["value"],
title_source=["name"],
plot_total=False,
),
]
.. image:: https://github.com/ra-systems/django-slick-reporting/blob/develop/docs/source/topics/_static/timeseries.png?raw=true
:alt: Time Series Report
:align: center
Cross Tab
---------
Use crosstab reports, also known as matrix reports, to show the relationships between three or more query items.
Crosstab reports show data in rows and columns with information summarized at the intersection points.
.. code-block:: python
# in views.py
from slick_reporting.views import ReportView
from slick_reporting.fields import ComputationField
from .models import MySalesItems
class MyCrosstabReport(ReportView):
crosstab_field = "client"
crosstab_ids = [1, 2, 3]
crosstab_columns = [
ComputationField.create(Sum, "value", verbose_name=_("Value for")),
]
crosstab_compute_remainder = True
columns = [
"some_optional_field",
# You can customize where the crosstab columns are displayed in relation to the other columns
"__crosstab__",
# This is the same as the Same as the calculation in the crosstab, but this one will be on the whole set. IE total value
ComputationField.create(Sum, "value", verbose_name=_("Total Value")),
]
.. image:: https://github.com/ra-systems/django-slick-reporting/blob/develop/docs/source/topics/_static/crosstab.png?raw=true
:alt: Homepage
:align: center
Low level
---------
The view is a wrapper over the `ReportGenerator` class, which is the core of the reporting engine.
You can interact with the `ReportGenerator` using same syntax as used with the `ReportView` .
.. code-block:: python
from slick_reporting.generator import ReportGenerator
from .models import MySalesModel
class MyReport(ReportGenerator):
report_model = MySalesModel
group_by = "product"
columns = ["title", "__total__"]
# OR
my_report = ReportGenerator(
report_model=MySalesModel, group_by="product", columns=["title", "__total__"]
)
my_report.get_report_data() # -> [{'title':'Product 1', '__total__: 56}, {'title':'Product 2', '__total__: 43}, ]
This is just a scratch of what you can do and customize.
Demo site
---------
Available on `Django Slick Reporting <https://django-slick-reporting.com/>`_
You can also use locally
.. code-block:: console
# clone the repo
git clone https://github.com/ra-systems/django-slick-reporting.git
# create a virtual environment and activate it
python -m venv /path/to/new/virtual/environment
source /path/to/new/virtual/environment/bin/activate
cd django-slick-reporting/demo_proj
pip install -r requirements.txt
python manage.py migrate
python manage.py create_entries
python manage.py runserver
the ``create_entries`` command will generate data for the demo app
Documentation
-------------
Available on `Read The Docs <https://django-slick-reporting.readthedocs.io/en/latest/>`_
You can run documentation locally
.. code-block:: console
<activate your virtual environment>
cd docs
pip install -r requirements.txt
sphinx-build -b html source build
Road Ahead
----------
* Continue on enriching the demo project
* Add the dashboard capabilities
Running tests
-----------------
Create a virtual environment (maybe with `virtual slick_reports_test`), activate it; Then ,
.. code-block:: console
$ git clone git+git@github.com:ra-systems/django-slick-reporting.git
$ cd tests
$ python -m pip install -e ..
$ python runtests.py
# Or for Coverage report
$ coverage run --include=../* runtests.py [-k]
$ coverage html
Support & Contributing
----------------------
Please consider star the project to keep an eye on it. Your PRs, reviews are most welcome and needed.
We honor the well formulated `Django's guidelines <https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/unit-tests/>`_ to serve as contribution guide here too.
Authors
--------
* **Ramez Ashraf** - *Initial work* - `RamezIssac <https://github.com/RamezIssac>`_
Cross Reference
---------------
If you like this package, chances are you may like those packages too!
`Django Tabular Permissions <https://github.com/RamezIssac/django-tabular-permissions>`_ Display Django permissions in a HTML table that is translatable and easy customized.
`Django ERP Framework <https://github.com/ra-systems/RA>`_ A framework to build business solutions with ease.
If you find this project useful or promising , You can support us by a github ⭐
================================================
FILE: demo_proj/demo_app/__init__.py
================================================
================================================
FILE: demo_proj/demo_app/admin.py
================================================
# Register your models here.
================================================
FILE: demo_proj/demo_app/apps.py
================================================
from django.apps import AppConfig
class DemoAppConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "demo_app"
================================================
FILE: demo_proj/demo_app/forms.py
================================================
from django import forms
from django.db.models import Q
from slick_reporting.forms import BaseReportForm
class TotalSalesFilterForm(BaseReportForm, forms.Form):
PRODUCT_SIZE_CHOICES = (
("all", "All"),
("big-only", "Big Only"),
("small-only", "Small Only"),
("medium-only", "Medium Only"),
("all-except-extra-big", "All except extra Big"),
)
start_date = forms.DateField(
required=False,
label="Start Date",
widget=forms.DateInput(attrs={"type": "date"}),
)
end_date = forms.DateField(
required=False, label="End Date", widget=forms.DateInput(attrs={"type": "date"})
)
product_size = forms.ChoiceField(
choices=PRODUCT_SIZE_CHOICES, required=False, label="Product Size", initial="all"
)
def get_filters(self):
# return the filters to be used in the report
# Note: the use of Q filters and kwargs filters
kw_filters = {}
q_filters = []
if self.cleaned_data["product_size"] == "big-only":
kw_filters["product__size__in"] = ["extra_big", "big"]
elif self.cleaned_data["product_size"] == "small-only":
kw_filters["product__size__in"] = ["extra_small", "small"]
elif self.cleaned_data["product_size"] == "medium-only":
kw_filters["product__size__in"] = ["medium"]
elif self.cleaned_data["product_size"] == "all-except-extra-big":
q_filters.append(~Q(product__size__in=["extra_big", "big"]))
return q_filters, kw_filters
def get_start_date(self):
return self.cleaned_data["start_date"]
def get_end_date(self):
return self.cleaned_data["end_date"]
================================================
FILE: demo_proj/demo_app/helpers.py
================================================
from django.urls import path
from . import reports
TUTORIAL = [
("product-sales", reports.ProductSales),
("total-product-sales", reports.TotalProductSales),
("total-product-sales-by-country", reports.TotalProductSalesByCountry),
("monthly-product-sales", reports.MonthlyProductSales),
("product-sales-per-client-crosstab", reports.ProductSalesPerClientCrosstab),
("product-sales-per-country-crosstab", reports.ProductSalesPerCountryCrosstab),
("last-10-sales", reports.LastTenSales),
("total-product-sales-with-custom-form", reports.TotalProductSalesWithCustomForm),
]
GROUP_BY = [
("group-by-report", reports.GroupByReport),
("group-by-traversing-field", reports.GroupByTraversingFieldReport),
("group-by-custom-queryset", reports.GroupByCustomQueryset),
("no-group-by", reports.NoGroupByReport),
]
TIME_SERIES = [
("time-series-report", reports.TimeSeriesReport),
("time-series-with-selector", reports.TimeSeriesReportWithSelector),
("time-series-with-custom-dates", reports.TimeSeriesReportWithCustomDates),
("time-series-with-custom-dates-and-title", reports.TimeSeriesReportWithCustomDatesAndCustomTitle),
("time-series-without-group-by", reports.TimeSeriesWithoutGroupBy),
("time-series-with-group-by-custom-queryset", reports.TimeSeriesReportWithCustomGroupByQueryset),
]
CROSSTAB = [
("crosstab-report", reports.CrosstabReport),
("crosstab-report-with-ids", reports.CrosstabWithIds),
("crosstab-report-traversing-field", reports.CrosstabWithTraversingField),
("crosstab-report-custom-filter", reports.CrosstabWithIdsCustomFilter),
("crosstab-report-custom-verbose-name", reports.CrossTabReportWithCustomVerboseName),
("crosstab-report-custom-verbose-name-2", reports.CrossTabReportWithCustomVerboseNameCustomFilter),
("crosstab-report-with-time-series", reports.CrossTabWithTimeSeries),
]
PIVOT = [
("precomputed-monthly-sales", reports.PreComputedMonthlySales),
("dynamic-model-sales-by-country", reports.DynamicModelSalesByCountry),
]
OTHER = [
("highcharts-examples", reports.HighChartExample),
("chartjs-examples", reports.ChartJSExample),
("apexcharts-examples", reports.ProductSalesApexChart),
("custom-export", reports.CustomExportReport),
("form-initial", reports.ReportWithFormInitial),
]
def get_urls_patterns():
urls = []
for name, report in TUTORIAL + GROUP_BY + TIME_SERIES + CROSSTAB + PIVOT + OTHER:
urls.append(path(f"{name}/", report.as_view(), name=name))
return urls
================================================
FILE: demo_proj/demo_app/management/commands/create_entries.py
================================================
import datetime
import random
from datetime import timedelta
from django.contrib.auth import get_user_model
from django.core.management.base import BaseCommand
# from expense.models import Expense, ExpenseTransaction
from ...models import Client, Product, SalesTransaction, ProductCategory, MonthlySalesSummary
User = get_user_model()
def date_range(start_date, end_date):
for i in range((end_date - start_date).days + 1):
yield start_date + timedelta(i)
class Command(BaseCommand):
help = "Create Sample entries for the demo app"
def handle(self, *args, **options):
# create clients
client_countries = [
"US",
"DE",
"EG",
"IN",
"KW",
"RA"
]
product_category = [
"extra_big",
"big",
"medium",
"small",
"extra-small"
]
SalesTransaction.objects.all().delete()
Client.objects.all().delete()
Product.objects.all().delete()
ProductCategory.objects.all().delete()
User.objects.filter(is_superuser=False).delete()
for i in range(10):
User.objects.create_user(username=f"user {i}", password="password")
list(User.objects.values_list("id", flat=True))
for i in range(1, 4):
ProductCategory.objects.create(name=f"Product Category {i}")
product_category_ids = list(ProductCategory.objects.values_list("id", flat=True))
for i in range(1, 10):
Client.objects.create(name=f"Client {i}",
country=random.choice(client_countries),
# owner_id=random.choice(users_id)
)
clients_ids = list(Client.objects.values_list("pk", flat=True))
# create products
for i in range(1, 10):
Product.objects.create(name=f"Product {i}",
product_category_id=random.choice(product_category_ids),
size=random.choice(product_category))
products_ids = list(Product.objects.values_list("pk", flat=True))
current_year = datetime.datetime.today().year
start_date = datetime.datetime(current_year, 1, 1)
end_date = datetime.datetime(current_year + 1, 1, 1)
for date in date_range(start_date, end_date):
for i in range(1, 10):
SalesTransaction.objects.create(
client_id=random.choice(clients_ids),
product_id=random.choice(products_ids),
quantity=random.randint(1, 10),
price=random.randint(1, 100),
date=date,
number=f"Sale {date.strftime('%Y-%m-%d')} #{i}",
)
# ExpenseTransaction.objects.create(
# expense_id=random.choice(expense_ids),
# value=random.randint(1, 100),
# date=date,
# number=f"Expense {date.strftime('%Y-%m-%d')} #{i}",
# )
# Populate MonthlySalesSummary from the generated SalesTransaction data
MonthlySalesSummary.objects.all().delete()
from django.db.models.functions import TruncMonth
from django.db.models import Sum
monthly_data = (
SalesTransaction.objects.annotate(month=TruncMonth("date"))
.values("product_id", "month")
.annotate(total_sales=Sum("value"), total_quantity=Sum("quantity"))
)
for row in monthly_data:
MonthlySalesSummary.objects.create(
product_id=row["product_id"],
month=row["month"],
total_sales=row["total_sales"],
total_quantity=row["total_quantity"],
)
# Create a raw SQL table for the dynamic model demo
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("DROP TABLE IF EXISTS regional_sales_summary")
cursor.execute("""
CREATE TABLE regional_sales_summary (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_name VARCHAR(100) NOT NULL,
country VARCHAR(100) NOT NULL,
total_sales DECIMAL(12, 2) NOT NULL DEFAULT 0,
total_quantity DECIMAL(12, 2) NOT NULL DEFAULT 0
)
""")
country_data = (
SalesTransaction.objects
.values("product__name", "client__country")
.annotate(total_sales=Sum("value"), total_quantity=Sum("quantity"))
)
for row in country_data:
cursor.execute(
"INSERT INTO regional_sales_summary (product_name, country, total_sales, total_quantity) "
"VALUES (%s, %s, %s, %s)",
[row["product__name"], row["client__country"], row["total_sales"], row["total_quantity"]],
)
self.stdout.write(self.style.SUCCESS("Entries Created Successfully"))
================================================
FILE: demo_proj/demo_app/migrations/0001_initial.py
================================================
# Generated by Django 4.2 on 2023-08-02 09:14
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Client",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, verbose_name="Client Name")),
],
options={
"verbose_name": "Client",
"verbose_name_plural": "Clients",
},
),
migrations.CreateModel(
name="Product",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=100, verbose_name="Product Name")),
],
options={
"verbose_name": "Product",
"verbose_name_plural": "Products",
},
),
migrations.CreateModel(
name="SalesTransaction",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"number",
models.CharField(
max_length=100, verbose_name="Sales Transaction #"
),
),
("date", models.DateTimeField()),
("notes", models.TextField(blank=True, null=True)),
("value", models.DecimalField(decimal_places=2, max_digits=9)),
(
"client",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="demo_app.client",
verbose_name="Client",
),
),
(
"product",
models.ForeignKey(
on_delete=django.db.models.deletion.PROTECT,
to="demo_app.product",
verbose_name="Product",
),
),
],
options={
"verbose_name": "Sales Transaction",
"verbose_name_plural": "Sales Transactions",
},
),
]
================================================
FILE: demo_proj/demo_app/migrations/0002_salestransaction_price_salestransaction_quantity.py
================================================
# Generated by Django 4.2 on 2023-08-02 09:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("demo_app", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="salestransaction",
name="price",
field=models.DecimalField(decimal_places=2, default=0, max_digits=9),
preserve_default=False,
),
migrations.AddField(
model_name="salestransaction",
name="quantity",
field=models.DecimalField(decimal_places=2, default=0, max_digits=9),
preserve_default=False,
),
]
================================================
FILE: demo_proj/demo_app/migrations/0003_product_category.py
================================================
# Generated by Django 4.2 on 2023-08-30 08:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("demo_app", "0002_salestransaction_price_salestransaction_quantity"),
]
operations = [
migrations.AddField(
model_name="product",
name="category",
field=models.CharField(
default="Medium", max_length=100, verbose_name="Product Category"
),
),
]
================================================
FILE: demo_proj/demo_app/migrations/0004_client_country_product_sku.py
================================================
# Generated by Django 4.2.4 on 2023-08-30 08:38
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
dependencies = [
('demo_app', '0003_product_category'),
]
operations = [
migrations.AddField(
model_name='client',
name='country',
field=models.CharField(default='US', max_length=255, verbose_name='Country'),
),
migrations.AddField(
model_name='product',
name='sku',
field=models.CharField(default=uuid.uuid4, max_length=255, verbose_name='SKU'),
),
]
================================================
FILE: demo_proj/demo_app/migrations/0005_product_size.py
================================================
# Generated by Django 4.2.4 on 2023-08-30 11:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('demo_app', '0004_client_country_product_sku'),
]
operations = [
migrations.AddField(
model_name='product',
name='size',
field=models.CharField(default='Medium', max_length=100, verbose_name='Product Category'),
),
]
================================================
FILE: demo_proj/demo_app/migrations/0006_productcategory_remove_product_category_and_more.py
================================================
# Generated by Django 4.2.4 on 2023-08-30 17:57
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('demo_app', '0005_product_size'),
]
operations = [
migrations.CreateModel(
name='ProductCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='Product Category Name')),
],
),
migrations.RemoveField(
model_name='product',
name='category',
),
migrations.AlterField(
model_name='product',
name='size',
field=models.CharField(default='Medium', max_length=100, verbose_name='Size'),
),
migrations.AddField(
model_name='product',
name='product_category',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='demo_app.productcategory'),
),
]
================================================
FILE: demo_proj/demo_app/migrations/0007_monthlysalessummary.py
================================================
# Generated by Django 6.0.3 on 2026-04-23 15:27
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('demo_app', '0006_productcategory_remove_product_category_and_more'),
]
operations = [
migrations.CreateModel(
name='MonthlySalesSummary',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('month', models.DateField(verbose_name='Month')),
('total_sales', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Total Sales')),
('total_quantity', models.DecimalField(decimal_places=2, max_digits=12, verbose_name='Total Quantity')),
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='demo_app.product', verbose_name='Product')),
],
options={
'verbose_name': 'Monthly Sales Summary',
'verbose_name_plural': 'Monthly Sales Summaries',
'unique_together': {('product', 'month')},
},
),
migrations.RunSQL("""
CREATE TABLE regional_sales_summary
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_name VARCHAR(100) NOT NULL,
country VARCHAR(100) NOT NULL,
total_sales DECIMAL(12, 2) NOT NULL DEFAULT 0,
total_quantity DECIMAL(12, 2) NOT NULL DEFAULT 0
)
""")
]
================================================
FILE: demo_proj/demo_app/migrations/__init__.py
================================================
================================================
FILE: demo_proj/demo_app/models.py
================================================
import uuid
from django.db import models
from django.utils.translation import gettext_lazy as _
# Create your models here.
class Client(models.Model):
name = models.CharField(max_length=100, verbose_name="Client Name")
country = models.CharField(_("Country"), max_length=255, default="US")
class Meta:
verbose_name = _("Client")
verbose_name_plural = _("Clients")
def __str__(self):
return self.name
class ProductCategory(models.Model):
name = models.CharField(max_length=100, verbose_name="Product Category Name")
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=100, verbose_name="Product Name")
# category = models.CharField(max_length=100, verbose_name="Product Category", default="Medium")
product_category = models.ForeignKey(ProductCategory, on_delete=models.CASCADE, null=True)
sku = models.CharField(_("SKU"), max_length=255, default=uuid.uuid4)
size = models.CharField(max_length=100, verbose_name="Size", default="Medium")
class Meta:
verbose_name = _("Product")
verbose_name_plural = _("Products")
def __str__(self):
return self.name
class SalesTransaction(models.Model):
number = models.CharField(max_length=100, verbose_name="Sales Transaction #")
date = models.DateTimeField()
notes = models.TextField(blank=True, null=True)
client = models.ForeignKey(
Client, on_delete=models.PROTECT, verbose_name=_("Client")
)
product = models.ForeignKey(
Product, on_delete=models.PROTECT, verbose_name=_("Product")
)
value = models.DecimalField(max_digits=9, decimal_places=2)
quantity = models.DecimalField(max_digits=9, decimal_places=2)
price = models.DecimalField(max_digits=9, decimal_places=2)
class Meta:
verbose_name = _("Sales Transaction")
verbose_name_plural = _("Sales Transactions")
def __str__(self):
return f"{self.number} - {self.date}"
def save(
self, *args, **kwargs,
):
self.value = self.price * self.quantity
super().save(*args, **kwargs)
class MonthlySalesSummary(models.Model):
"""Pre-aggregated monthly sales data for demonstrating crosstab_precomputed."""
product = models.ForeignKey(Product, on_delete=models.CASCADE, verbose_name=_("Product"))
month = models.DateField(verbose_name=_("Month"))
total_sales = models.DecimalField(max_digits=12, decimal_places=2, verbose_name=_("Total Sales"))
total_quantity = models.DecimalField(max_digits=12, decimal_places=2, verbose_name=_("Total Quantity"))
class Meta:
verbose_name = _("Monthly Sales Summary")
verbose_name_plural = _("Monthly Sales Summaries")
unique_together = ("product", "month")
def __str__(self):
return f"{self.product} - {self.month}"
================================================
FILE: demo_proj/demo_app/reports.py
================================================
import datetime
from django.db.models import Sum, Q
from django.http import HttpResponse
from django.utils.translation import gettext_lazy as _
from slick_reporting.fields import ComputationField
from slick_reporting.views import ListReportView
from slick_reporting.views import ReportView, Chart
from .forms import TotalSalesFilterForm
from .models import SalesTransaction, Product, MonthlySalesSummary
class ProductSales(ReportView):
report_title = _("Product Sales")
report_description = _("Given a typical 'Sale Item' model, this report demonstrate a total of product sold. "
"With a bar and a pie charts.")
report_model = SalesTransaction
date_field = "date"
group_by = "product"
columns = [
"name",
ComputationField.create(
method=Sum,
field="value",
name="value__sum",
verbose_name="Total sold $",
is_summable=True,
),
]
# Charts
chart_settings = [
Chart(
"Total sold $",
Chart.BAR,
data_source=["value__sum"],
title_source=["name"],
),
]
class TotalProductSales(ReportView):
report_title = _("Product Sales Quantity and Value [no auto load]")
report_description = _("We compute the report over *two* fields `quantity and `value`."
"Results only load after you press Filter")
report_model = SalesTransaction
date_field = "date"
group_by = "product"
columns = [
"name",
ComputationField.create(Sum, "quantity", verbose_name="Total quantity sold", is_summable=False),
ComputationField.create(Sum, "value", name="sum__value", verbose_name="Total Value sold $"),
]
auto_load = False # Require the user to press the filter, useful if the report is resource demanding
chart_settings = [
Chart(
"Total sold $",
Chart.BAR,
data_source=["sum__value"],
title_source=["name"],
),
Chart(
"Total sold $ [PIE]",
Chart.PIE,
data_source=["sum__value"],
title_source=["name"],
),
]
class TotalProductSalesByCountry(ReportView):
report_title = _("Product Sales by Country")
report_description = _("Group by using Django's double-underscore traversal (group_by='client__country').")
report_model = SalesTransaction
date_field = "date"
group_by = "client__country" # notice the double underscore
columns = [
"client__country",
ComputationField.create(Sum, "value", name="sum__value", verbose_name="Total Value sold by country $"),
]
chart_settings = [
Chart(
"Total sold by country $",
Chart.PIE, # A Pie Chart
data_source=["sum__value"],
title_source=["client__country"],
),
]
class SumValueComputationField(ComputationField):
calculation_method = Sum
calculation_field = "value"
verbose_name = _("Sales Value")
name = "my_value_sum"
class MonthlyProductSales(ReportView):
report_title = _("Product Sales Monthly")
report_description = _("Breaks product sales into one column per month using a Time Series, "
"also demonstrates defining a reusable ComputationField.")
report_model = SalesTransaction
date_field = "date"
group_by = "product"
columns = ["name", "sku"]
time_series_pattern = "monthly"
time_series_columns = [
SumValueComputationField,
]
chart_settings = [
Chart(
_("Total Sales Monthly"),
Chart.PIE,
data_source=["my_value_sum"],
title_source=["name"],
plot_total=True,
),
Chart(
_("Sales Monthly [Bar]"),
Chart.COLUMN,
data_source=["my_value_sum"],
title_source=["name"],
),
]
class ProductSalesPerClientCrosstab(ReportView):
report_title = _("Product Sales Per Client Crosstab")
report_description = _("A crosstab matrix with products as rows and clients as columns. "
"The remainder column (crosstab_compute_remainder=True) "
"captures sales not tied to a said clients.")
report_model = SalesTransaction
date_field = "date"
group_by = "product"
crosstab_field = "client"
crosstab_columns = [
SumValueComputationField,
]
crosstab_compute_remainder = True # Add a extra column to the report, capturing the value all other clients
columns = [
"name",
"sku", # a field that exists on the `Product` model
"__crosstab__",
SumValueComputationField,
]
class ProductSalesPerCountryCrosstab(ReportView):
report_title = _("Product Sales Per Country Crosstab")
report_description = _("Demonstrate a crosstab/pivot on pre-set IDs (US, KW, EG, DE). "
"The remainder column collects sales from all other countries.")
report_model = SalesTransaction
date_field = "date"
group_by = "product"
crosstab_field = "client__country"
crosstab_columns = [
SumValueComputationField,
]
crosstab_ids = ["US", "KW", "EG", "DE"]
crosstab_compute_remainder = True
columns = [
"name",
"sku",
"__crosstab__",
SumValueComputationField,
]
class LastTenSales(ListReportView):
report_model = SalesTransaction
report_title = "Last 10 sales"
report_description = ("A list view (no aggregation) showing the ten most recent individual sale records."
"Uses ListReportView for row-level data instead of grouped summaries.")
date_field = "date"
filters = ["product", "client", "date"]
columns = [
"product__name",
"client__name",
"date",
"quantity",
"price",
"value",
]
default_order_by = "-date"
limit_records = 10
class TotalProductSalesWithCustomForm(TotalProductSales):
report_title = _("Total Product Sales with Custom Form")
report_description = _("Demonstrates a custom Form (form_class) that adds a product-size filter "
"alongside the standard date range filters.")
form_class = TotalSalesFilterForm
columns = [
"name",
"size",
ComputationField.create(Sum, "quantity", verbose_name="Total quantity sold", is_summable=False),
ComputationField.create(Sum, "value", name="sum__value", verbose_name="Total Value sold $"),
]
class GroupByReport(ReportView):
report_model = SalesTransaction
report_title = _("Group By Report")
report_description = _("Groups sales by product with no date_field set. "
"Shows that it's optional — omitting it gives all-time totals "
"regardless of the date pickers.")
# date_field = "date"
group_by = "product"
columns = [
"name",
ComputationField.create(
method=Sum,
field="value",
name="value__sum",
verbose_name="Total sold $",
is_summable=True,
),
]
# Charts
chart_settings = [
Chart(
"Total sold $",
Chart.BAR,
data_source=["value__sum"],
title_source=["name"],
),
]
class GroupByTraversingFieldReport(GroupByReport):
report_title = _("Group By Traversing Field")
report_description = _(
"Groups by a related model field using Django's double-underscore traversal (group_by='product__product_category'). No date filtering is applied.")
group_by = "product__product_category"
class GroupByCustomQueryset(ReportView):
report_model = SalesTransaction
report_title = _("Group By Custom Queryset")
report_description = _("Replaces automatic group-by with three crafted querysets (big, small, medium)."
"`format_row()` substitutes readable labels for the row's numeric index.")
date_field = "date"
group_by_custom_querysets = [
SalesTransaction.objects.filter(product__size__in=["big", "extra_big"]),
SalesTransaction.objects.filter(product__size__in=["small", "extra_small"]),
SalesTransaction.objects.filter(product__size="medium"),
]
group_by_custom_querysets_column_verbose_name = _("Product Size")
columns = [
"__index__",
ComputationField.create(Sum, "value", verbose_name=_("Total Sold $"), name="value"),
]
chart_settings = [
Chart(
title="Total sold By Size $",
type=Chart.BAR,
data_source=["value"],
title_source=["__index__"],
),
]
def format_row(self, row_obj):
# Put the verbose names we need instead of the integer index
index = row_obj["__index__"]
if index == 0:
row_obj["__index__"] = "Big"
elif index == 1:
row_obj["__index__"] = "Small"
elif index == 2:
row_obj["__index__"] = "Medium"
return row_obj
class NoGroupByReport(ReportView):
report_model = SalesTransaction
report_title = _("No-Group-By Report")
report_description = _("Produces a single summary row for the whole dataset with no grouping."
"Useful when you need a grand total over a date range rather than a breakdown.")
date_field = "date"
group_by = ""
columns = [
ComputationField.create(
method=Sum,
field="value",
name="value__sum",
verbose_name="Total sold $",
is_summable=True,
),
]
class TimeSeriesReport(ReportView):
report_title = _("Time Series Report")
report_description = _(
"Groups clients as rows and generates one column per month across the date range. The time_series_columns list defines what is computed for each period.")
report_model = SalesTransaction
group_by = "client"
date_field = "date"
# options are : "daily", "weekly", "bi-weekly", "monthly", "quarterly", "semiannually", "annually" and "custom"
time_series_pattern = "monthly"
# These columns will be calculated for each period in the time series.
time_series_columns = [
ComputationField.create(Sum, "value", verbose_name=_("Sales For ")),
]
columns = [
"name",
# placeholder for the generated time series columns
"__time_series__",
# This is the same as the time_series_columns, but this one will be on the whole set
ComputationField.create(Sum, "value", verbose_name=_("Total Sales")),
]
chart_settings = [
Chart(
"Client Sales",
Chart.BAR,
data_source=["sum__value"],
title_source=["name"],
),
Chart(
"Total Sales [Pie]",
Chart.PIE,
data_source=["sum__value"],
title_source=["name"],
plot_total=True,
),
Chart(
"Total Sales [Area chart]",
Chart.AREA,
data_source=["sum__value"],
title_source=["name"],
),
]
class TimeSeriesReportWithSelector(TimeSeriesReport):
report_title = _("Time Series Report With Pattern Selector")
report_description = _("Adds a pattern selector (daily / weekly / bi-weekly / monthly) to the filter form."
"users can switch time granularity without changing report code.")
time_series_selector = True
time_series_selector_choices = (
("daily", _("Daily")),
("weekly", _("Weekly")),
("bi-weekly", _("Bi-Weekly")),
("monthly", _("Monthly")),
)
time_series_selector_default = "bi-weekly"
# The label for the time series selector
time_series_selector_label = _("Period Pattern")
# Allow the user to select an empty time series, in which case no time series will be applied to the report.
time_series_selector_allow_empty = True
def get_current_year():
return datetime.datetime.now().year
class TimeSeriesReportWithCustomDates(TimeSeriesReport):
report_title = _("Time Series Report With Custom Dates")
report_description = _("Demonstrates a 'custom' time_series_pattern"
"here: first 10 days of Jan, Feb and Mar. "
"Useful for non-standard or irregular periods.")
time_series_pattern = "custom"
time_series_custom_dates = (
(datetime.datetime(get_current_year(), 1, 1), datetime.datetime(get_current_year(), 1, 10)),
(datetime.datetime(get_current_year(), 2, 1), datetime.datetime(get_current_year(), 2, 10)),
(datetime.datetime(get_current_year(), 3, 1), datetime.datetime(get_current_year(), 3, 10)),
)
class TimeSeriesReportWithCustomGroupByQueryset(ReportView):
report_title = _("Time Series Report")
report_description = _("Combines custom querysets (US clients vs. RS+DE clients) with time series. "
"Each queryset becomes a named row in the resulting matrix.")
report_model = SalesTransaction
date_field = "date"
group_by_custom_querysets = (
SalesTransaction.objects.filter(client__country="US"),
SalesTransaction.objects.filter(client__country__in=["RS", "DE"]),
)
time_series_pattern = "monthly"
time_series_columns = [
ComputationField.create(Sum, "value", verbose_name=_("Sales For ")),
]
columns = [
"__index__",
# placeholder for the generated time series columns
"__time_series__",
# This is the same as the time_series_columns, but this one will be on the whole set
ComputationField.create(Sum, "value", verbose_name=_("Total Sales")),
]
chart_settings = [
Chart(
"Client Sales",
Chart.BAR,
data_source=["sum__value"],
title_source=["__index__"],
),
Chart(
"Total Sales [Pie]",
Chart.PIE,
data_source=["sum__value"],
title_source=["__index__"],
plot_total=True,
),
Chart(
"Total Sales [Area chart]",
Chart.AREA,
data_source=["sum__value"],
title_source=["name"],
),
]
class SumOfFieldValue(ComputationField):
# A custom computation Field with custom verbose names
# Similar to `ComputationField.create(Sum, "value", verbose_name=_("Total Sales"))`
calculation_method = Sum
calculation_field = "value"
name = "sum_of_value"
@classmethod
def get_time_series_field_verbose_name(cls, date_period, index, dates, pattern):
# date_period: is a tuple (start_date, end_date)
# index is the index of the current pattern in the patterns on the report
# dates: the whole dates we have on the reports
# pattern it's the pattern name, ex: monthly, daily, custom
return f"First 10 days sales {date_period[0].month}-{date_period[0].year}"
class TimeSeriesReportWithCustomDatesAndCustomTitle(TimeSeriesReportWithCustomDates):
report_title = _("Time Series Report With Custom Dates and custom Title")
report_description = _(
"Extends custom date ranges with get_time_series_field_verbose_name() "
"to give each period column a human-readable heading like 'First 10 days sales 1-2024'.")
time_series_columns = [
SumOfFieldValue, # Use our newly created ComputationField with the custom time series verbose name
]
chart_settings = [
Chart(
"Client Sales",
Chart.BAR,
data_source=["sum_of_value"], # Note: This is the name of our `TotalSalesField` computation field
title_source=["name"],
),
Chart(
"Total Sales [Pie]",
Chart.PIE,
data_source=["sum_of_value"],
title_source=["name"],
plot_total=True,
),
]
class TimeSeriesWithoutGroupBy(ReportView):
report_title = _("Time Series without a group by")
report_description = _("A time series with no group_by: "
"the entire dataset becomes one summary row split into pattern (here monthly) columns.")
report_model = SalesTransaction
time_series_pattern = "monthly"
date_field = "date"
time_series_columns = [
ComputationField.create(Sum, "value", verbose_name=_("Sales For ")),
]
columns = [
"__time_series__",
ComputationField.create(Sum, "value", verbose_name=_("Total Sales")),
]
chart_settings = [
Chart(
"Total Sales [Bar]",
Chart.BAR,
data_source=["sum__value"],
title_source=["name"],
),
Chart(
"Total Sales [Pie]",
Chart.PIE,
data_source=["sum__value"],
title_source=["name"],
),
]
class CrosstabReport(ReportView):
report_title = _("Cross tab Report")
report_description = _("A basic crosstab: clients as rows, products as columns. "
"Each cell holds the sales value for that client–product combination, "
"with a total column on the right.")
report_model = SalesTransaction
group_by = "client"
# date_field = "date"
columns = [
"name",
# You can customize where the crosstab columns are displayed in relation to the other columns
"__crosstab__",
# This is the same as the calculation in the crosstab,
# but this one will be on the whole set. IE total value.
ComputationField.create(Sum, "value", verbose_name=_("Total Value")),
]
crosstab_field = "product"
crosstab_columns = [
ComputationField.create(Sum, "value", verbose_name=_("Value")),
]
class CrosstabWithTraversingField(CrosstabReport):
report_title = _("Cross tab Report With Traversing Field")
report_description = _("Uses a traversed field (product__size) as the crosstab axis.")
crosstab_field = "product__size"
class CrosstabWithIds(CrosstabReport):
report_title = _("Cross tab Report With Pre-set Ids")
report_description = _("Pre-sets the crosstab column IDs to the first and last product via get_crosstab_ids(). "
"Useful when you want a known set of columns resolved at request time.")
def get_crosstab_ids(self):
return [Product.objects.first().pk, Product.objects.last().pk]
class CrosstabWithIdsCustomFilter(CrosstabReport):
report_title = _("Crosstab with Custom Filters")
report_description = _("Replaces per-ID filters with two arbitrary Q-object filters (big vs 'not' big)."
"Demonstrates flexibility in breaking down crosstab columns ")
crosstab_ids_custom_filters = [
(~Q(product__size__in=["extra_big", "big"]), dict()),
(None, dict(product__size__in=["extra_big", "big"])),
]
# Note:
# if crosstab_ids_custom_filters is set, these settings has NO EFFECT
# crosstab_field = "client"
# crosstab_ids = [1, 2]
# crosstab_compute_remainder = True
class CustomCrossTabTotalField(ComputationField):
calculation_field = "value"
calculation_method = Sum
verbose_name = _("Sales for")
name = "sum__value"
@classmethod
def get_crosstab_field_verbose_name(cls, model, id):
if id == "----": # 4 dashes: the remainder column
return _("Rest of Products")
name = Product.objects.get(pk=id).name
return f"{cls.verbose_name} {name}"
class CrossTabReportWithCustomVerboseName(CrosstabReport):
report_title = _("Crosstab with customized verbose name")
report_description = _("Demonstrates how to customize the verbose name"
"Here, in each column header, we show 'Sales for Widget A' instead of the raw PK .")
crosstab_columns = [CustomCrossTabTotalField]
class CustomCrossTabTotalPerSize(CustomCrossTabTotalField):
@classmethod
def get_crosstab_field_verbose_name(cls, model, id):
if id == 0:
return f"{cls.verbose_name} Big and Extra Big"
return f"{cls.verbose_name} all other sizes"
@classmethod
def get_time_series_field_verbose_name(cls, date_period, index, dates, pattern):
return super().get_time_series_field_verbose_name(date_period, index, dates, pattern)
class CrossTabReportWithCustomVerboseNameCustomFilter(CrosstabWithIdsCustomFilter):
report_title = _("Crosstab customized verbose name with custom filter")
report_description = _("Combines Q-object filters with custom verbose names, "
"labelling columns 'Big and Extra Big' and 'All other sizes'.")
crosstab_columns = [CustomCrossTabTotalPerSize]
class CrossTabWithTimeSeries(CrossTabReportWithCustomVerboseNameCustomFilter):
report_title = _("Crosstab with time series")
report_description = _("Layers a monthly time series on top of the crosstab, producing columns for each filter × period combination. "
"Demonstrates the most complex report configuration available.")
date_field = "date"
time_series_pattern = "monthly"
crosstab_columns = [CustomCrossTabTotalPerSize]
columns = ["name", "__time_series__"]
class ChartJSExample(TimeSeriesReport):
report_title = _("ChartJS Examples ")
report_description = _("The same time-series data visualised with Chart.js. "
"Switching chart engines requires only setting"
"chart_engine='chartsjs' on the report class.")
chart_engine = "chartsjs"
chart_settings = [
Chart(
"Client Sales",
Chart.BAR,
data_source=["sum__value"],
title_source=["name"],
),
Chart(
"Total Sales [Pie]",
Chart.PIE,
data_source=["sum__value"],
title_source=["name"],
plot_total=True,
),
Chart(
"Total Sales [Line total]",
Chart.LINE,
data_source=["sum__value"],
title_source=["name"],
plot_total=True,
),
]
class HighChartExample(TimeSeriesReport):
chart_engine = "highcharts"
report_title = _("Highcharts Examples ")
report_description = _("Renders the same time-series data with all supported Highcharts chart types."
": column, bar, line, area, pie — including stacked and plot-total variants.")
chart_settings = [
Chart("Columns", Chart.COLUMN, data_source=["sum__value"], title_source=["name"]),
Chart(
"Stacking Columns",
Chart.COLUMN,
data_source=["sum__value"],
title_source=["name"],
stacking=True,
),
Chart(
"Totals Column",
Chart.COLUMN,
data_source=["sum__value"],
title_source=["name"],
plot_total=True,
),
Chart(
"Total Stacking Column",
Chart.COLUMN,
data_source=["sum__value"],
title_source=["name"],
plot_total=True,
stacking=True,
),
Chart(
"Bar",
Chart.BAR,
data_source=["sum__value"],
title_source=["name"],
),
Chart(
"Totals Bar", Chart.BAR, data_source=["sum__value"], title_source=["name"], plot_total=True
),
Chart(
"Line Chart",
Chart.LINE,
data_source=["sum__value"],
title_source=["name"],
# plot_total=True,
),
Chart(
"Total Line chart",
Chart.LINE,
data_source=["sum__value"],
title_source=["name"],
plot_total=True,
),
Chart(
"Pie: Total Sales",
Chart.PIE,
data_source=["sum__value"],
title_source=["name"],
plot_total=True,
),
Chart(
"Area: Client Sales",
Chart.AREA,
data_source=["sum__value"],
title_source=["name"],
),
]
class ProductSalesApexChart(ReportView):
report_title = _("Product Sales Apex Charts")
report_description = _(
"Demonstrates the ApexCharts engine with a custom template and "
"a custom JS entry point (displayChartCustomEntryPoint) for fully bespoke chart initialisation.")
report_model = SalesTransaction
date_field = "date"
group_by = "product"
chart_engine = "apexcharts"
template_name = "demo/apex_report.html"
columns = [
"name",
ComputationField.create(
method=Sum,
field="value",
name="value__sum",
verbose_name="Total sold $",
is_summable=True,
),
]
chart_settings = [
Chart(
"Total sold $",
type="pie",
data_source=["value__sum"],
title_source=["name"],
),
Chart(
"Total sold $",
type="bar",
data_source=["value__sum"],
title_source=["name"],
),
Chart(
"A custom Entry Point $",
type="bar",
data_source=["value__sum"],
title_source=["name"],
entryPoint="displayChartCustomEntryPoint", # a custom entry point to control the chart
),
]
class CustomExportReport(GroupByReport):
report_title = _("Custom Export Report")
report_description = _("Demonstrates adding custom action (here export_pdf) action alongside the built-ins"
"Also shows how to customize buttons label and css class")
export_actions = ["export_pdf"]
def export_pdf(self, report_data):
return HttpResponse(f"Dummy PDF Exported \n {report_data}")
export_pdf.title = _("Export PDF") # The label for the export action button
export_pdf.css_class = "btn btn-secondary" # the button classes
def export_csv(self, report_data):
return super().export_csv(report_data)
export_csv.title = _("My Custom CSV export Title")
export_csv.css_class = "btn btn-primary"
class ReportWithFormInitial(ReportView):
report_title = _("Report With Form Initial")
report_description = _("Pre-populates the client filter with the first and last client using get_initial(). "
"Shows how to set dynamic default filter values based on live data.")
report_model = SalesTransaction
date_field = "date"
group_by = "product"
columns = [
"name",
ComputationField.create(
method=Sum,
field="value",
name="value__sum",
verbose_name="Total sold $",
is_summable=True,
),
]
def get_initial(self):
from .models import Client
initial = super().get_initial()
initial["client_id"] = [Client.objects.first().pk, Client.objects.last().pk]
return initial
class PreComputedMonthlySales(ReportView):
report_title = _("Crosstab Precomputed: Monthly Sales")
report_description = _("Uses crosstab_precomputed=True on a model whose rows are already aggregated. "
"Columns are the distinct month values discovered at query time — no aggregation needed.")
report_model = MonthlySalesSummary
date_field = "month"
group_by = "product"
crosstab_field = "month"
crosstab_precomputed = True # signals that data is already aggregated
crosstab_columns = ["total_sales", "total_quantity"] # These fields are already computed/aggregated in database
columns = ["name", "__crosstab__"]
chart_settings = [
Chart(
_("Monthly Sales by Product"),
Chart.BAR,
data_source=["total_sales"],
title_source=["name"],
),
Chart(
_("Monthly Quantity by Product"),
Chart.LINE,
data_source=["total_quantity"],
title_source=["name"],
),
]
class DynamicModelSalesByCountry(ReportView):
report_title = _("Raw SQL Table / Dynamic Model Sales by Country")
report_description = _("Here we're acting on raw SQL table (table_name='regional_sales_summary'). "
"No Django model is involved — the schema is introspected at runtime."
"Demonstrating it with Pre-computed crosstab, but we can use it with any type of reports")
table_name = "regional_sales_summary"
group_by = "product_name"
crosstab_field = "country"
crosstab_columns = ["total_sales", "total_quantity"]
crosstab_precomputed = True
columns = ["product_name", "__crosstab__"]
chart_settings = [
Chart(
_("Sales by Country"),
Chart.BAR,
data_source=["total_sales"],
title_source=["product_name"],
),
Chart(
_("Sales by Country [Pie]"),
Chart.PIE,
data_source=["total_sales"],
title_source=["product_name"],
),
]
================================================
FILE: demo_proj/demo_app/templatetags/__init__.py
================================================
================================================
FILE: demo_proj/demo_app/templatetags/slick_reporting_demo_tags.py
================================================
import inspect
from django import template
from django.urls import reverse
from django.utils.html import format_html
from django.utils.safestring import mark_safe
register = template.Library()
def get_section(section):
from ..helpers import TUTORIAL, GROUP_BY, TIME_SERIES, CROSSTAB, PIVOT
to_use = []
if section == "tutorial":
to_use = TUTORIAL
elif section == "group_by":
to_use = GROUP_BY
elif section == "timeseries":
to_use = TIME_SERIES
elif section == "crosstab":
to_use = CROSSTAB
elif section == "pivot":
to_use = PIVOT
return to_use
@register.simple_tag(takes_context=True)
def get_menu(context, section):
request = context['request']
to_use = get_section(section)
menu = []
for link, report in to_use:
is_active = "active" if f"/{link}/" in request.path else ""
menu.append(format_html(
'<a class="dropdown-item {active}" href="{href}">{text}</a>', active=is_active,
href=reverse(link), text=report.report_title or link)
)
return mark_safe("".join(menu))
@register.simple_tag
def get_report_source(report):
try:
return inspect.getsource(report.__class__)
except (OSError, TypeError):
return "# Source code not available"
@register.simple_tag
def get_report_class_label(report):
cls = report.__class__
return f"{cls.__module__}.{cls.__name__}"
@register.simple_tag(takes_context=True)
def should_show(context, section):
request = context["request"]
to_use = get_section(section)
for link, report in to_use:
if f"/{link}/" in request.path:
return "show"
return ""
================================================
FILE: demo_proj/demo_app/tests.py
================================================
import datetime
from django.contrib.auth import get_user_model
from django.db import connection
from django.test import TestCase
from django.utils import timezone
from . import helpers
from .models import Client, MonthlySalesSummary, Product, ProductCategory, SalesTransaction
ALL_REPORTS = helpers.TUTORIAL + helpers.GROUP_BY + helpers.TIME_SERIES + helpers.CROSSTAB + helpers.PIVOT + helpers.OTHER
class DemoSanityTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.superuser = get_user_model().objects.create_superuser("admin", "admin@example.com", "password")
category = ProductCategory.objects.create(name="Electronics")
cls.p1 = Product.objects.create(name="Widget A", product_category=category, size="medium")
cls.p2 = Product.objects.create(name="Widget B", product_category=category, size="big")
cls.p3 = Product.objects.create(name="Widget C", product_category=category, size="small")
cls.c1 = Client.objects.create(name="Client US", country="US")
cls.c2 = Client.objects.create(name="Client DE", country="DE")
cls.c3 = Client.objects.create(name="Client KW", country="KW")
pairs = [
(cls.p1, cls.c1), (cls.p2, cls.c2), (cls.p1, cls.c3),
(cls.p3, cls.c1), (cls.p2, cls.c2), (cls.p3, cls.c3),
]
for i, (product, client) in enumerate(pairs):
SalesTransaction.objects.create(
number=f"INV-{i + 1:04d}",
date=timezone.make_aware(datetime.datetime(2024, (i % 12) + 1, 15)),
client=client,
product=product,
quantity=10,
price=100,
)
for product in [cls.p1, cls.p2, cls.p3]:
for month in range(1, 4):
MonthlySalesSummary.objects.create(
product=product,
month=datetime.date(2024, month, 1),
total_sales=1000,
total_quantity=10,
)
with connection.cursor() as cursor:
for row in [
("Widget A", "US", 5000, 50),
("Widget B", "DE", 3000, 30),
("Widget C", "KW", 2000, 20),
]:
cursor.execute(
"INSERT INTO regional_sales_summary (product_name, country, total_sales, total_quantity)"
" VALUES (%s, %s, %s, %s)",
row,
)
def setUp(self):
self.client.force_login(self.superuser)
def test_all_pages_load(self):
for url in ["/", "/dashboard/"]:
with self.subTest(url=url):
self.assertEqual(self.client.get(url).status_code, 200)
for name, _ in ALL_REPORTS:
with self.subTest(name=name):
response = self.client.get(f"/{name}/")
self.assertEqual(response.status_code, 200)
def test_all_report_data_endpoints(self):
for name, _ in ALL_REPORTS:
with self.subTest(name=name):
response = self.client.get(
f"/{name}/",
data={"start_date": "2024-01-01", "end_date": "2024-12-31"},
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(response.status_code, 200)
self.assertIn("data", response.json())
def test_precomputed_crosstab_fk_group_by_returns_data(self):
"""Regression: precomputed crosstab with FK group_by was returning empty rows.
The generator was building main_queryset as .values("product_id") so each obj
only had {"product_id": N}. _get_record_data then looked up obj["id"] which was
None, causing every group key to resolve to "None" and miss the precomputed dict.
Fix: fetch the related model objects (same as non-precomputed FK path).
"""
response = self.client.get(
"/precomputed-monthly-sales/",
data={"start_date": "2024-01-01", "end_date": "2024-12-31"},
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(response.status_code, 200)
data = response.json()["data"]
self.assertEqual(len(data), 3, "Expected one row per product")
product_names = {row["name"] for row in data}
self.assertEqual(product_names, {"Widget A", "Widget B", "Widget C"})
for row in data:
crosstab_values = [v for k, v in row.items() if k not in ("name",) and v != ""]
self.assertTrue(
any(v not in (0, "0", None) for v in crosstab_values),
f"All crosstab values are zero/empty for {row['name']} — group key lookup is broken",
)
================================================
FILE: demo_proj/demo_app/views.py
================================================
from django.views.generic import TemplateView
# Create your views here.
class HomeView(TemplateView):
template_name = "home.html"
class Dashboard(TemplateView):
template_name = "dashboard.html"
================================================
FILE: demo_proj/demo_proj/__init__.py
================================================
================================================
FILE: demo_proj/demo_proj/asgi.py
================================================
"""
ASGI config for demo_proj project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo_proj.settings")
application = get_asgi_application()
================================================
FILE: demo_proj/demo_proj/settings.py
================================================
"""
Django settings for demo_proj project.
Generated by 'django-admin startproject' using Django 4.2.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-kb+5wbkzz-dxvmzs%49y07g7zkk9@30w%+u@2@d5x!)daivk&7"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv("DEBUG", "True").lower() in ("true", "1", "yes")
_allowed_hosts = os.getenv("ALLOWED_HOSTS", "")
ALLOWED_HOSTS = _allowed_hosts.split(",") if _allowed_hosts else ["*"]
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"demo_app",
"crispy_forms",
"crispy_bootstrap5",
"slick_reporting",
# "slick_reporting.dashboards",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "demo_proj.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(BASE_DIR, "templates")],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "demo_proj.wsgi.application"
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = "static/"
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
CRISPY_TEMPLATE_PACK = "bootstrap5"
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
SLICK_REPORTING_DEFAULT_CHARTS_ENGINE = "highcharts"
SLICK_REPORTING_SETTINGS = {
"CHARTS": {
"apexcharts": {
"entryPoint": "DisplayApexPieChart",
"js": ("https://cdn.jsdelivr.net/npm/apexcharts", "slick_reporting/slick_reporting.chartsjs.js"),
"css": {"all": ("https://cdn.jsdelivr.net/npm/apexcharts/dist/apexcharts.min.css",)},
},
},
}
STATIC_ROOT = os.getenv("STATIC_ROOT", BASE_DIR / "collected_static")
MEDIA_ROOT = os.getenv("MEDIA_ROOT", str(BASE_DIR / "media"))
================================================
FILE: demo_proj/demo_proj/urls.py
================================================
"""
URL configuration for demo_proj project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from demo_app import views
from demo_app import helpers
urlpatterns = helpers.get_urls_patterns() + [
path("", views.HomeView.as_view(), name="home"),
path("dashboard/", views.Dashboard.as_view(), name="dashboard"),
path("admin/", admin.site.urls),
]
================================================
FILE: demo_proj/demo_proj/wsgi.py
================================================
"""
WSGI config for demo_proj project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os, sys
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo_proj.settings_production")
BASE_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "../")
sys.path.append(os.path.abspath(BASE_DIR))
application = get_wsgi_application()
================================================
FILE: demo_proj/manage.py
================================================
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "demo_proj.settings")
# add slick reporting to path so that it can be imported
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(os.path.abspath(BASE_DIR))
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
================================================
FILE: demo_proj/requirements.txt
================================================
django>=4.2
python-dateutil>=2.8.1
simplejson
django-crispy-forms
crispy-bootstrap5
================================================
FILE: demo_proj/templates/base.html
================================================
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<title>{% block meta_page_title %}{{ report_title }}{% endblock %}</title>
<!-- CSS files -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/css/tabler.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/css/tabler-flags.min.css">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/css/tabler-payments.min.css">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/css/tabler-vendors.min.css">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css">
<style>
@import url('https://rsms.me/inter/inter.css');
:root {
--tblr-font-sans-serif: 'Inter Var', -apple-system, BlinkMacSystemFont, San Francisco, Segoe UI, Roboto, Helvetica Neue, sans-serif;
}
body {
font-feature-settings: "cv03", "cv04", "cv11";
}
@media (min-width: 576px)
.navbar-expand-sm.navbar-vertical ~ .navbar, .navbar-expand-sm.navbar-vertical ~ .page-wrapper {
margin-left: 18rem;
}
@media (min-width: 576px)
.navbar-vertical.navbar-expand-sm {
width: 18rem;
}
</style>
</head>
<body class=" layout-fluid">
{# <script src="./dist/js/demo-theme.min.js?1684106062"></script>#}
<div class="page">
<!-- Sidebar -->
<aside class="navbar navbar-vertical navbar-expand-sm navbar-dark">
<div class="container-fluid">
<button class="navbar-toggler" type="button">
<span class="navbar-toggler-icon"></span>
</button>
<h1 class="navbar-brand navbar-brand-autodark">
<a href="#">
{# <img src="https://preview.tabler.io/static/logo-white.svg" width="110" height="32" alt="Tabler" class="navbar-brand-image">#}
Django Slick Reporting
</a>
</h1>
<div class="collapse navbar-collapse" id="sidebar-menu">
{% include "menu.html" %}
</div>
</div>
</aside>
<div class="page-wrapper">
<div class="page-header d-print-none">
<div class="container-xl">
<div class="row g-2 align-items-center">
<div class="col">
<h2 class="page-title">
{# Vertical layout#}
{% block page_title %}
{% endblock %}
</h2>
{% block page_subtitle %}{% endblock %}
</div>
</div>
</div>
</div>
<div class="page-body">
<div class="container-xl">
{% block content %}
{% endblock %}
</div>
</div>
</div>
</div>
<!-- Libs JS -->
<!-- Tabler Core -->
{# <script src="./dist/js/tabler.min.js?1684106062" defer></script>#}
{# <script src="./dist/js/demo.min.js?1684106062" defer></script>#}
<script src="https://cdn.jsdelivr.net/npm/@tabler/core@1.0.0-beta17/dist/js/tabler.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
{% block extrajs %}
{% endblock %}
</body>
</html>
================================================
FILE: demo_proj/templates/dashboard.html
================================================
{% extends "base.html" %}
{% load slick_reporting_tags %}
{% block page_title %} Dashboard {% endblock %}
{% block meta_page_title %} Dashboard {% endblock %}
{% block content %}
<div class="mb-3 ">
<label class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="masonry-toggle">
<span class="form-check-label">Compact layout</span>
</label>
</div>
<div id="dashboard-container" class="row row-cards">
<div class="col-lg-6">
{% get_widget_from_url url_name="product-sales" %}
</div>
<div class="col-lg-6">
{% get_widget_from_url url_name="total-product-sales-by-country" title="Widget custom title" %}
</div>
<div class="col-lg-6">
{% get_widget_from_url url_name="total-product-sales" chart_id=1 title="Custom default Chart" %}
</div>
<div class="col-lg-6">
{% get_widget_from_url url_name="monthly-product-sales" chart_id=1 display_table=False title="No table, Chart Only" %}
</div>
<div class="col-lg-6">
{% get_widget_from_url url_name="total-product-sales" display_chart=False title="Table only, no chart" %}
</div>
<div class="col-lg-6">
{% get_widget_from_url url_name="total-product-sales" display_table=False display_chart_selector=False title="No Chart Selector, only the assigned one" %}
</div>
<div class="col-lg-6">
{% get_widget_from_url url_name="total-product-sales" success_callback="custom_js_callback" title="Custom Js Handler and template" template_name="widget_template_with_pre.html" %}
</div>
</div>
<style>
.dashboard-masonry {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: flex-start;
}
.dashboard-masonry > * {
flex: 0 0 calc(50% - 0.5rem);
min-width: 0;
}
.dashboard-masonry canvas {
max-width: 100%;
}
@media (max-width: 991px) {
.dashboard-masonry > * {
flex: 0 0 100%;
}
}
</style>
{% endblock %}
{% block extrajs %}
{% include "slick_reporting/js_resources.html" %}
{# make sure to have the js_resources added to the dashboard page #}
{% get_charts_media "all" %}
{# make sure to add all charts needed media to the dashboard page. #}
{# "all" loads all registered chart engines. If a CDN is blocked, it may break the page. #}
{# You can also pass the specific chart_settings from a report view to load only what's needed. #}
<script>
function custom_js_callback(data, $elem) {
// data is the json response from the server
// $elem is the jquery object of the element `[data-report-widget]` that the report is attached to.
console.info(data);
console.info($elem);
$('#responsePre').text(JSON.stringify(data, null, 4));
}
$('#masonry-toggle').on('change', function () {
let $container = $('#dashboard-container');
if (this.checked) {
$container.removeClass('row row-cards').addClass('dashboard-masonry');
$container.children().removeClass('col-lg-6');
} else {
$container.removeClass('dashboard-masonry').addClass('row row-cards');
$container.children().addClass('col-lg-6');
}
});
</script>
{% endblock %}
================================================
FILE: demo_proj/templates/demo/apex_report.html
================================================
{% extends "slick_reporting/report.html" %}
{% load slick_reporting_tags %}
{% block content %}
{{ block.super }}
{% endblock %}
{% block extrajs %}
{{ block.super }}
<script>
let chart = null;
function displayChartCustomEntryPoint(data, $elem, chartOptions) {
alert("This is a custom entry point for displaying charts. " +
"Check the console for the sent arguments")
console.log("data:", data);
console.log("$elem:", $elem);
console.log("chartOptions:", chartOptions);
}
function DisplayApexPieChart(data, $elem, chartOptions) {
let legendAndSeries = $.slick_reporting.chartsjs.getGroupByLabelAndSeries(data, chartOptions);
let options = {}
if (chartOptions.type === "pie") {
options = {
series: legendAndSeries.series,
chart: {
type: "pie",
height: 350
},
labels: legendAndSeries.labels,
};
} else {
options = {
chart: {
type: 'bar'
},
series: [{
name: 'Sales',
data: legendAndSeries.series
}],
xaxis: {
categories: legendAndSeries.labels,
}
}
}
try {
// destroy old chart, if any
chart.destroy();
} catch (e) {
// do nothing
}
chart = new ApexCharts($elem[0], options);
chart.render();
}
</script>
{% endblock %}
================================================
FILE: demo_proj/templates/home.html
================================================
{% extends "base.html" %}
{% block content %}
<section class="jumbotron text-center">
<div class="container">
<h1 class="jumbotron-heading">Welcome to Django Slick Reporting</h1>
<p class="lead text-muted">The Reporting Engine for Django.</p>
<p>
{# <a href="{% url 'product-sales' %}" class="btn btn-primary my-2">Start walk through</a>#}
<a href="https://github.com/ra-systems/django-slick-reporting" class="btn btn-secondary my-2">Github</a>
</p>
</div>
</section>
<div class="container">
<!-- Example row of columns Create powerful analytics with a simple class syntax. -->
<div class="row">
<div class="col-md-4">
<h2>Powerful</h2>
<p>Effortlessly create Simple, Grouped, Time series and Crosstab reports in a handful of code lines.
You can also create your Custom Calculation easily, which will be integrated with the above reports
types</p>
<p>
{# <a class="btn btn-secondary" href="https://github.com/ra-systems/slick-reporting-demo" role="button">This#}
{# site on Github »</a>#}
<a href="{% url 'product-sales' %}" class="btn btn-primary my-2 btn btn-primary">Begin Walk through</a>
</p>
</div>
<div class="col-md-4">
<h2>Chart Wrappers</h2>
<p>Slick reporting comes with <a href="https://www.highcharts.com/"> Highcharts </a> and <a
href="https://www.chartjs.org/"> Charts.js </a> wrappers to transform the generated data into
attractive charts in handfule of lines</p>
<p>You can check Django Slick Reporting documentation for more in depth information</p>
<p><a class="btn btn-secondary" href="https://django-slick-reporting.readthedocs.io/" role="button">Read
the docs »</a></p>
</div>
<div class="col-md-4">
<h2>Open source</h2>
<p>Optimized for speed. You can also check this same website and generate more data and test this package on million on records yourself
<p><a class="btn btn-secondary" href="https://github.com/ra-systems/slick-reporting-demo" role="button">This
site on Github »</a></p>
{# <a class="github-button" href="https://github.com/ra-systems/django-slick-reporting"#}
{# data-color-scheme="no-preference: dark; light: dark; dark: dark;" data-icon="octicon-star"#}
{# data-show-count="true" aria-label="Star ra-systems/django-slick-reporting on GitHub">Star</a>#}
</p>
{# <p><a class="btn btn-secondary" href="#" role="button">View details »</a></p>#}
</div>
</div>
<hr>
</div>
{% endblock %}
================================================
FILE: demo_proj/templates/menu.html
================================================
{% load slick_reporting_demo_tags %}
<ul class="navbar-nav pt-lg-3">
<li class="nav-item">
<a class="nav-link" href="{% url "home" %}">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/home -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M5 12l-2 0l9 -9l9 9l-2 0"/><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"/><path
d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"/></svg>
</span>
<span class="nav-link-title">
Home
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url "dashboard" %}">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/home -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M5 12l-2 0l9 -9l9 9l-2 0"/><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"/><path
d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"/></svg>
</span>
<span class="nav-link-title">
Dashboard Example
</span>
</a>
</li>
<li class="nav-item dropdown ">
<a class="nav-link dropdown-toggle" href="#navbar-base" data-bs-toggle="dropdown" data-bs-auto-close="false"
role="button" aria-expanded="false">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/package -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5"/><path d="M12 12l8 -4.5"/><path
d="M12 12l0 9"/><path d="M12 12l-8 -4.5"/><path d="M16 5.25l-8 4.5"/></svg>
</span>
<span class="nav-link-title">
Tutorial
</span>
</a>
<div class="dropdown-menu {% should_show "tutorial" %}">
<div class="dropdown-menu-columns">
<div class="dropdown-menu-column">
{% get_menu "tutorial" %}
{# <a class="dropdown-item" href="./badges.html">#}
{# Badges#}
{# <span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>#}
{# </a>#}
{# <a class="dropdown-item" href="./buttons.html">#}
{# Buttons#}
{# </a>#}
{# <div class="dropend">#}
{# <a class="dropdown-item dropdown-toggle" href="#sidebar-cards" data-bs-toggle="dropdown"#}
{# data-bs-auto-close="false" role="button" aria-expanded="false">#}
{# Cards#}
{# <span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>#}
{# </a>#}
{# <div class="dropdown-menu">#}
{# <a href="./cards.html" class="dropdown-item">#}
{# Sample cards#}
{# </a>#}
{# <a href="./card-actions.html" class="dropdown-item">#}
{# Card actions#}
{# <span class="badge badge-sm bg-green-lt text-uppercase ms-auto">New</span>#}
{# </a>#}
{# <a href="./cards-masonry.html" class="dropdown-item">#}
{# Cards Masonry#}
{# </a>#}
{# </div>#}
{# </div>#}
{# <a class="dropdown-item" href="./colors.html">#}
{# Colors#}
{# </a>#}
</div>
</div>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#navbar-base" data-bs-toggle="dropdown" data-bs-auto-close="false"
role="button" aria-expanded="false">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/package -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5"/><path d="M12 12l8 -4.5"/><path
d="M12 12l0 9"/><path d="M12 12l-8 -4.5"/><path d="M16 5.25l-8 4.5"/></svg>
</span>
<span class="nav-link-title">
Group By Examples
</span>
</a>
<div class="dropdown-menu {% should_show "group_by" %}">
<div class="dropdown-menu-columns">
<div class="dropdown-menu-column">
{% get_menu "group_by" %}
</div>
</div>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#navbar-base" data-bs-toggle="dropdown" data-bs-auto-close="false"
role="button" aria-expanded="false">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/package -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5"/><path d="M12 12l8 -4.5"/><path
d="M12 12l0 9"/><path d="M12 12l-8 -4.5"/><path d="M16 5.25l-8 4.5"/></svg>
</span>
<span class="nav-link-title">
Time Series Examples
</span>
</a>
<div class="dropdown-menu {% should_show "timeseries" %}">
<div class="dropdown-menu-columns">
<div class="dropdown-menu-column">
{% get_menu "timeseries" %}
</div>
</div>
</div>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#navbar-base" data-bs-toggle="dropdown" data-bs-auto-close="false"
role="button" aria-expanded="false">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/package -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5"/><path d="M12 12l8 -4.5"/><path
d="M12 12l0 9"/><path d="M12 12l-8 -4.5"/><path d="M16 5.25l-8 4.5"/></svg>
</span>
<span class="nav-link-title">
Crosstab Examples
</span>
</a>
<div class="dropdown-menu {% should_show "crosstab" %}">
<div class="dropdown-menu-columns">
<div class="dropdown-menu-column">
{% get_menu "crosstab" %}
</div>
</div>
</div>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == "/precomputed-monthly-sales/" %}active{% endif %}"
href="{% url "precomputed-monthly-sales" %}">
<span class="nav-link-icon d-md-none d-lg-inline-block">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5"/><path d="M12 12l8 -4.5"/><path
d="M12 12l0 9"/><path d="M12 12l-8 -4.5"/><path d="M16 5.25l-8 4.5"/></svg>
</span>
<span class="nav-link-title">Pre Computed</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == "/dynamic-model-sales-by-country/" %}active{% endif %}"
href="{% url "dynamic-model-sales-by-country" %}">
<span class="nav-link-icon d-md-none d-lg-inline-block">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5"/><path d="M12 12l8 -4.5"/><path
d="M12 12l0 9"/><path d="M12 12l-8 -4.5"/><path d="M16 5.25l-8 4.5"/></svg>
</span>
<span class="nav-link-title">Raw SQL Table (not a django model)</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url "highcharts-examples" %}">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/home -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M5 12l-2 0l9 -9l9 9l-2 0"/><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"/><path
d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"/></svg>
</span>
<span class="nav-link-title">
HighCharts Charts Demo
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url "chartjs-examples" %}">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/home -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M5 12l-2 0l9 -9l9 9l-2 0"/><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"/><path
d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"/></svg>
</span>
<span class="nav-link-title">
Charts.js Charts
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url "apexcharts-examples" %}">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/home -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M5 12l-2 0l9 -9l9 9l-2 0"/><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"/><path
d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"/></svg>
</span>
<span class="nav-link-title">
Apex Charts
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url "custom-export" %}">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/home -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M5 12l-2 0l9 -9l9 9l-2 0"/><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"/><path
d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"/></svg>
</span>
<span class="nav-link-title">
Custom Export
</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url "form-initial" %}">
<span class="nav-link-icon d-md-none d-lg-inline-block"><!-- Download SVG icon from http://tabler-icons.io/i/home -->
<svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path
d="M5 12l-2 0l9 -9l9 9l-2 0"/><path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"/><path
d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"/></svg>
</span>
<span class="nav-link-title">
Form initial
</span>
</a>
</li>
</ul>
================================================
FILE: demo_proj/templates/slick_reporting/base.html
================================================
{% extends "base.html" %}
{% block meta_page_title %} {{ report_title }}{% endblock %}
{% block page_title %} {{ report_title }} {% endblock %}
{% block page_subtitle %}{% if report.report_description %}<p class="text-muted mt-1">{{ report.report_description }}</p>{% endif %}{% endblock %}
{% block extrajs %}
{{ block.super }}
{% include "slick_reporting/js_resources.html" %}
{% endblock %}
================================================
FILE: demo_proj/templates/slick_reporting/report_form.html
================================================
{% load i18n crispy_forms_tags slick_reporting_demo_tags %}
<form id="reportForm" class="card">
<div class="card-header">
<h3 class="card-title">{% translate "Filters" %}</h3>
</div>
<div class="card-body">
{% if form and crispy_helper %}
{% crispy form crispy_helper %}
{% else %}
{% crispy form %}
{% endif %}
</div>
<div class="card-footer d-flex justify-content-between align-items-center">
<button type="button" class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal" data-bs-target="#sourceCodeModal">
</> {% translate "View Source" %}
</button>
<div>
<input type="submit" value="{% translate "Filter" %}"
class="btn btn-primary refreshReport" data-get-results-button>
{% for export_action in report.get_export_actions %}
<button class="btn {{ export_action.css_class }}" data-export-btn
data-export-parameter="{{ export_action.parameter }}"
data-form-selector="#reportForm"
{% if export_action.new_window %}data-export-new-window="true"{% endif %}>
{% if export_action.icon %}<i class="{{ export_action.icon }}"></i> {% endif %}
{{ export_action.title }}
</button>
{% endfor %}
</div>
</div>
</form>
{% get_report_source report as source_code %}
{% get_report_class_label report as class_label %}
<div class="modal modal-blur fade" id="sourceCodeModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title font-monospace">{{ class_label }}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{% translate "Close" %}"></button>
</div>
<div class="modal-body p-0">
<pre class="mb-0"><code class="language-python">{{ source_code }}</code></pre>
</div>
</div>
</div>
</div>
================================================
FILE: demo_proj/templates/widget_template_with_pre.html
================================================
{% extends "slick_reporting/widget_template.html" %}
{% block widget_content %}
<div>
<pre id="responsePre"></pre>
</div>
{% endblock %}
================================================
FILE: docs/requirements.txt
================================================
-r ../requirements.txt
crispy_bootstrap4
sphinx
sphinx_rtd_theme==1.3.0
readthedocs-sphinx-search==0.3.1
================================================
FILE: docs/source/concept.rst
================================================
.. _structure:
Welcome to Django Slick Reporting documentation!
==================================================
Django Slick Reporting a reporting engine allowing you to create and chart different kind of analytics from your model in a breeze.
Demo site
---------
If you haven't yet, please check https://django-slick-reporting.com for a quick walk-though with live code examples..
:ref:`Tutorial <tutorial>`
--------------------------
The tutorial will guide you to what is slick reporting, what kind of reports it can do for you and how to use it in your project.
:ref:`Topic Guides <topics>`
----------------------------
Discuss each type of report main structures you can create with Django Slick Reporting and their options.
* :ref:`Group By report <group_by_topic>`: Similar to what we'd do with a GROUP BY sql statement. We group by a field and do some kind of calculations over the grouped records.
* :ref:`time_series`: A step further, where the calculations are computed for time periods (day, week, month, custom etc).
* :ref:`crosstab_reports`: Where the results shows the relationship between two or more variables. It's a table that shows the distribution of one variable in rows and another in columns.
* :ref:`list_reports`: Similar to a django admin's changelist, it's a direct view of the report model records
* And other topics like how to customize the form, and extend the exporting options.
:ref:`Reference <reference>`
----------------------------
Detailed information about main on Django Slick Reporting's main components
#. :ref:`Settings <settings>`: The settings you can use to customize the behavior of Django Slick Reporting.
#. :ref:`Report View <report_view_options>`: A ``FormView`` CBV subclass with reporting capabilities allowing you to create different types of reports in the view.
It provide a default :ref:`Filter Form <filter_form>` to filter the report on.
It mimics the Generator API interface, so knowing one is enough to work with the other.
#. :ref:`Generator <report_generator>`: Responsible for generating report and orchestrating and calculating the computation fields values and mapping them to the results.
It has an intuitive API that allows you to define the report structure and the computation fields to be calculated.
#. :ref:`Computation Field <computation_field>`: a calculation unit,like a Sum or a Count of a certain field.
Computation field class set how the calculation should be done. ComputationFields can also depend on each other.
#. Charting JS helpers: Highcharts and Charts js helpers libraries to plot the data generated. so you can create the chart in 1 line in the view
================================================
FILE: docs/source/conf.py
================================================
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
import django
sys.path.insert(0, os.path.abspath("../../"))
os.environ["DJANGO_SETTINGS_MODULE"] = "tests.settings"
django.setup()
# -- Project information -----------------------------------------------------
project = "Django Slick Reporting"
copyright = "2020, Ramez Ashraf"
author = "Ramez Ashraf"
master_doc = "index"
# The full version, including alpha/beta/rc tags
release = "0.6.8"
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
autosummary_generate = True
autoclass_content = "class"
extensions = [
"sphinx.ext.viewcode",
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
================================================
FILE: docs/source/howto/customize_frontend.rst
================================================
Charting and Front End Customization
=====================================
The ajax response structure
---------------------------
Understanding how the response is structured is imperative in order to customize how the report is displayed on the front end
Let's have a look
.. code-block:: python
# Ajax response or `report_results` template context variable.
response = {
# the report slug, defaults to the class name all lower
"report_slug": "",
# a list of objects representing the actual results of the report
"data": [
{
"name": "Product 1",
"quantity__sum": "1774",
"value__sum": "8758",
"field_x": "value_x",
},
{
"name": "Product 2",
"quantity__sum": "1878",
"value__sum": "3000",
"field_x": "value_x",
},
# etc .....
],
# A list explaining the columns/keys in the data results.
# ie: len(response.columns) == len(response.data[i].keys())
# It contains needed information about verbose name , if summable and hints about the data type.
"columns": [
{
"name": "name",
"computation_field": "",
"verbose_name": "Name",
"visible": True,
"type": "CharField",
"is_summable": False,
},
{
"name": "quantity__sum",
"computation_field": "",
"verbose_name": "Quantities Sold",
"visible": True,
"type": "number",
"is_summable": True,
},
{
"name": "value__sum",
"computation_field": "",
"verbose_name": "Value $",
"visible": True,
"type": "number",
"is_summable": True,
},
],
# Contains information about the report as whole if it's time series or a a crosstab
# And what's the actual and verbose names of the time series or crosstab specific columns.
"metadata": {
"time_series_pattern": "",
"time_series_column_names": [],
"time_series_column_verbose_names": [],
"crosstab_model": "",
"crosstab_column_names": [],
"crosstab_column_verbose_names": [],
},
# A mirror of the set charts_settings on the ReportView
# ``ReportView`` populates the id and the `engine_name' if not set
"chart_settings": [
{
"type": "pie",
"engine_name": "highcharts",
"data_source": ["quantity__sum"],
"title_source": ["name"],
"title": "Pie Chart (Quantities)",
"id": "pie-0",
},
{
"type": "bar",
"engine_name": "chartsjs",
"data_source": ["value__sum"],
"title_source": ["name"],
"title": "Column Chart (Values)",
"id": "bar-1",
},
],
}
The ajax response structure
---------------------------
Understanding how the response is structured is imperative in order to customize how the report is displayed on the front end
Let's have a look
.. code-block:: python
# Ajax response or `report_results` template context variable.
response = {
"report_slug": "", # the report slug, defaults to the class name all lower
"data": [], # a list of objects representing the actual results of the report
"columns": [], # A list explaining the columns/keys in the data results.
# ie: len(response.columns) == len(response.data[i].keys())
# A List of objects. each object contain field needed information like verbose name , if summable and hints about the data type.
"metadata": {}, # Contains information about the report as whole if it's time series or a a crosstab
# And what's the actual and verbose names of the time series or crosstab specific columns.
"chart_settings": [], # a list of objects mirror of the set charts_settings
}
================================================
FILE: docs/source/howto/index.rst
================================================
.. _how_to:
=======
How To
=======
In this section we will go over some of the frequent tasks you will need to do when using ReportView.
Customize the form
==================
The filter form is automatically generated for convenience
but you can override it and add your own Form.
The system expect that the form used with the ``ReportView`` to implement the ``slick_reporting.forms.BaseReportForm`` interface.
The interface is simple, only 3 mandatory methods to implement, The rest are mandatory only if you are working with a crosstab report or a time series report.
#. get_filters: return the filters to be used in the report in a tuple
The first element is a list of Q filters (is any)
The second element is a dict of filters to be used in the queryset
These filters will be passed to the report_model.objects.filter(*q_filters, **kw_filters)
#. get_start_date: return the start date to be used in the report
#. get_end_date: return the end date to be used in the report
.. code-block:: python
# forms.py
from slick_reporting.forms import BaseReportForm
class RequestFilterForm(BaseReportForm, forms.Form):
SECURE_CHOICES = (
("all", "All"),
("secure", "Secure"),
("non-secure", "Not Secure"),
)
start_date = forms.DateField(
required=False,
label="Start Date",
widget=forms.DateInput(attrs={"type": "date"}),
)
end_date = forms.DateField(
required=False, label="End Date", widget=forms.DateInput(attrs={"type": "date"})
)
secure = forms.ChoiceField(
choices=SECURE_CHOICES, required=False, label="Secure", initial="all"
)
method = forms.CharField(required=False, label="Method")
other_people_only = forms.BooleanField(
required=False, label="Show requests from other People Only"
)
def __init__(self, request=None, *args, **kwargs):
self.request = request
super().__init__(*args, **kwargs)
self.fields["start_date"].initial = datetime.date.today()
self.fields["end_date"].initial = datetime.date.today()
def get_filters(self):
q_filters = []
kw_filters = {}
if self.cleaned_data["secure"] == "secure":
kw_filters["is_secure"] = True
elif self.cleaned_data["secure"] == "non-secure":
kw_filters["is_secure"] = False
if self.cleaned_data["method"]:
kw_filters["method"] = self.cleaned_data["method"]
if self.cleaned_data["response"]:
kw_filters["response"] = self.cleaned_data["response"]
if self.cleaned_data["other_people_only"]:
q_filters.append(~Q(user=self.request.user))
return q_filters, kw_filters
def get_start_date(self):
return self.cleaned_data["start_date"]
def get_end_date(self):
return self.cleaned_data["end_date"]
For a complete reference of the ``BaseReportForm`` interface, check :ref:`filter_form_customization`
Use the report view in our own template
---------------------------------------
To use the report template with your own project templates, you simply need to override the ``slick_reporting/base.html`` template to make it extends your own base template
You only need to have a ``{% block content %}`` in your base template to be able to use the report template
and a ``{% block extrajs %}`` block to add the javascript implementation.
The example below assumes you have a ``base.html`` template in your project templates folder and have a content block and a project_extrajs block in it.
.. code-block:: html
{% extends "base.html" %}
{% load static %}
{% block content %}
{% endblock %}
{% block project_extrajs %}
{% include "slick_reporting/js_resources.html" %}
{% block extrajs %}
{% endblock %}
{% endblock %}
Work with tree data & Nested categories
---------------------------------------
Change the report structure in response to User input
-----------------------------------------------------
Create your own Chart Engine
-----------------------------
Create a Custom ComputationField and reuse it
---------------------------------------------
Add a new chart engine
----------------------
Add an exporting option
-----------------------
Work with categorical data
--------------------------
How to create a custom ComputationField
---------------------------------------
create custom columns
---------------------
format numbers in the datatable
custom group by
custom time series periods
custom crosstab reports
.. toctree::
:maxdepth: 2
:caption: Topics:
:titlesonly:
customize_frontend
================================================
FILE: docs/source/index.rst
================================================
Django Slick Reporting
======================
**Django Slick Reporting** a reporting engine allowing you to create & display diverse analytics. Batteries like a ready to use View and Highcharts & Charts.js integration are included.
* Create group by , crosstab , timeseries, crosstab in timeseries and list reports in handful line with intuitive syntax
* Highcharts & Charts.js integration ready to use with the shipped in View, easily extendable to use with your own charts.
* Export to CSV
* Easily extendable to add your own computation fields,
Installation
------------
To install django-slick-reporting with pip
.. code-block:: bash
pip install django-slick-reporting
Usage
-----
#. Add ``"slick_reporting", "crispy_forms", "crispy_bootstrap4",`` to ``INSTALLED_APPS``.
#. Add ``CRISPY_TEMPLATE_PACK = "bootstrap4"`` to your ``settings.py``
#. Execute `python manage.py collectstatic` so the JS helpers are collected and served.
Quickstart
----------
You can start by using ``ReportView`` which is a subclass of ``django.views.generic.FormView``
.. code-block:: python
# in views.py
from slick_reporting.views import ReportView, Chart
from slick_reporting.fields import ComputationField
from .models import MySalesItems
from django.db.models import Sum
class ProductSales(ReportView):
report_model = MySalesItems
date_field = "date_placed"
group_by = "product"
columns = [
"title",
ComputationField.create(
method=Sum, field="value", name="value__sum", verbose_name="Total sold $"
),
]
# Charts
chart_settings = [
Chart(
"Total sold $",
Chart.BAR,
data_source=["value__sum"],
title_source=["title"],
),
]
# in urls.py
from django.urls import path
from .views import ProductSales
urlpatterns = [
path("product-sales/", ProductSales.as_view(), name="product-sales"),
]
Demo site
----------
https://django-slick-reporting.com is a quick walk-though with live code examples
Next step :ref:`tutorial`
.. toctree::
:maxdepth: 2
:caption: Contents:
concept
tutorial
topics/index
ref/index
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
================================================
FILE: docs/source/ref/computation_field.rst
================================================
.. _computation_field_ref:
ComputationField API
--------------------
.. autoclass:: slick_reporting.fields.ComputationField
.. autoattribute:: name
.. autoattribute:: calculation_field
.. autoattribute:: calculation_method
.. autoattribute:: verbose_name
.. autoattribute:: requires
.. autoattribute:: type
.. rubric:: Below are some data passed by the `ReportGenerator`, for extra manipulation, you can change them
.. autoattribute:: report_model
.. autoattribute:: group_by
.. autoattribute:: plus_side_q
.. autoattribute:: minus_side_q
.. rubric:: You can customize those methods for maximum control where you can do pretty much whatever you want.
.. automethod:: prepare
.. automethod:: resolve
.. automethod:: get_dependency_value
================================================
FILE: docs/source/ref/dynamic_model.rst
================================================
.. _dynamic_model_ref:
=============
Dynamic Model
=============
.. module:: slick_reporting.dynamic_model
``get_dynamic_model``
---------------------
.. function:: get_dynamic_model(table_name, database="default", schema=None)
Introspect a database table and return a Django model class for it.
The returned model has ``managed = False`` and is fully compatible with the Django ORM.
Results are cached so repeated calls return the same class.
:param table_name: The database table name to introspect.
:type table_name: str
:param database: The database alias from ``DATABASES`` setting.
:type database: str
:param schema: Optional schema name (PostgreSQL). The schema must be in the
connection's ``search_path``. When provided, ``db_table`` is set to
``"schema"."table_name"``.
:type schema: str or None
:returns: A Django model class mapped to the table.
:rtype: type (subclass of ``django.db.models.Model``)
:raises ValueError: If the table does not exist.
``table_name`` attribute
------------------------
Both ``ReportGenerator`` and ``ReportView`` accept a ``table_name`` parameter.
When set (and ``report_model`` is not), ``get_dynamic_model`` is called automatically.
.. code-block:: python
class MyReport(ReportView):
table_name = "legacy_sales"
date_field = "sale_date"
group_by = "region"
columns = [...]
# Or via ReportGenerator init
report = ReportGenerator(table_name="legacy_sales", ...)
See :ref:`dynamic_model_topic` for full usage guide and examples.
================================================
FILE: docs/source/ref/index.rst
================================================
.. _reference:
Reference
===========
Below are links to the reference documentation for the various components of the Django slick reporting .
.. toctree::
:maxdepth: 2
:caption: Components:
settings
view_options
computation_field
report_generator
dynamic_model
================================================
FILE: docs/source/ref/report_generator.rst
================================================
.. _report_generator:
Report Generator API
====================
The main class responsible generating the report and managing the flow
ReportGenerator
---------------
.. autoclass:: slick_reporting.generator.ReportGenerator
.. rubric:: Below are the basic needed attrs
.. autoattribute:: report_model
.. autoattribute:: queryset
.. autoattribute:: date_field
.. autoattribute:: columns
.. autoattribute:: group_by
.. rubric:: Below are the needed attrs and methods for time series manipulation
.. autoattribute:: time_series_pattern
.. autoattribute:: time_series_columns
.. automethod:: get_custom_time_series_dates
.. automethod:: get_time_series_field_verbose_name
.. rubric:: Below are the needed attrs and methods for crosstab manipulation
.. autoattribute:: crosstab_field
.. autoattribute:: crosstab_columns
.. autoattribute:: crosstab_ids
.. autoattribute:: crosstab_compute_remainder
.. automethod:: get_crosstab_field_verbose_name
.. rubric:: Below are the magical attrs
.. autoattribute:: limit_records
.. autoattribute:: swap_sign
.. autoattribute:: field_registry_class
================================================
FILE: docs/source/ref/settings.rst
================================================
.. _settings:
Settings
========
.. note::
Settings are changed in version 1.1.1 to being a dictionary instead of individual variables.
Variables will continue to work till next major release.
Below are the default settings for django-slick-reporting. You can override them in your settings file.
.. code-block:: python
SLICK_REPORTING_SETTINGS = {
"JQUERY_URL": "https://code.jquery.com/jquery-3.7.0.min.js",
"DEFAULT_START_DATE_TIME": datetime(
datetime.now().year, 1, 1, 0, 0, 0, tzinfo=timezone.utc
), # Default: 1st Jan of current year
"DEFAULT_END_DATE_TIME": datetime.datetime.today(), # Default to today
"DEFAULT_CHARTS_ENGINE": SLICK_REPORTING_DEFAULT_CHARTS_ENGINE,
"MEDIA": {
"override": False, # set it to True to override the media files,
# False will append the media files to the existing ones.
"js": (
"https://cdn.jsdelivr.net/momentjs/latest/moment.min.js",
"https://cdn.datatables.net/1.13.4/js/jquery.dataTables.min.js",
"https://cdn.datatables.net/1.13.4/js/dataTables.bootstrap5.min.js",
"slick_reporting/slick_reporting.js",
"slick_reporting/slick_reporting.report_loader.js",
"slick_reporting/slick_reporting.datatable.js",
),
"css": {
"all": (
"https://cdn.datatables.net/1.13.4/css/dataTables.bootstrap5.min.css",
)
},
},
"FONT_AWESOME": {
"CSS_URL": "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css",
"ICONS": {
"pie": "fas fa-chart-pie",
"bar": "fas fa-chart-bar",
"line": "fas fa-chart-line",
"area": "fas fa-chart-area",
"column": "fas fa-chart-column",
},
},
"CHARTS": {
"highcharts": "$.slick_reporting.highcharts.displayChart",
"chartjs": "$.slick_reporting.chartjs.displayChart",
},
"MESSAGES": {
"total": _("Total"),
},
}
* JQUERY_URL:
Link to the jquery file, You can use set it to False and manage the jQuery addition to your liking
* DEFAULT_START_DATE_TIME
Default date time that would appear on the filter form in the start date
* DEFAULT_END_DATE_TIME
Default date time that would appear on the filter form in the end date
* FONT_AWESOME:
Font awesome is used to display the icon next to the chart title. You can override the following settings:
1. ``CSS_URL``: URL to the font-awesome css file
2. ``ICONS``: Icons used for different chart types.
* CHARTS:
The entry points for displaying charts on the front end.
You can add your own chart engine by adding an entry to this dictionary.
* MESSAGES:
The strings used in the front end. You can override them here, it also gives a chance to set and translate them per your requirements.
Old versions settings:
1. ``SLICK_REPORTING_DEFAULT_START_DATE``: Default: the beginning of the current year
2. ``SLICK_REPORTING_DEFAULT_END_DATE``: Default: the end of the current year.
3. ``SLICK_REPORTING_FORM_MEDIA``: Controls the media files required by the search form.
Defaults is:
.. code-block:: python
SLICK_REPORTING_FORM_MEDIA = {
"css": {
"all": (
"https://cdn.datatables.net/v/bs4/dt-1.10.20/datatables.min.css",
"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.css",
)
},
"js": (
"https://code.jquery.com/jquery-3.3.1.slim.min.js",
"https://cdn.datatables.net/v/bs4/dt-1.10.20/datatables.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js",
"https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js",
"https://code.highcharts.com/highcharts.js",
),
}
4. ``SLICK_REPORTING_DEFAULT_CHARTS_ENGINE``: Controls the default chart engine used.
================================================
FILE: docs/source/ref/view_options.rst
================================================
.. _report_view_options:
================
The Report View
================
Below is the list of options that can be used in the ReportView class.
Core Options
=============
report_model
------------
The model where the relevant data is stored, in more complex reports,
it's usually a database view / materialized view.
You can customize it at runtime via the ``get_report_model`` hook.
.. code-block:: python
class MyReportView(ReportView):
def get_report_model(self):
from my_app.models import MyReportModel
return MyReportModel.objects.filter(some_field__isnull=False)
queryset
--------
The queryset to be used in the report,
if not specified, it will default to ``report_model._default_manager.all()``
group_by
--------
If the data in the report_model needs to be grouped by a field.
It can be a foreign key, a text field / choice field on the report model or traversing.
Example:
Assuming we have the following SalesModel
.. code-block:: python
class SalesModel(models.Model):
date = models.DateTimeField()
notes = models.TextField(blank=True, null=True)
client = models.ForeignKey(
"client.Client", on_delete=models.PROTECT, verbose_name=_("Client")
)
product = models.ForeignKey(
"product.Product", on_delete=models.PROTECT, verbose_name=_("Product")
)
value = models.DecimalField(max_digits=9, decimal_places=2)
quantity = models.DecimalField(max_digits=9, decimal_places=2)
price = models.DecimalField(max_digits=9, decimal_places=2)
Our ReportView can have the following group_by options:
.. code-block:: python
from slick_reporting.views import ReportView
class MyReport(ReportView):
report_model = SalesModel
group_by = "product" # a field on the model
# OR
# group_by = 'client__country' a traversing foreign key field
# group_by = 'client__gender' a traversing choice field
columns
-------
Columns are a list of column names and to make it more flexible,
you can pass a tuple of column name and options.
The options are only `verbose_name` and `is_summable`.
like this:
.. code-block:: python
class MyReport(ReportView):
columns = [
"id",
("name", {"verbose_name": "My verbose name", "is_summable": False}),
]
A column name can be any of the following:
1. A computation field
2. A field on the grouped by model
3. A callable on the view /or the generator
4. A Special ``__time_series__``, ``__crosstab__``, ``__index__``
Let's take them one by one:
1. A Computation Field.
~~~~~~~~~~~~~~~~~~~~~~~~~~
Added as a class or by its name.
Example:
.. code-block:: python
from slick_reporting.fields import ComputationField, Sum
from slick_reporting.registry import field_registry
from slick_reporting.views import ReportView
@field_registry.register
class MyTotalReportField(ComputationField):
name = "__some_special_name__"
class MyReport(ReportView):
columns = [
ComputationField.create(Sum, "value", verbose_name=_("Value"), name="value"),
# a computation field created on the fly
MyTotalReportField, # Added a a class
"__some_special_name__", # added by name
]
For more information: :ref:`computation_field`
2. Fields on the group by model
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Implying that the group_by is set to a field on the report_model.
.. code-block:: python
class MyReport(ReportView):
report_model = SalesModel
group_by = "client"
columns = [
"name", # field that exists on the Client Model
"date_of_birth", # field that exists on the Client Model
"agent__name", # a traversing field from client model
# ...
]
# If the group_by is traversing then the available columns would be of the model at the end of the traversing
class MyOtherReport(ReportView):
report_model = MySales
group_by = "client__agent"
columns = [
"name",
"country", # fields that exists on the Agent Model
"contact__email", # A traversing field from the Agent model
]
.. note::
If group_by is not set, columns can be only a calculation field. refer to the topic `no_group_by_topic`
3. A callable on the view
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The callable should accept the following arguments
:param obj: a dictionary of the current group_by row
:param row: a the current row of the report.
:return: the value to be displayed in the report
.. code-block:: python
class Report(ReportView):
columns = [ "field_on_group_by_model", "group_by_model__traversing_field",
"get_attribute", ComputationField.create(name="example")]
def get_attribute(self, obj: dict, row: dict):
# obj: a dictionary of the current group_by row
# row: a the current row of the report.
return f"{obj["field_on_group_by_model_2"]} - {row["group_by_model__traversing_field"]}"
get_attribute.verbose_name = "My awesome title"
4. A Special ``__time_series__``, ``__crosstab__``, ``__index__``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``__time_series__``: is used to control the position of the time series columns inside the report.
``__crosstab__``: is used to control the position of the crosstab columns inside the report.
``__index__``: is used to display the index of the report, it's usually used with the ``group_by_custom_querysets`` option.
date_field
----------
The date field to be used in filtering and computing
start_date_field_name
---------------------
The name of the start date field, if not specified, it will default to what set in ``date_field``
end_date_field_name
-------------------
The name of the end date field, if not specified, it will default to ``date_field``
chart_settings
--------------
A list of Chart objects representing the charts you want to attach to the report.
Example:
.. code-block:: python
class MyReport(ReportView):
report_model = Request
# ..
chart_settings = [
Chart(
title="Browsers",
type=Chart.PIE, # or just string "bar"
title_source=["user_agent"],
data_source=["count__id"],
plot_total=False,
),
Chart(
"Browsers Bar Chart",
Chart.BAR,
title_source=["user_agent"],
data_source=["count__id"],
plot_total=True,
),
]
form_class
----------
The form you need to display to control the results.
Default to an automatically generated form containing the start date, end date and all foreign keys on the model.
For more information: `filter_form`
excluded_fields
-----------------
Fields to be excluded from the automatically generated form
auto_load
--------------
Control if the report should be loaded automatically on page load or not, default to ``True``
``report_title``
----------------
The title of the report to be displayed in the report page.
``report_title_context_key``
----------------------------
The context key to be used to pass the report title to the template, default to ``report_title``.
``template_name``
-----------------
The template to be used to render the report, default to ``slick_reporting/report.html``
You can override this to customize the report look and feel.
``csv_export_class``
--------------------
Set the csv export class to be used to export the report, default to ``ExportToStreamingCSV``
``report_generator_class``
--------------------------
Set the generator class to be used to generate the report, default to ``ReportGenerator``
``default_order_by``
--------------------
A Default order by for the results.
As you would expect, for DESC order: default_order_by (or order_by as a parameter) ='-field_name'
.. note::
Ordering can also be controlled at run time by passing order_by='field_name' as a parameter to the view.
``limit_records``
-----------------
Limit the number of records to be displayed in the report, default to ``None`` (no limit)
``swap_sign``
--------------
Swap the sign of the values in the report, default to ``False``
Double Sided Calculations Options
==================================
.. attribute:: ReportView.with_type
Set if double sided calculations should be taken into account, default to ``False``
Read more about double sided calculations here https://django-erp-framework.readthedocs.io/en/latest/topics/doc_types.html
.. attribute:: ReportView.doc_type_field_name
Set the doc_type field name to be used in double sided calculations, default to ``doc_type``
.. attribute:: ReportView.doc_type_plus_list
Set the doc_type plus list to be used in double sided calculations, default to ``None``
.. attribute:: ReportView.doc_type_minus_list
Set the doc_type minus list to be used in double sided calculations, default to ``None``
Hooks and functions
====================
.. attribute:: ReportView.get_queryset()
Override this function to return a custom queryset to be used in the report.
.. attribute:: ReportView.get_report_title()
Override this function to return a custom report title.
.. attribute:: ReportView.ajax_render_to_response()
Override this function to return a custom response for ajax requests.
.. attribute:: ReportView.format_row()
Override this function to return a custom row format.
.. attribute:: ReportView.filter_results(data, for_print=False)
Hook to Filter results, usable if you want to do actions on the data set based on computed data (like eliminate __balance__ = 0, etc)
:param data: the data set , list of dictionaries
:param for_print: if the data is being filtered for printing or not
:return: the data set after filtering.
.. attribute:: ReportView.get_form_crispy_helper()
Override this function to return a custom crispy form helper for the report form.
================================================
FILE: docs/source/topics/charts.rst
================================================
Charts Customization
====================
Charts Configuration
---------------------
ReportView ``charts_settings`` is a list of objects which each object represent a chart configurations.
The chart configurations are:
* title: the Chart title. Defaults to the `report_title`.
* type: A string. Examples are pie, bar, line, etc ...
* engine_name: A string, default to the ReportView ``chart_engine`` attribute, then to the ``SLICK_REPORTING_SETTINGS.DEFAULT_CHARTS_ENGINE``.
* data_source: string, the field name containing the numbers we want to plot.
* title_source: string, the field name containing labels of the data_source
* plot_total: if True the chart will plot the total of the columns. Useful with time series and crosstab reports.
* entryPoint: the javascript entry point to display the chart, the entryPoint function accepts the data, $elem and the chartSettings parameters.
On front end, for each chart needed we pass the whole response to the relevant chart helper function and it handles the rest.
Customizing the entryPoint for a chart
--------------------------------------
Sometimes you want to display the chart differently, in this case, you can just change the entryPoint function.
Example:
.. code-block:: python
class ProductSalesApexChart(ReportView):
# ..
template_name = "product_sales_report.html"
chart_settings = [
# ..
Chart(
"Total sold $",
type="bar",
data_source=["value__sum"],
title_source=["name"],
entryPoint="displayChartCustomEntryPoint", # this is the new entryPoint
),
]
Then in your template `product_sales_report.html` add the javascript function specified as the new entryPoint.
.. code-block:: html+django
{% extends "slick_reporting/report.html" %}
{% load slick_reporting_tags %}
{% block extra_js %}
{{ block.super }}
<script>
function displayChartCustomEntryPoint(data, $elem, chartSettings) {
// data: is the ajax response coming from server
// $elem: is the jquery element where the chart should be rendered
// chartSettings: is the relevant chart dictionary/object in your ReportView chart_settings
// do your custom logic here
}
</script>
{% endblock %}
Adding a new charting engine
----------------------------
In the following part we will add some Apex charts to the demo app to demonstrate how you can add your own charting engine to slick reporting.
#. We need to add the new chart Engine to the settings. Note that the css and js are specified and handled like Django's ``Form.Media``
.. code-block:: python
SLICK_REPORTING_SETTINGS = {
"CHARTS": {
"apexcharts": {
"entryPoint": "DisplayApexPieChart",
"js": (
"https://cdn.jsdelivr.net/npm/apexcharts",
"js_file_for_apex_chart.js", # this file contains the entryPoint function and is responsible
# for compiling the data and rendering the chart
),
"css": {
"all": "https://cdn.jsdelivr.net/npm/apexcharts/dist/apexcharts.min.css"
},
}
},
}
#. Add the entry point function to the javascript file `js_file_for_apex_chart.js` in this example.
It can look something like this:
.. code-block:: javascript
let chart = null;
function DisplayApexPieChart(data, $elem, chartOptions) {
// Where:
// data: is the ajax response coming from server
// $elem: is the jquery element where the chart should be rendered
// chartOptions: is the relevant chart dictionary/object in your ReportView chart_settings
let legendAndSeries = $.slick_reporting.chartsjs.getGroupByLabelAndSeries(data, chartOptions);
// `getGroupByLabelAndSeries` is a helper function that will return an object with two keys: labels and series
let options = {}
if (chartOptions.type === "pie") {
options = {
series: legendAndSeries.series,
chart: {
type: "pie",
height: 350
},
labels: legendAndSeries.labels,
};
} else {
options = {
chart: {
type: 'bar'
},
series: [{
name: 'Sales',
data: legendAndSeries.series
}],
xaxis: {
categories: legendAndSeries.labels,
}
}
}
try {
// destroy old chart, if any
chart.destroy();
} catch (e) {
// do nothing
}
chart = new ApexCharts($elem[0], options);
chart.render();
}
================================================
FILE: docs/source/topics/computation_field.rst
================================================
.. _computation_field:
Computation Field
=================
ComputationFields are the basic unit in a report.they represent a number that is being computed.
Computation Fields can be add to a report as a class, as you saw in other examples , or by name.
Creating Computation Fields
---------------------------
There are 3 ways you can create a Computation Field
1. Create a subclass of ComputationField and set the needed attributes and use it in the columns attribute of the ReportView
2. Use the `ComputationField.create()` method and pass the needed attributes and use it in the columns attribute of the ReportView
3. Use the `report_field_register` decorator to register a ComputationField subclass and use it by its name in the columns attribute of the ReportView
.. code-block:: python
from slick_reporting.fields import ComputationField
from slick_reporting.decorators import report_field_register
@report_field_register
class TotalQTYReportField(ComputationField):
name = "__total_quantity__"
calculation_field = "quantity" # the field we want to compute on
calculation_method = Sum # What method we want, default to Sum
verbose_name = _("Total quantity")
class ProductSales(ReportView):
report_model = SalesTransaction
# ..
columns = [
# ...
"__total_quantity__", # Use the ComputationField by its registered name
TotalQTYReportField, # Use Computation Field as a class
ComputationField.create(
Sum, "quantity", name="__total_quantity__", verbose_name=_("Total quantity")
)
# Using the ComputationField.create() method
]
What happened here is that we:
1. Created a ComputationField subclass and gave it the needed attributes
2. Register it via ``report_field_register`` so it can be picked up by the framework.
3. Used it by name inside the columns attribute (or in time_series_columns, or in crosstab_columns)
4. Note that this is same as using the class directly in the columns , also the same as using `ComputationField.create()`
Another example, adding and AVG to the field `price`:
.. code-block:: python
from django.db.models import Avg
from slick_reporting.decorators import report_field_register
@report_field_register
class TotalQTYReportField(ComputationField):
name = "__avg_price__"
calculation_field = "price"
calculation_method = Avg
verbose_name = _("Avg. Price")
class ProductSales(ReportView):
# ..
columns = [
"name",
"__avg_price__",
]
Using Value of a Computation Field within a another
---------------------------------------------------
Sometime you want to stack values on top of each other. For example: Net revenue = Gross revenue - Discounts.
.. code-block:: python
class PercentageToTotalBalance(ComputationField):
requires = [BalanceReportField]
name = "__percent_to_total_balance__"
verbose_name = _("%")
calculation_method = Sum
calculation_field = "value"
prevent_group_by = True
def resolve(
self,
prepared_results,
required_computation_results: dict,
current_pk,
current_row=None,
) -> float:
result = super().resolve(
prepared_results, required_computation_results, current_pk, current_row
)
return required_computation_results.get("__balance__") / result * 100
We need to override ``resolve`` to do the needed calculation. The ``required_computation_results`` is a dictionary of the results of the required fields, where the keys are the names.
Note:
1. The ``requires`` attribute is a list of the required fields to be computed before this field.
2. The values of the ``requires`` fields are available in the ``required_computation_results`` dictionary.
3. In the example we used the ``prevent_group_by`` attribute. It's as the name sounds, it prevents the rows from being grouped for teh ComputationField giving us the result over the whole set.
How it works ?
--------------
When the `ReportGenerator` is initialized, it generates a list of the needed fields to be displayed and computed.
Each computation field in the report is given the filters needed and asked to get all the results prepared.
Then for each record, the ReportGenerator again asks each ComputationField to get the data it has for each record and map it back.
Customizing the Calculation Flow
--------------------------------
The results are prepared in 2 main stages
1. Preparation: Where you can get the whole result set for the report. Example: Sum of all the values in a model group by the products.
2. resolve: Where you get the value for each record.
.. code-block:: python
class MyCustomComputationField(ComputationField):
name = "__custom_field__"
def prepare(
self,
q_filters: list | object = None,
kwargs_filters: dict = None,
queryset=None,
**kwargs
):
# do all you calculation here for the whole set if any and return the prepared results
pass
def resolve(
self,
prepared_results,
required_computation_results: dict,
current_pk,
current_row=None,
) -> float:
# does the calculation for each record, return a value
pass
Bundled Report Fields
---------------------
As this project came form an ERP background, there are some bundled report fields that you can use out of the box.
* __total__ : `Sum` of the field named `value`
* __total_quantity__ : `Sum` of the field named `quantity`
* __fb__ : First Balance, Sum of the field `value` on the start date (or period in case of time series)
* __balance__: Compound Sum of the field `value`. IE: the sum of the field `value` on end date.
* __credit__: Sum of field Value for the minus_list
* __debit__: Sum of the field value for the plus list
* __percent_to_total_balance__: Percent of the field value to the balance
What is the difference between total and balance fields ?
Total: Sum of the value for the period
Balance: Sum of the value for the period + all the previous periods.
Example: You have a client who buys 10 in Jan., 12 in Feb. and 13 in March:
* `__total__` will return 10 in Jan, 12 in Feb and 13 in March.
* `__balance__` will return 10 in Jan, 22 in Feb and 35 in March
================================================
FILE: docs/source/topics/crosstab_options.rst
================================================
.. _crosstab_reports:
Crosstab Reports
=================
Use crosstab reports, also known as matrix reports, to show the relationships between three or more query items.
Crosstab reports show data in rows and columns with information summarized at the intersection points.
General use case
----------------
Here is a general use case:
.. code-block:: python
from django.utils.translation import gettext_lazy as _
from django.db.models import Sum
from slick_reporting.views import ReportView
class CrosstabReport(ReportView):
report_title = _("Cross tab Report")
report_model = SalesTransaction
group_by = "client"
date_field = "date"
columns = [
"name",
"__crosstab__",
# You can customize where the crosstab columns are displayed in relation to the other columns
ComputationField.create(Sum, "value", verbose_name=_("Total Value")),
# This is the same as the calculation in the crosstab,
# but this one will be on the whole set. IE total value.
]
crosstab_field = "product"
crosstab_columns = [
ComputationField.create(Sum, "value", verbose_name=_("Value")),
]
Crosstab on a Traversing Field
------------------------------
You can also crosstab on a traversing field. In the example below we extend the previous crosstab report to be on the product sizes
.. code-block:: python
class CrosstabWithTraversingField(CrosstabReport):
crosstab_field = "product__size"
Customizing the crosstab ids
----------------------------
You can set the default ids that you want to crosstab on, so the initial report, ie without user setting anything, comes out with the values you want
.. code-block:: python
class CrosstabWithIds(CrosstabReport):
def get_crosstab_ids(self):
return [Product.objects.first().pk, Product.objects.last().pk]
Customizing the Crosstab Filter
-------------------------------
For more fine tuned report, You can customize the crosstab report by supplying a list of tuples to the ``crosstab_ids_custom_filters`` attribute.
The tuple should have 2 items, the first is a list of Q object(s) -if any- , and the second is a dict of kwargs filters . Both will be passed to the filter method of the ``report_model``.
Example:
.. code-block:: python
class CrosstabWithIdsCustomFilter(CrosstabReport):
crosstab_ids_custom_filters = [
(~Q(product__size__in=["extra_big", "big"]), dict()),
(None, dict(product__size__in=["extra_big", "big"])),
]
# Note:
# if crosstab_ids_custom_filters is set, these settings has NO EFFECT
# crosstab_field = "client"
# crosstab_ids = [1, 2]
# crosstab_compute_remainder = True
Customizing the verbose name of the crosstab columns
----------------------------------------------------
Similar to what we did in customizing the verbose name of the computation field for the time series,
Here, We also can customize the verbose name of the crosstab columns by Subclass ``ComputationField`` and setting the ``crosstab_field_verbose_name`` attribute on your custom class.
Default is that the verbose name will display the id of the crosstab field, and the remainder column will be called "The remainder".
Let's see two examples on how we can customize the verbose name.
Example 1: On a "regular" crosstab report
.. code-block:: python
class CustomCrossTabTotalField(ComputationField):
calculation_field = "value"
calculation_method = Sum
verbose_name = _("Sales for")
name = "sum__value"
@classmethod
def get_crosstab_field_verbose_name(cls, model, id):
if id == "----": # 4 dashes: the remainder column
return _("Rest of Products")
name = Product.objects.get(pk=id).name
return f"{cls.verbose_name} {name}"
class CrossTabReportWithCustomVerboseName(CrosstabReport):
crosstab_columns = [CustomCrossTabTotalField]
Example 2: On the ``crosstab_ids_custom_filters`` one
.. code-block:: python
class CustomCrossTabTotalField2(CustomCrossTabTotalField):
@classmethod
def get_crosstab_field_verbose_name(cls, model, id):
if id == 0:
return f"{cls.verbose_name} Big and Extra Big"
return f"{cls.verbose_name} all other sizes"
class CrossTabReportWithCustomVerboseNameCustomFilter(CrosstabWithIdsCustomFilter):
crosstab_columns = [CustomCrossTabTotalField2]
Example
-------
.. image:: _static/crosstab.png
:width: 800
:alt: crosstab
:align: center
1. The Group By. In this example, it is the product field.
2. The Crosstab. In this example, it is the client field. crosstab_ids were set to client 1 and client 2
3. The remainder. In this example, it is the rest of the clients. crosstab_compute_remainder was set to True
================================================
FILE: docs/source/topics/dynamic_model.rst
================================================
.. _dynamic_model_topic:
==============
Dynamic Models
==============
General use case
----------------
Sometimes you need to generate reports from database tables that don't have a corresponding Django model.
This is common with:
* Legacy database tables
* Data warehouse / analytics tables
* Tables managed by other systems or ETL pipelines
* Temporary or staging tables
Django Slick Reporting provides ``get_dynamic_model`` which introspects any database table and creates
a real Django model at runtime. Since the result is a genuine Django model, all report features work
natively: group by, time series, crosstab, aggregation, filtering, and charts.
Basic usage
-----------
Use the ``get_dynamic_model`` utility to create a model from an existing table:
.. code-block:: python
from slick_reporting.dynamic_model import get_dynamic_model
# Introspect the table and get a Django model class
SalesData = get_dynamic_model("legacy_sales")
# Use it like any Django model
SalesData.objects.all()
SalesData.objects.filter(region="US").count()
Then use it with ``ReportGenerator`` or ``ReportView`` as usual:
.. code-block:: python
from slick_reporting.views import ReportView, Chart
from slick_reporting.fields import ComputationField
from slick_reporting.dynamic_model import get_dynamic_model
from django.db.models import Sum
SalesData = get_dynamic_model("legacy_sales")
class LegacySalesReport(ReportView):
report_model = SalesData
date_field = "sale_date"
group_by = "product_id"
columns = [
"product_id",
ComputationField.create(
method=Sum,
field="amount",
name="amount__sum",
verbose_name="Total Sales",
),
]
chart_settings = [
Chart(
"Total Sales",
Chart.BAR,
data_source=["amount__sum"],
title_source=["product_id"],
),
]
Using ``table_name`` shortcut
-----------------------------
Instead of calling ``get_dynamic_model`` yourself, you can set ``table_name`` directly on
``ReportView`` or pass it to ``ReportGenerator``:
.. code-block:: python
# On a view
class LegacySalesReport(ReportView):
table_name = "legacy_sales"
date_field = "sale_date"
group_by = "product_id"
columns = [
"product_id",
ComputationField.create(
method=Sum,
field="amount",
name="amount__sum",
verbose_name="Total Sales",
),
]
.. code-block:: python
# With ReportGenerator directly
from slick_reporting.generator import ReportGenerator
report = ReportGenerator(
table_name="legacy_sales",
date_field="sale_date",
group_by="product_id",
columns=[
"product_id",
ComputationField.create(Sum, "amount", name="amount__sum", verbose_name="Total Sales"),
],
start_date=start,
end_date=end,
)
data = report.get_report_data()
When ``table_name`` is set and ``report_model`` is not, the dynamic model is created automatically.
Using a different database
--------------------------
If the table is in a non-default database, pass the ``database`` parameter:
.. code-block:: python
WarehouseData = get_dynamic_model("fact_sales", database="warehouse")
This uses the database alias defined in your Django ``DATABASES`` setting.
Working with database schemas
-----------------------------
On PostgreSQL, tables can live in different schemas (e.g. ``analytics``, ``staging``).
**Prerequisites:** The schema must be accessible via the connection's ``search_path`` so Django's
introspection can find the table. You can configure this in your database settings:
.. code-block:: python
# settings.py
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "mydb",
"OPTIONS": {
"options": "-c search_path=analytics,public",
},
}
}
Then pass the ``schema`` parameter so ORM queries reference the correct schema:
.. code-block:: python
SalesData = get_dynamic_model("sales", schema="analytics")
This sets the model's ``db_table`` to ``"analytics"."sales"`` so all generated SQL uses the
schema-qualified table name.
**Schema support by database backend:**
+----------------+-------------------------------------------------------------------+
| Backend | How schemas work |
+================+===================================================================+
| PostgreSQL | Use ``schema`` parameter + ensure schema is in ``search_path`` |
+----------------+-------------------------------------------------------------------+
| MySQL | Schemas are databases. Use the ``database`` parameter instead |
+----------------+-------------------------------------------------------------------+
| SQLite | No schema concept. Just use ``table_name`` |
+----------------+-------------------------------------------------------------------+
How it works
------------
``get_dynamic_model`` performs the following steps:
1. Connects to the database and verifies the table exists.
2. Uses Django's ``connection.introspection`` to read column names, types, and constraints.
3. Maps each database column to the appropriate Django field (``IntegerField``, ``CharField``, ``DateField``, etc.).
4. Creates a Django model class at runtime with ``managed = False`` (no migrations needed).
5. Registers the model in Django's app registry so the ORM works fully.
6. Caches the result so subsequent calls for the same table return the same model class.
Since the result is a standard Django model, all ``ReportGenerator`` features work without
any special handling: ``group_by``, ``time_series_pattern``, ``crosstab_field``, ``ComputationField``,
form generation, and chart rendering.
Caching
-------
Dynamic models are cached in memory. Calling ``get_dynamic_model("my_table")`` multiple times
returns the same model class. The cache key includes the database alias and schema, so models
for different databases or schemas are cached separately.
Limitations
-----------
* **No foreign key relationships.** Foreign key columns are introspected as plain integer or
varchar fields. This means ``group_by`` traversal across relationships (e.g. ``product__category``)
is not available. You can group by the FK column directly (e.g. ``product_id``).
* **No automatic form filters for FK fields.** Since there are no FK relations, the auto-generated
filter form will only contain date range fields. Supply a custom ``form_class`` if you need
additional filters.
* **Schema must be in search_path.** On PostgreSQL, Django's introspection only finds tables
visible in the current ``search_path``. The schema must be configured at the connection level.
* **Table structure changes require restart.** Because models are cached, if the table structure
changes (columns added/removed), you need to restart the application or clear the cache.
================================================
FILE: docs/source/topics/exporting.rst
================================================
Exporting
=========
Exporting to CSV
-----------------
To trigger an export to CSV, just add ``?_export=csv`` to the url. This is performed by by the Export to CSV button in the default form.
This will call the export_csv on the view class, engaging a `ExportToStreamingCSV`
Having an `_export` parameter not implemented, ie the view class do not implement ``export_{parameter_name}``, will be ignored.
Configuring the CSV export option
---------------------------------
You can disable the CSV export option by setting the ``csv_export_class`` attribute to ``False`` on the view class.
and you can override the function and its attributes to customize the button text
.. code-block:: python
class CustomExportReport(GroupByReport):
report_title = _("Custom Export Report")
def export_csv(self, report_data):
return super().export_csv(report_data)
export_csv.title = _("My Custom CSV export Title")
export_csv.css_class = "btn btn-success"
Adding an export option
-----------------------
You can extend the functionality, say you want to export to pdf.
Add a ``export_pdf`` method to the view class, accepting the report_data json response and return the response you want.
This ``export_pdf` will be called automatically when url parameter contain ``?_export=pdf``
Example to add a pdf export option:
.. code-block:: python
class CustomExportReport(GroupByReport):
report_title = _("Custom Export Report")
export_actions = ["export_pdf"]
def export_pdf(self, report_data):
return HttpResponse(f"Dummy PDF Exported {report_data}")
export_pdf.title = _("Export PDF")
export_pdf.icon = "fa fa-file-pdf-o"
export_pdf.css_class = "btn btn-primary"
The export function should accept the report_data json response and return the response you want.
================================================
FILE: docs/source/topics/filter_form.rst
================================================
.. _filter_form:
Customizing Filter Form
=======================
The filter form is a form that is used to filter the data to be used in the report.
The gener
gitextract_vuqer0hb/
├── .github/
│ └── workflows/
│ ├── django.yml
│ └── release.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .readthedocs.yaml
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE.md
├── MANIFEST.in
├── README.rst
├── demo_proj/
│ ├── demo_app/
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── forms.py
│ │ ├── helpers.py
│ │ ├── management/
│ │ │ └── commands/
│ │ │ └── create_entries.py
│ │ ├── migrations/
│ │ │ ├── 0001_initial.py
│ │ │ ├── 0002_salestransaction_price_salestransaction_quantity.py
│ │ │ ├── 0003_product_category.py
│ │ │ ├── 0004_client_country_product_sku.py
│ │ │ ├── 0005_product_size.py
│ │ │ ├── 0006_productcategory_remove_product_category_and_more.py
│ │ │ ├── 0007_monthlysalessummary.py
│ │ │ └── __init__.py
│ │ ├── models.py
│ │ ├── reports.py
│ │ ├── templatetags/
│ │ │ ├── __init__.py
│ │ │ └── slick_reporting_demo_tags.py
│ │ ├── tests.py
│ │ └── views.py
│ ├── demo_proj/
│ │ ├── __init__.py
│ │ ├── asgi.py
│ │ ├── settings.py
│ │ ├── urls.py
│ │ └── wsgi.py
│ ├── manage.py
│ ├── requirements.txt
│ └── templates/
│ ├── base.html
│ ├── dashboard.html
│ ├── demo/
│ │ └── apex_report.html
│ ├── home.html
│ ├── menu.html
│ ├── slick_reporting/
│ │ ├── base.html
│ │ └── report_form.html
│ └── widget_template_with_pre.html
├── docs/
│ ├── requirements.txt
│ └── source/
│ ├── concept.rst
│ ├── conf.py
│ ├── howto/
│ │ ├── customize_frontend.rst
│ │ └── index.rst
│ ├── index.rst
│ ├── ref/
│ │ ├── computation_field.rst
│ │ ├── dynamic_model.rst
│ │ ├── index.rst
│ │ ├── report_generator.rst
│ │ ├── settings.rst
│ │ └── view_options.rst
│ ├── topics/
│ │ ├── charts.rst
│ │ ├── computation_field.rst
│ │ ├── crosstab_options.rst
│ │ ├── dynamic_model.rst
│ │ ├── exporting.rst
│ │ ├── filter_form.rst
│ │ ├── group_by_report.rst
│ │ ├── index.rst
│ │ ├── integrating_slick_reporting.rst
│ │ ├── list_report_options.rst
│ │ ├── pivot_report.rst
│ │ ├── structure.rst
│ │ ├── time_series_options.rst
│ │ └── widgets.rst
│ ├── tour.rst
│ └── tutorial.rst
├── pyproject.toml
├── requirements.txt
├── runtests.py
├── scripts/
│ └── extract_changelog.py
├── setup.cfg
├── setup.py
├── slick_reporting/
│ ├── __init__.py
│ ├── app_settings.py
│ ├── apps.py
│ ├── decorators.py
│ ├── dynamic_model.py
│ ├── fields.py
│ ├── form_factory.py
│ ├── forms.py
│ ├── generator.py
│ ├── helpers.py
│ ├── locale/
│ │ ├── ar/
│ │ │ └── LC_MESSAGES/
│ │ │ └── django.po
│ │ └── de/
│ │ └── LC_MESSAGES/
│ │ └── django.po
│ ├── registry.py
│ ├── static/
│ │ └── slick_reporting/
│ │ ├── slick_reporting.chartsjs.js
│ │ ├── slick_reporting.datatable.js
│ │ ├── slick_reporting.highchart.js
│ │ ├── slick_reporting.js
│ │ └── slick_reporting.report_loader.js
│ ├── templates/
│ │ └── slick_reporting/
│ │ ├── base.html
│ │ ├── js_resources.html
│ │ ├── print_report.html
│ │ ├── print_report_controls.html
│ │ ├── print_report_footer.html
│ │ ├── print_report_header.html
│ │ ├── report.html
│ │ ├── report_form.html
│ │ └── widget_template.html
│ ├── templatetags/
│ │ ├── __init__.py
│ │ └── slick_reporting_tags.py
│ └── views.py
└── tests/
├── __init__.py
├── models.py
├── report_generators.py
├── requirements.txt
├── settings.py
├── templates/
│ └── base.html
├── test_dynamic_model.py
├── test_generator.py
├── test_pivot_generator.py
├── tests.py
├── urls.py
└── views.py
SYMBOL INDEX (527 symbols across 41 files)
FILE: demo_proj/demo_app/apps.py
class DemoAppConfig (line 4) | class DemoAppConfig(AppConfig):
FILE: demo_proj/demo_app/forms.py
class TotalSalesFilterForm (line 6) | class TotalSalesFilterForm(BaseReportForm, forms.Form):
method get_filters (line 26) | def get_filters(self):
method get_start_date (line 41) | def get_start_date(self):
method get_end_date (line 44) | def get_end_date(self):
FILE: demo_proj/demo_app/helpers.py
function get_urls_patterns (line 55) | def get_urls_patterns():
FILE: demo_proj/demo_app/management/commands/create_entries.py
function date_range (line 14) | def date_range(start_date, end_date):
class Command (line 19) | class Command(BaseCommand):
method handle (line 22) | def handle(self, *args, **options):
FILE: demo_proj/demo_app/migrations/0001_initial.py
class Migration (line 7) | class Migration(migrations.Migration):
FILE: demo_proj/demo_app/migrations/0002_salestransaction_price_salestransaction_quantity.py
class Migration (line 6) | class Migration(migrations.Migration):
FILE: demo_proj/demo_app/migrations/0003_product_category.py
class Migration (line 6) | class Migration(migrations.Migration):
FILE: demo_proj/demo_app/migrations/0004_client_country_product_sku.py
class Migration (line 7) | class Migration(migrations.Migration):
FILE: demo_proj/demo_app/migrations/0005_product_size.py
class Migration (line 6) | class Migration(migrations.Migration):
FILE: demo_proj/demo_app/migrations/0006_productcategory_remove_product_category_and_more.py
class Migration (line 7) | class Migration(migrations.Migration):
FILE: demo_proj/demo_app/migrations/0007_monthlysalessummary.py
class Migration (line 7) | class Migration(migrations.Migration):
FILE: demo_proj/demo_app/models.py
class Client (line 8) | class Client(models.Model):
class Meta (line 12) | class Meta:
method __str__ (line 16) | def __str__(self):
class ProductCategory (line 20) | class ProductCategory(models.Model):
method __str__ (line 23) | def __str__(self):
class Product (line 27) | class Product(models.Model):
class Meta (line 35) | class Meta:
method __str__ (line 39) | def __str__(self):
class SalesTransaction (line 43) | class SalesTransaction(models.Model):
class Meta (line 57) | class Meta:
method __str__ (line 61) | def __str__(self):
method save (line 64) | def save(
class MonthlySalesSummary (line 71) | class MonthlySalesSummary(models.Model):
class Meta (line 79) | class Meta:
method __str__ (line 84) | def __str__(self):
FILE: demo_proj/demo_app/reports.py
class ProductSales (line 14) | class ProductSales(ReportView):
class TotalProductSales (line 44) | class TotalProductSales(ReportView):
class TotalProductSalesByCountry (line 74) | class TotalProductSalesByCountry(ReportView):
class SumValueComputationField (line 96) | class SumValueComputationField(ComputationField):
class MonthlyProductSales (line 103) | class MonthlyProductSales(ReportView):
class ProductSalesPerClientCrosstab (line 134) | class ProductSalesPerClientCrosstab(ReportView):
class ProductSalesPerCountryCrosstab (line 158) | class ProductSalesPerCountryCrosstab(ReportView):
class LastTenSales (line 181) | class LastTenSales(ListReportView):
class TotalProductSalesWithCustomForm (line 200) | class TotalProductSalesWithCustomForm(TotalProductSales):
class GroupByReport (line 213) | class GroupByReport(ReportView):
class GroupByTraversingFieldReport (line 244) | class GroupByTraversingFieldReport(GroupByReport):
class GroupByCustomQueryset (line 251) | class GroupByCustomQueryset(ReportView):
method format_row (line 279) | def format_row(self, row_obj):
class NoGroupByReport (line 291) | class NoGroupByReport(ReportView):
class TimeSeriesReport (line 310) | class TimeSeriesReport(ReportView):
class TimeSeriesReportWithSelector (line 357) | class TimeSeriesReportWithSelector(TimeSeriesReport):
function get_current_year (line 377) | def get_current_year():
class TimeSeriesReportWithCustomDates (line 381) | class TimeSeriesReportWithCustomDates(TimeSeriesReport):
class TimeSeriesReportWithCustomGroupByQueryset (line 394) | class TimeSeriesReportWithCustomGroupByQueryset(ReportView):
class SumOfFieldValue (line 441) | class SumOfFieldValue(ComputationField):
method get_time_series_field_verbose_name (line 450) | def get_time_series_field_verbose_name(cls, date_period, index, dates,...
class TimeSeriesReportWithCustomDatesAndCustomTitle (line 458) | class TimeSeriesReportWithCustomDatesAndCustomTitle(TimeSeriesReportWith...
class TimeSeriesWithoutGroupBy (line 485) | class TimeSeriesWithoutGroupBy(ReportView):
class CrosstabReport (line 517) | class CrosstabReport(ReportView):
class CrosstabWithTraversingField (line 542) | class CrosstabWithTraversingField(CrosstabReport):
class CrosstabWithIds (line 548) | class CrosstabWithIds(CrosstabReport):
method get_crosstab_ids (line 553) | def get_crosstab_ids(self):
class CrosstabWithIdsCustomFilter (line 557) | class CrosstabWithIdsCustomFilter(CrosstabReport):
class CustomCrossTabTotalField (line 572) | class CustomCrossTabTotalField(ComputationField):
method get_crosstab_field_verbose_name (line 579) | def get_crosstab_field_verbose_name(cls, model, id):
class CrossTabReportWithCustomVerboseName (line 587) | class CrossTabReportWithCustomVerboseName(CrosstabReport):
class CustomCrossTabTotalPerSize (line 594) | class CustomCrossTabTotalPerSize(CustomCrossTabTotalField):
method get_crosstab_field_verbose_name (line 596) | def get_crosstab_field_verbose_name(cls, model, id):
method get_time_series_field_verbose_name (line 602) | def get_time_series_field_verbose_name(cls, date_period, index, dates,...
class CrossTabReportWithCustomVerboseNameCustomFilter (line 606) | class CrossTabReportWithCustomVerboseNameCustomFilter(CrosstabWithIdsCus...
class CrossTabWithTimeSeries (line 614) | class CrossTabWithTimeSeries(CrossTabReportWithCustomVerboseNameCustomFi...
class ChartJSExample (line 624) | class ChartJSExample(TimeSeriesReport):
class HighChartExample (line 655) | class HighChartExample(TimeSeriesReport):
class ProductSalesApexChart (line 724) | class ProductSalesApexChart(ReportView):
class CustomExportReport (line 769) | class CustomExportReport(GroupByReport):
method export_pdf (line 775) | def export_pdf(self, report_data):
method export_csv (line 781) | def export_csv(self, report_data):
class ReportWithFormInitial (line 788) | class ReportWithFormInitial(ReportView):
method get_initial (line 807) | def get_initial(self):
class PreComputedMonthlySales (line 814) | class PreComputedMonthlySales(ReportView):
class DynamicModelSalesByCountry (line 842) | class DynamicModelSalesByCountry(ReportView):
FILE: demo_proj/demo_app/templatetags/slick_reporting_demo_tags.py
function get_section (line 11) | def get_section(section):
function get_menu (line 29) | def get_menu(context, section):
function get_report_source (line 45) | def get_report_source(report):
function get_report_class_label (line 53) | def get_report_class_label(report):
function should_show (line 59) | def should_show(context, section):
FILE: demo_proj/demo_app/tests.py
class DemoSanityTests (line 14) | class DemoSanityTests(TestCase):
method setUpTestData (line 16) | def setUpTestData(cls):
method setUp (line 63) | def setUp(self):
method test_all_pages_load (line 66) | def test_all_pages_load(self):
method test_all_report_data_endpoints (line 76) | def test_all_report_data_endpoints(self):
method test_precomputed_crosstab_fk_group_by_returns_data (line 87) | def test_precomputed_crosstab_fk_group_by_returns_data(self):
FILE: demo_proj/demo_app/views.py
class HomeView (line 6) | class HomeView(TemplateView):
class Dashboard (line 10) | class Dashboard(TemplateView):
FILE: demo_proj/manage.py
function main (line 7) | def main():
FILE: scripts/extract_changelog.py
function extract (line 10) | def extract(version: str, changelog_path: str = "CHANGELOG.md") -> str:
FILE: slick_reporting/app_settings.py
function get_first_of_this_year (line 9) | def get_first_of_this_year():
function get_end_of_this_year (line 14) | def get_end_of_this_year():
function get_start_date (line 19) | def get_start_date():
function get_end_date (line 24) | def get_end_date():
function get_slick_reporting_settings (line 95) | def get_slick_reporting_settings():
function get_media (line 131) | def get_media():
function get_access_function (line 135) | def get_access_function():
FILE: slick_reporting/apps.py
class ReportAppConfig (line 4) | class ReportAppConfig(apps.AppConfig):
method ready (line 8) | def ready(self):
FILE: slick_reporting/decorators.py
function report_field_register (line 1) | def report_field_register(report_field, *args, **kwargs):
FILE: slick_reporting/dynamic_model.py
function _make_field (line 35) | def _make_field(field_type_str, column_info, is_pk):
function get_dynamic_model (line 68) | def get_dynamic_model(table_name, database="default", schema=None):
FILE: slick_reporting/fields.py
class ComputationField (line 12) | class ComputationField(object):
method __new__ (line 62) | def __new__(cls, *args, **kwargs):
method create (line 74) | def create(cls, method, field, name=None, verbose_name=None, is_summab...
method __init__ (line 102) | def __init__(
method _get_required_classes (line 135) | def _get_required_classes(cls):
method apply_aggregation (line 139) | def apply_aggregation(self, queryset, group_by=""):
method init_preparation (line 150) | def init_preparation(self, q_filters=None, kwargs_filters=None, **kwar...
method prepare_custom_group_by_queryset (line 176) | def prepare_custom_group_by_queryset(self, q_filters=None, kwargs_filt...
method prepare (line 186) | def prepare(
method get_queryset (line 237) | def get_queryset(self):
method _prepare_required_computations (line 245) | def _prepare_required_computations(
method resolve (line 265) | def resolve(self, prepared_results, required_computation_results: dict...
method do_resolve (line 278) | def do_resolve(self, current_obj, current_row=None):
method get_dependency_value (line 283) | def get_dependency_value(self, current_obj, name):
method _resolve_dependencies (line 296) | def _resolve_dependencies(self, current_obj, name=None):
method extract_data (line 306) | def extract_data(self, prepared_results, current_obj):
method get_full_dependency_list (line 328) | def get_full_dependency_list(cls):
method get_crosstab_field_verbose_name (line 347) | def get_crosstab_field_verbose_name(cls, model, id):
method get_time_series_field_verbose_name (line 359) | def get_time_series_field_verbose_name(cls, date_period, index, dates,...
class FirstBalanceField (line 385) | class FirstBalanceField(ComputationField):
method prepare (line 389) | def prepare(
method resolve (line 407) | def resolve(self, prepared_results, required_computation_results: dict...
class TotalReportField (line 416) | class TotalReportField(ComputationField):
class BalanceReportField (line 425) | class BalanceReportField(ComputationField):
method resolve (line 430) | def resolve(self, prepared_results, required_computation_results: dict...
class PercentageToTotalBalance (line 440) | class PercentageToTotalBalance(ComputationField):
method resolve (line 447) | def resolve(self, prepared_results, required_computation_results: dict...
class CreditReportField (line 452) | class CreditReportField(ComputationField):
method resolve (line 456) | def resolve(self, prepared_results, required_computation_results: dict...
class DebitReportField (line 465) | class DebitReportField(ComputationField):
method resolve (line 469) | def resolve(self, prepared_results, required_computation_results: dict...
class CreditQuantityReportField (line 475) | class CreditQuantityReportField(ComputationField):
method resolve (line 481) | def resolve(self, prepared_results, required_computation_results: dict...
class DebitQuantityReportField (line 487) | class DebitQuantityReportField(ComputationField):
method resolve (line 493) | def resolve(self, prepared_results, required_computation_results: dict...
class TotalQTYReportField (line 498) | class TotalQTYReportField(ComputationField):
class FirstBalanceQTYReportField (line 508) | class FirstBalanceQTYReportField(FirstBalanceField):
class BalanceQTYReportField (line 518) | class BalanceQTYReportField(ComputationField):
method resolve (line 525) | def resolve(self, prepared_results, required_computation_results: dict...
class SlickReportField (line 534) | class SlickReportField(ComputationField):
method warn (line 536) | def warn():
method create (line 544) | def create(cls, method, field, name=None, verbose_name=None, is_summab...
method __new__ (line 548) | def __new__(cls, *args, **kwargs):
FILE: slick_reporting/forms.py
function default_formfield_callback (line 19) | def default_formfield_callback(f, **kwargs):
function get_crispy_helper (line 25) | def get_crispy_helper(
function get_choices_form_queryset_list (line 68) | def get_choices_form_queryset_list(qs):
class OrderByForm (line 75) | class OrderByForm(forms.Form):
method get_order_by (line 78) | def get_order_by(self, default_field=None):
method parse_order_by_field (line 91) | def parse_order_by_field(self, order_field):
class BaseReportForm (line 106) | class BaseReportForm:
method get_filters (line 107) | def get_filters(self):
method get_start_date (line 113) | def get_start_date(self):
method get_end_date (line 116) | def get_end_date(self):
method get_crosstab_compute_remainder (line 119) | def get_crosstab_compute_remainder(self):
method get_crosstab_ids (line 124) | def get_crosstab_ids(self):
method get_time_series_pattern (line 129) | def get_time_series_pattern(self):
method get_crispy_helper (line 135) | def get_crispy_helper(self):
class SlickReportForm (line 147) | class SlickReportForm(BaseReportForm):
method get_start_date (line 152) | def get_start_date(self):
method get_end_date (line 155) | def get_end_date(self):
method get_time_series_pattern (line 158) | def get_time_series_pattern(self):
method get_filters (line 161) | def get_filters(self):
method crosstab_key_name (line 180) | def crosstab_key_name(self):
method get_crosstab_ids (line 191) | def get_crosstab_ids(self):
method get_crosstab_compute_remainder (line 204) | def get_crosstab_compute_remainder(self):
method get_crispy_helper (line 207) | def get_crispy_helper(self, foreign_keys_map=None, crosstab_model=None...
function _default_foreign_key_widget (line 218) | def _default_foreign_key_widget(f_field):
function report_form_factory (line 225) | def report_form_factory(
FILE: slick_reporting/generator.py
class Chart (line 19) | class Chart:
method to_dict (line 34) | def to_dict(self):
class ReportGeneratorAPI (line 47) | class ReportGeneratorAPI:
class ReportGenerator (line 154) | class ReportGenerator(ReportGeneratorAPI, object):
method __init__ (line 163) | def __init__(
method _get_fk_group_by_queryset (line 353) | def _get_fk_group_by_queryset(self, filtered_qs):
method prepare_queryset (line 364) | def prepare_queryset(self, queryset):
method _remove_order (line 387) | def _remove_order(self, main_queryset):
method _apply_queryset_options (line 397) | def _apply_queryset_options(self, query, fields=None):
method _apply_precomputed_queryset_options (line 420) | def _apply_precomputed_queryset_options(self, query):
method _build_precomputed_crosstab_data (line 435) | def _build_precomputed_crosstab_data(self, queryset):
method _construct_crosstab_filter (line 453) | def _construct_crosstab_filter(self, col_data, queryset_filters=None):
method _prepare_report_dependencies (line 473) | def _prepare_report_dependencies(self):
method get_primary_key_name (line 542) | def get_primary_key_name(self, model):
method _get_record_data (line 550) | def _get_record_data(self, obj, columns):
method get_report_data (line 610) | def get_report_data(self):
method _default_format_row (line 624) | def _default_format_row(self, row_obj):
method check_columns (line 633) | def check_columns(
method _parse (line 763) | def _parse(self):
method get_database_columns (line 776) | def get_database_columns(self):
method get_list_display_columns (line 782) | def get_list_display_columns(self):
method get_time_series_parsed_columns (line 803) | def get_time_series_parsed_columns(self):
method get_time_series_field_verbose_name (line 847) | def get_time_series_field_verbose_name(self, computation_class, date_p...
method get_custom_time_series_dates (line 860) | def get_custom_time_series_dates(self):
method _get_time_series_dates (line 867) | def _get_time_series_dates(self, series=None, start_date=None, end_dat...
method get_crosstab_parsed_columns (line 905) | def get_crosstab_parsed_columns(self):
method _get_precomputed_crosstab_parsed_columns (line 955) | def _get_precomputed_crosstab_parsed_columns(self):
method get_crosstab_field_verbose_name (line 977) | def get_crosstab_field_verbose_name(self, computation_class, model, id):
method get_metadata (line 987) | def get_metadata(self):
method get_columns_data (line 1004) | def get_columns_data(self):
method get_full_response (line 1026) | def get_full_response(
method get_chart_settings (line 1042) | def get_chart_settings(chart_settings=None, default_chart_title=None, ...
class ListViewReportGenerator (line 1070) | class ListViewReportGenerator(ReportGenerator):
method prepare_queryset (line 1071) | def prepare_queryset(self, queryset):
method _apply_queryset_options (line 1074) | def _apply_queryset_options(self, query, fields=None):
method _get_record_data (line 1095) | def _get_record_data(self, obj, columns):
method _remove_order (line 1145) | def _remove_order(self, main_queryset):
function _sanitize_crosstab_key (line 1149) | def _sanitize_crosstab_key(value):
FILE: slick_reporting/helpers.py
function get_calculation_annotation (line 7) | def get_calculation_annotation(calculation_field, calculation_method):
function get_foreign_keys (line 18) | def get_foreign_keys(model):
function get_field_from_query_text (line 40) | def get_field_from_query_text(path, model):
function user_test_function (line 59) | def user_test_function(report_view):
FILE: slick_reporting/registry.py
class ReportFieldRegistry (line 6) | class ReportFieldRegistry(object):
method __init__ (line 7) | def __init__(self):
method register (line 11) | def register(self, report_field, override=False):
method unregister (line 24) | def unregister(self, report_field):
method get_field_by_name (line 35) | def get_field_by_name(self, name):
method get_all_report_fields_names (line 43) | def get_all_report_fields_names(self):
FILE: slick_reporting/static/slick_reporting/slick_reporting.chartsjs.js
function is_time_series (line 11) | function is_time_series(response, chartOptions) {
function is_crosstab (line 16) | function is_crosstab(response, chartOptions) {
function getTimeSeriesColumnNames (line 20) | function getTimeSeriesColumnNames(response) {
function createChartObject (line 24) | function createChartObject(response, chartOptions, extraOptions) {
function getGroupByLabelAndSeries (line 82) | function getGroupByLabelAndSeries(response, chartOptions) {
function getCrosstabColumnNames (line 104) | function getCrosstabColumnNames(response, chartOptions) {
function extractDataFromResponse (line 118) | function extractDataFromResponse(response, chartOptions) {
function getBackgroundColors (line 232) | function getBackgroundColors(i) {
function displayChart (line 239) | function displayChart(data, $elem, chartOptions) {
FILE: slick_reporting/static/slick_reporting/slick_reporting.datatable.js
function constructTable (line 13) | function constructTable(css_class, cols, cols_names, add_footer, total_v...
function buildAndInitializeDataTable (line 53) | function buildAndInitializeDataTable(data, $elem, extraOptions, successF...
function getDatatableColumns (line 86) | function getDatatableColumns(data) {
function initializeReportDatatable (line 103) | function initializeReportDatatable(tableSelector, data, extraOptions) {
FILE: slick_reporting/static/slick_reporting/slick_reporting.highchart.js
function dataArrayToObject (line 7) | function dataArrayToObject(data, key) {
function normalStackedTooltipFormatter (line 26) | function normalStackedTooltipFormatter() {
function transform_to_pie (line 39) | function transform_to_pie(chartObject_series, index, categories) {
function createChartObject (line 54) | function createChartObject(response, chartOptions, extraOptions) {
function get_normal_data (line 245) | function get_normal_data(response, chartOptions) {
function get_time_series_data (line 273) | function get_time_series_data(response, chartOptions) {
function get_crosstab_data (line 334) | function get_crosstab_data(response, chartOptions) {
function is_timeseries_support (line 384) | function is_timeseries_support(response, chartOptions) {
function is_crosstab_support (line 389) | function is_crosstab_support(response, chartOptions) {
function displayChart (line 393) | function displayChart(data, $elem, chartOptions) {
FILE: slick_reporting/static/slick_reporting/slick_reporting.js
function executeFunctionByName (line 3) | function executeFunctionByName(functionName, context /*, args */) {
function getObjFromArray (line 22) | function getObjFromArray(objList, obj_key, key_value, failToFirst) {
function calculateTotalOnObjectArray (line 38) | function calculateTotalOnObjectArray(data, columns) {
function get_xpath (line 67) | function get_xpath($element, forceTree) {
FILE: slick_reporting/static/slick_reporting/slick_reporting.report_loader.js
function failFunction (line 10) | function failFunction(data, $elem) {
function loadComponents (line 18) | function loadComponents(data, $elem) {
function displayChart (line 38) | function displayChart(data, $elem, chart_id) {
function refreshReportWidget (line 54) | function refreshReportWidget($elem, extra_params) {
function initialize (line 88) | function initialize() {
function _get_chart_icon (line 102) | function _get_chart_icon(chart_type) {
function createChartsUIfromResponse (line 111) | function createChartsUIfromResponse(data, $elem, a_class) {
FILE: slick_reporting/templatetags/slick_reporting_tags.py
function _resolve_static (line 13) | def _resolve_static(path):
function get_widget_from_url (line 21) | def get_widget_from_url(url_name=None, url=None, **kwargs):
function get_widget (line 34) | def get_widget(report, template_name="", url_name="", report_url=None, *...
function add_jquery (line 62) | def add_jquery():
function get_charts_media (line 70) | def get_charts_media(chart_settings):
function get_slick_reporting_media (line 85) | def get_slick_reporting_media():
function get_slick_reporting_settings (line 93) | def get_slick_reporting_settings():
FILE: slick_reporting/views.py
function dictsort (line 31) | def dictsort(value, arg, desc=False):
class ExportToCSV (line 39) | class ExportToCSV(object):
method get_filename (line 40) | def get_filename(self):
method get_response (line 43) | def get_response(self):
method get_rows (line 53) | def get_rows(self):
method get_columns (line 59) | def get_columns(self, extra_context=None):
method __init__ (line 62) | def __init__(self, request, report_data, report_title, **kwargs):
class ExportToStreamingCSV (line 69) | class ExportToStreamingCSV(ExportToCSV):
method get_response (line 70) | def get_response(self):
class PrintHTMLExport (line 87) | class PrintHTMLExport:
method __init__ (line 90) | def __init__(self, request, report_data, report_title, **kwargs):
method get_response (line 95) | def get_response(self):
class ReportViewBase (line 104) | class ReportViewBase(ReportGeneratorAPI, UserPassesTestMixin, FormView):
method test_func (line 142) | def test_func(self):
method get_report_title (line 147) | def get_report_title(cls):
method order_results (line 156) | def order_results(self, data):
method get_doc_types_q_filters (line 167) | def get_doc_types_q_filters(self):
method get_export_actions (line 177) | def get_export_actions(self):
method get (line 209) | def get(self, request, *args, **kwargs):
method export_csv (line 235) | def export_csv(self, report_data):
method export_print (line 242) | def export_print(self, report_data):
method get_report_model (line 251) | def get_report_model(cls):
method ajax_render_to_response (line 260) | def ajax_render_to_response(self, report_data):
method serialize_to_json (line 263) | def serialize_to_json(self, response_data):
method get_form_class (line 280) | def get_form_class(self):
method fkeys_filter_func_hook (line 301) | def fkeys_filter_func_hook(fkeys_dict):
method get_form_kwargs (line 312) | def get_form_kwargs(self):
method get_crosstab_ids (line 337) | def get_crosstab_ids(self):
method get_group_by_custom_querysets (line 344) | def get_group_by_custom_querysets(self):
method get_report_generator (line 347) | def get_report_generator(self, queryset=None, for_print=False):
method format_row (line 401) | def format_row(self, row_obj):
method get_columns_data (line 410) | def get_columns_data(cls, generator):
method get_report_results (line 418) | def get_report_results(self, for_print=False):
method get_metadata (line 439) | def get_metadata(cls, generator):
method get_chart_settings (line 446) | def get_chart_settings(self, generator=None):
method get_queryset (line 457) | def get_queryset(cls):
method filter_results (line 462) | def filter_results(self, data, for_print=False):
method get_report_slug (line 473) | def get_report_slug(cls):
method get_initial (line 476) | def get_initial(self):
method get_form_crispy_helper (line 486) | def get_form_crispy_helper(self):
method get_context_data (line 495) | def get_context_data(self, **kwargs):
method form_invalid (line 506) | def form_invalid(self, form):
class ReportView (line 512) | class ReportView(ReportViewBase):
method __init_subclass__ (line 513) | def __init_subclass__(cls) -> None:
class SlickReportingListViewMixin (line 531) | class SlickReportingListViewMixin(ReportViewBase):
method get_queryset (line 535) | def get_queryset(self):
method get_form_filters (line 541) | def get_form_filters(self, form):
method get_form_crispy_helper (line 567) | def get_form_crispy_helper(self):
method get_report_generator (line 570) | def get_report_generator(self, queryset=None, for_print=False):
method get_form_class (line 588) | def get_form_class(self):
method get_report_results (line 614) | def get_report_results(self, for_print=False):
class SlickReportingListView (line 633) | class SlickReportingListView(SlickReportingListViewMixin, ReportViewBase):
method __init_subclass__ (line 634) | def __init_subclass__(cls) -> None:
class ListReportView (line 644) | class ListReportView(SlickReportingListViewMixin):
class SlickReportViewBase (line 648) | class SlickReportViewBase(ReportViewBase):
method __init_subclass__ (line 653) | def __init_subclass__(cls) -> None:
class SlickReportView (line 664) | class SlickReportView(ReportView):
method __init_subclass__ (line 665) | def __init_subclass__(cls) -> None:
FILE: tests/models.py
class Product (line 8) | class Product(models.Model):
class Meta (line 21) | class Meta:
class ProductCustomID (line 26) | class ProductCustomID(models.Model):
class Meta (line 40) | class Meta:
class Agent (line 45) | class Agent(models.Model):
class Contact (line 49) | class Contact(models.Model):
class Client (line 57) | class Client(models.Model):
class SexChoices (line 58) | class SexChoices(models.TextChoices):
class Meta (line 71) | class Meta:
class SimpleSales (line 76) | class SimpleSales(models.Model):
method save (line 95) | def save(
class Meta (line 101) | class Meta:
class SimpleSales2 (line 107) | class SimpleSales2(models.Model):
method save (line 126) | def save(
class Meta (line 132) | class Meta:
class SalesProductWithCustomID (line 138) | class SalesProductWithCustomID(models.Model):
method save (line 157) | def save(
class Meta (line 163) | class Meta:
class SalesWithFlag (line 169) | class SalesWithFlag(models.Model):
method save (line 182) | def save(
class Meta (line 188) | class Meta:
class UserJoined (line 194) | class UserJoined(models.Model):
class TaxCode (line 199) | class TaxCode(models.Model):
class ComplexSales (line 204) | class ComplexSales(models.Model):
method save (line 224) | def save(
class Meta (line 230) | class Meta:
class Order (line 282) | class Order(models.Model):
class OrderLine (line 287) | class OrderLine(models.Model):
class Architect (line 295) | class Architect(models.Model):
class Initiative (line 309) | class Initiative(models.Model):
FILE: tests/report_generators.py
class GenericGenerator (line 22) | class GenericGenerator(ReportGenerator):
class GeneratorWithAttrAsColumn (line 33) | class GeneratorWithAttrAsColumn(GenericGenerator):
method get_data (line 38) | def get_data(self, obj):
class CrosstabOnClient (line 44) | class CrosstabOnClient(GenericGenerator):
class CrosstabTimeSeries (line 52) | class CrosstabTimeSeries(GenericGenerator):
class CrosstabOnField (line 67) | class CrosstabOnField(ReportGenerator):
class CrosstabCustomQueryset (line 79) | class CrosstabCustomQueryset(ReportGenerator):
class CrosstabOnTraversingField (line 96) | class CrosstabOnTraversingField(ReportGenerator):
class ClientTotalBalance (line 109) | class ClientTotalBalance(ReportGenerator):
class TotalBalanceWithQueryset (line 121) | class TotalBalanceWithQueryset(ReportGenerator):
class ClientTotalBalance2 (line 129) | class ClientTotalBalance2(ReportGenerator):
class GroupByCharField (line 136) | class GroupByCharField(ReportGenerator):
class GroupByCharFieldPlusTimeSeries (line 143) | class GroupByCharFieldPlusTimeSeries(ReportGenerator):
class ClientTotalBalancesOrdered (line 153) | class ClientTotalBalancesOrdered(ClientTotalBalance):
class ClientTotalBalancesOrderedDESC (line 158) | class ClientTotalBalancesOrderedDESC(ClientTotalBalance):
class ProductTotalSales (line 163) | class ProductTotalSales(ReportGenerator):
method get_object_sku (line 176) | def get_object_sku(self, obj: dict, row: dict) -> any:
method average_value (line 186) | def average_value(self, obj, data):
class ProductTotalSalesProductWithCustomID (line 192) | class ProductTotalSalesProductWithCustomID(ReportGenerator):
class ProductTotalSalesWithPercentage (line 199) | class ProductTotalSalesWithPercentage(ReportGenerator):
class ClientList (line 212) | class ClientList(ReportGenerator):
class ProductClientSales (line 219) | class ProductClientSales(ReportGenerator):
method get_data (line 230) | def get_data(self, obj):
class ProductSalesMonthlySeries (line 234) | class ProductSalesMonthlySeries(ReportGenerator):
class TimeSeriesCustomDates (line 270) | class TimeSeriesCustomDates(ReportGenerator):
class TimeSeriesWithOutGroupBy (line 285) | class TimeSeriesWithOutGroupBy(ReportGenerator):
class ClientReportMixin (line 295) | class ClientReportMixin:
class ClientSalesMonthlySeries (line 300) | class ClientSalesMonthlySeries(ReportGenerator):
class CountField (line 310) | class CountField(ComputationField):
class TestCountField (line 317) | class TestCountField(ReportGenerator):
class ClientDetailedStatement (line 328) | class ClientDetailedStatement(ReportGenerator):
class ClientDetailedStatement2 (line 335) | class ClientDetailedStatement2(ReportGenerator):
class ProductClientSalesMatrix (line 367) | class ProductClientSalesMatrix(ReportGenerator):
class ProductClientSalesMatrixToFieldSet (line 378) | class ProductClientSalesMatrixToFieldSet(ReportGenerator):
class ProductClientSalesMatrix2 (line 389) | class ProductClientSalesMatrix2(ReportGenerator):
class ProductClientSalesMatrixwSimpleSales2 (line 400) | class ProductClientSalesMatrixwSimpleSales2(ReportGenerator):
class GeneratorClassWithAttrsAs (line 411) | class GeneratorClassWithAttrsAs(ReportGenerator):
class ClientTotalBalancesWithShowEmptyFalse (line 415) | class ClientTotalBalancesWithShowEmptyFalse(ClientTotalBalance):
FILE: tests/test_dynamic_model.py
class DynamicModelTestBase (line 32) | class DynamicModelTestBase(TestCase):
method setUpClass (line 34) | def setUpClass(cls):
method tearDownClass (line 49) | def tearDownClass(cls):
class TestGetDynamicModel (line 65) | class TestGetDynamicModel(DynamicModelTestBase):
method test_returns_model_class (line 66) | def test_returns_model_class(self):
method test_model_meta (line 70) | def test_model_meta(self):
method test_model_fields (line 75) | def test_model_fields(self):
method test_pk_field (line 86) | def test_pk_field(self):
method test_cache_returns_same_model (line 92) | def test_cache_returns_same_model(self):
method test_nonexistent_table_raises (line 97) | def test_nonexistent_table_raises(self):
class TestDynamicModelQuerySet (line 103) | class TestDynamicModelQuerySet(DynamicModelTestBase):
method test_objects_all (line 104) | def test_objects_all(self):
method test_filter (line 109) | def test_filter(self):
method test_values (line 114) | def test_values(self):
method test_aggregate (line 122) | def test_aggregate(self):
method test_annotate (line 127) | def test_annotate(self):
class TestReportGeneratorWithDynamicModel (line 138) | class TestReportGeneratorWithDynamicModel(DynamicModelTestBase):
method test_group_by_report (line 139) | def test_group_by_report(self):
method test_time_series_report (line 159) | def test_time_series_report(self):
method test_table_name_convenience_param (line 176) | def test_table_name_convenience_param(self):
method test_no_group_by_report (line 191) | def test_no_group_by_report(self):
class TestReportViewTableNameImportSafety (line 207) | class TestReportViewTableNameImportSafety(TestCase):
method test_class_definition_does_not_hit_db (line 210) | def test_class_definition_does_not_hit_db(self):
FILE: tests/test_generator.py
class CrosstabTests (line 26) | class CrosstabTests(BaseTestData, TestCase):
method test_matrix_column_included (line 27) | def test_matrix_column_included(self):
method test_matrix_column_position (line 36) | def test_matrix_column_position(self):
method test_get_crosstab_columns (line 50) | def test_get_crosstab_columns(self):
method test_get_crosstab_parsed_columns (line 62) | def test_get_crosstab_parsed_columns(self):
method test_crosstab_on_field (line 72) | def test_crosstab_on_field(self):
method test_crosstab_ids_queryset (line 81) | def test_crosstab_ids_queryset(self):
method test_crosstab_on_traversing_field (line 90) | def test_crosstab_on_traversing_field(self):
method test_crosstab_time_series (line 99) | def test_crosstab_time_series(self):
class GeneratorReportStructureTest (line 126) | class GeneratorReportStructureTest(BaseTestData, TestCase):
method setUpTestData (line 128) | def setUpTestData(cls):
method test_time_series_columns_inclusion (line 138) | def test_time_series_columns_inclusion(self):
method test_time_series_patterns (line 151) | def test_time_series_patterns(self):
method test_time_series_custom_pattern (line 197) | def test_time_series_custom_pattern(self):
method test_time_series_columns_placeholder (line 207) | def test_time_series_columns_placeholder(self):
method test_time_series_and_cros_tab (line 220) | def test_time_series_and_cros_tab(self):
method test_attr_as_column (line 223) | def test_attr_as_column(self):
method test_improper_group_by (line 229) | def test_improper_group_by(self):
method test_missing_report_model (line 235) | def test_missing_report_model(self):
method test_missing_date_field (line 241) | def test_missing_date_field(self):
method test_wrong_date_field (line 247) | def test_wrong_date_field(self):
method test_unknown_column (line 253) | def test_unknown_column(self):
method test_gather_dependencies_for_time_series (line 264) | def test_gather_dependencies_for_time_series(self):
method test_group_by_traverse (line 276) | def test_group_by_traverse(self):
method test_group_by_and_foreign_key_field (line 296) | def test_group_by_and_foreign_key_field(self):
method test_custom_group_by (line 332) | def test_custom_group_by(self):
method test_custom_group_by_with_index (line 355) | def test_custom_group_by_with_index(self):
method test_traversing_group_by_and_foreign_key_field (line 376) | def test_traversing_group_by_and_foreign_key_field(self):
method test_traversing_group_by_sanity (line 399) | def test_traversing_group_by_sanity(self):
method test_db_field_column_verbose_name (line 412) | def test_db_field_column_verbose_name(self):
method test_group_by_char_field (line 417) | def test_group_by_char_field(self):
class TestReportFields (line 423) | class TestReportFields(BaseTestData, TestCase):
method test_get_full_dependency_list (line 424) | def test_get_full_dependency_list(self):
method test_computation_field_count (line 430) | def test_computation_field_count(self):
class TestHelpers (line 438) | class TestHelpers(TestCase):
method test_get_model_for_keys (line 439) | def test_get_model_for_keys(self):
class TestListViewGenerator (line 444) | class TestListViewGenerator(BaseTestData, TestCase):
method test_traversing_field_in_column (line 445) | def test_traversing_field_in_column(self):
FILE: tests/test_pivot_generator.py
class PrecomputedCrosstabTestBase (line 30) | class PrecomputedCrosstabTestBase(TestCase):
method setUpClass (line 32) | def setUpClass(cls):
method tearDownClass (line 47) | def tearDownClass(cls):
class TestPrecomputedCrosstabBasic (line 63) | class TestPrecomputedCrosstabBasic(PrecomputedCrosstabTestBase):
method test_date_crosstab (line 64) | def test_date_crosstab(self):
method test_missing_period_defaults_to_zero (line 88) | def test_missing_period_defaults_to_zero(self):
method test_entity_crosstab (line 106) | def test_entity_crosstab(self):
method test_multiple_crosstab_columns (line 131) | def test_multiple_crosstab_columns(self):
class TestPrecomputedCrosstabMetadata (line 153) | class TestPrecomputedCrosstabMetadata(PrecomputedCrosstabTestBase):
method test_crosstab_metadata_populated (line 154) | def test_crosstab_metadata_populated(self):
method test_column_computation_field_attribute (line 175) | def test_column_computation_field_attribute(self):
class TestPrecomputedCrosstabWithTableName (line 195) | class TestPrecomputedCrosstabWithTableName(PrecomputedCrosstabTestBase):
method test_table_name_convenience (line 196) | def test_table_name_convenience(self):
class TestPrecomputedCrosstabDateFiltering (line 212) | class TestPrecomputedCrosstabDateFiltering(PrecomputedCrosstabTestBase):
method test_date_filter_limits_crosstab_values (line 213) | def test_date_filter_limits_crosstab_values(self):
class TestPrecomputedCrosstabWithSpaces (line 249) | class TestPrecomputedCrosstabWithSpaces(TestCase):
method setUpClass (line 251) | def setUpClass(cls):
method tearDownClass (line 265) | def tearDownClass(cls):
method test_crosstab_values_with_spaces (line 280) | def test_crosstab_values_with_spaces(self):
method test_crosstab_values_with_special_chars (line 297) | def test_crosstab_values_with_special_chars(self):
method test_verbose_name_preserves_original (line 313) | def test_verbose_name_preserves_original(self):
class TestPrecomputedCrosstabWithFKGroupBy (line 328) | class TestPrecomputedCrosstabWithFKGroupBy(TestCase):
method setUpTestData (line 340) | def setUpTestData(cls):
method test_rows_populated_with_fk_group_by (line 360) | def test_rows_populated_with_fk_group_by(self):
FILE: tests/tests.py
class BaseTestData (line 44) | class BaseTestData:
method setUpTestData (line 48) | def setUpTestData(cls):
class ReportTest (line 314) | class ReportTest(BaseTestData, TestCase):
method test_client_balance (line 315) | def test_client_balance(self):
method test_compute_from_queryset (line 321) | def test_compute_from_queryset(self):
method test_product_total_sales (line 326) | def test_product_total_sales(self):
method test_product_total_sales_product_custom_id (line 332) | def test_product_total_sales_product_custom_id(self):
method test_product_total_sales_with_percentage (line 342) | def test_product_total_sales_with_percentage(self):
method test_product_total_sales_with_changed_dated (line 351) | def test_product_total_sales_with_changed_dated(self):
method test_client_client_sales_monthly (line 356) | def test_client_client_sales_monthly(self):
method test_productclientsalesmatrix (line 378) | def test_productclientsalesmatrix(self):
method test_productclientsalesmatrix_no_remainder (line 385) | def test_productclientsalesmatrix_no_remainder(self):
method test_show_empty_records (line 394) | def test_show_empty_records(self):
method test_filters (line 402) | def test_filters(self):
method test_view_filter_to_field_set (line 411) | def test_view_filter_to_field_set(self):
method test_filter_as_int_n_list (line 436) | def test_filter_as_int_n_list(self):
method test_timeseries_without_group (line 445) | def test_timeseries_without_group(self):
method test_many_to_many_group_by (line 450) | def test_many_to_many_group_by(self):
class TestView (line 470) | class TestView(BaseTestData, TestCase):
method test_view (line 471) | def test_view(self):
method test_qs_only (line 489) | def test_qs_only(self):
method test_view_filter (line 507) | def test_view_filter(self):
method test_view_filter_to_field_set (line 530) | def test_view_filter_to_field_set(self):
method test_ajax (line 552) | def test_ajax(self):
method test_crosstab_report_view (line 567) | def test_crosstab_report_view(self):
method test_crosstab_report_view_clumns_on_fly (line 589) | def test_crosstab_report_view_clumns_on_fly(self):
method test_crosstab_report_view_to_field_set (line 607) | def test_crosstab_report_view_to_field_set(self):
method test_crosstab_report_view_clumns_on_fly_to_field_set (line 629) | def test_crosstab_report_view_clumns_on_fly_to_field_set(self):
method test_chart_settings (line 647) | def test_chart_settings(self):
method test_error_on_missing_date_field (line 662) | def test_error_on_missing_date_field(self):
class TestReportFieldRegistry (line 670) | class TestReportFieldRegistry(TestCase):
method test_unregister (line 671) | def test_unregister(self):
method test_registering_new (line 678) | def test_registering_new(self):
method test_already_registered (line 689) | def test_already_registered(self):
method test_unregister_a_non_existent (line 699) | def test_unregister_a_non_existent(self):
method test_get_non_existent_field (line 706) | def test_get_non_existent_field(self):
method test_creating_a_report_field_on_the_fly (line 713) | def test_creating_a_report_field_on_the_fly(self):
method test_creating_a_report_field_on_the_fly_wo_name (line 719) | def test_creating_a_report_field_on_the_fly_wo_name(self):
class TestGroupByDate (line 726) | class TestGroupByDate(TestCase):
method setUpTestData (line 728) | def setUpTestData(cls):
method test_joined_per_day (line 735) | def test_joined_per_day(self):
class TestGroupByFlag (line 753) | class TestGroupByFlag(TestCase):
method setUpTestData (line 757) | def setUpTestData(cls):
method test_group_by_flag (line 859) | def test_group_by_flag(self):
method test_group_by_flag_time_series (line 865) | def test_group_by_flag_time_series(self):
FILE: tests/views.py
class MonthlyProductSales (line 8) | class MonthlyProductSales(ReportView):
class MonthlyProductSalesToFIeldSet (line 17) | class MonthlyProductSalesToFIeldSet(ReportView):
class ProductClientSalesMatrix (line 26) | class ProductClientSalesMatrix(ReportView):
class ProductClientSalesMatrixToFieldSet (line 46) | class ProductClientSalesMatrixToFieldSet(ReportView):
class CrossTabColumnOnFly (line 66) | class CrossTabColumnOnFly(ReportView):
class CrossTabColumnOnFlyToFieldSet (line 90) | class CrossTabColumnOnFlyToFieldSet(ReportView):
class MonthlyProductSalesWQS (line 114) | class MonthlyProductSalesWQS(ReportView):
class TaxSales (line 123) | class TaxSales(ReportView):
class MonthlyProductSalesToFIeldSet (line 143) | class MonthlyProductSalesToFIeldSet(ReportView):
class TaxSales (line 152) | class TaxSales(ReportView):
Condensed preview — 121 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (565K chars).
[
{
"path": ".github/workflows/django.yml",
"chars": 807,
"preview": "name: Django CI\n\non:\n push:\n branches: [ \"develop\", \"master\" ]\n pull_request:\n branches: [ \"develop\", \"master\" ]"
},
{
"path": ".github/workflows/release.yml",
"chars": 2040,
"preview": "name: Release\n\non:\n push:\n tags:\n - 'v*'\n\njobs:\n release:\n runs-on: ubuntu-latest\n environment: release\n"
},
{
"path": ".gitignore",
"chars": 1810,
"preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packagi"
},
{
"path": ".pre-commit-config.yaml",
"chars": 402,
"preview": "repos:\n\n- repo: https://github.com/adamchainz/blacken-docs\n rev: \"1.13.0\"\n hooks:\n - id: blacken-docs\n "
},
{
"path": ".readthedocs.yaml",
"chars": 280,
"preview": "version: 2\n\nbuild:\n os: \"ubuntu-22.04\"\n tools:\n python: \"3.11\"\n\n# Build from the docs/ directory with Sphinx\nsphinx"
},
{
"path": "CHANGELOG.md",
"chars": 15598,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n## [1.4.0] - 2026-05-01\n\n### New Feat"
},
{
"path": "CLAUDE.md",
"chars": 3359,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "LICENSE.md",
"chars": 1518,
"preview": "BSD 3-Clause License\n\nCopyright (c) 2020, Ra Systems\nAll rights reserved.\n\nRedistribution and use in source and binary f"
},
{
"path": "MANIFEST.in",
"chars": 195,
"preview": "include LICENSE.md\ninclude README.md\nrecursive-include slick_reporting/static *\nrecursive-include slick_reporting/templa"
},
{
"path": "README.rst",
"chars": 9145,
"preview": ".. image:: https://img.shields.io/pypi/v/django-slick-reporting.svg\n :target: https://pypi.org/project/django-slick-r"
},
{
"path": "demo_proj/demo_app/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "demo_proj/demo_app/admin.py",
"chars": 30,
"preview": "\n# Register your models here.\n"
},
{
"path": "demo_proj/demo_app/apps.py",
"chars": 147,
"preview": "from django.apps import AppConfig\n\n\nclass DemoAppConfig(AppConfig):\n default_auto_field = \"django.db.models.BigAutoFi"
},
{
"path": "demo_proj/demo_app/forms.py",
"chars": 1706,
"preview": "from django import forms\nfrom django.db.models import Q\nfrom slick_reporting.forms import BaseReportForm\n\n\nclass TotalSa"
},
{
"path": "demo_proj/demo_app/helpers.py",
"chars": 2550,
"preview": "from django.urls import path\n\nfrom . import reports\n\nTUTORIAL = [\n (\"product-sales\", reports.ProductSales),\n (\"tot"
},
{
"path": "demo_proj/demo_app/management/commands/create_entries.py",
"chars": 5186,
"preview": "import datetime\nimport random\nfrom datetime import timedelta\n\nfrom django.contrib.auth import get_user_model\nfrom django"
},
{
"path": "demo_proj/demo_app/migrations/0001_initial.py",
"chars": 3021,
"preview": "# Generated by Django 4.2 on 2023-08-02 09:14\n\nfrom django.db import migrations, models\nimport django.db.models.deletion"
},
{
"path": "demo_proj/demo_app/migrations/0002_salestransaction_price_salestransaction_quantity.py",
"chars": 677,
"preview": "# Generated by Django 4.2 on 2023-08-02 09:14\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Mig"
},
{
"path": "demo_proj/demo_app/migrations/0003_product_category.py",
"chars": 497,
"preview": "# Generated by Django 4.2 on 2023-08-30 08:06\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.Mig"
},
{
"path": "demo_proj/demo_app/migrations/0004_client_country_product_sku.py",
"chars": 625,
"preview": "# Generated by Django 4.2.4 on 2023-08-30 08:38\n\nfrom django.db import migrations, models\nimport uuid\n\n\nclass Migration("
},
{
"path": "demo_proj/demo_app/migrations/0005_product_size.py",
"chars": 444,
"preview": "# Generated by Django 4.2.4 on 2023-08-30 11:24\n\nfrom django.db import migrations, models\n\n\nclass Migration(migrations.M"
},
{
"path": "demo_proj/demo_app/migrations/0006_productcategory_remove_product_category_and_more.py",
"chars": 1125,
"preview": "# Generated by Django 4.2.4 on 2023-08-30 17:57\n\nfrom django.db import migrations, models\nimport django.db.models.deleti"
},
{
"path": "demo_proj/demo_app/migrations/0007_monthlysalessummary.py",
"chars": 1592,
"preview": "# Generated by Django 6.0.3 on 2026-04-23 15:27\n\nimport django.db.models.deletion\nfrom django.db import migrations, mode"
},
{
"path": "demo_proj/demo_app/migrations/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "demo_proj/demo_app/models.py",
"chars": 2895,
"preview": "import uuid\n\nfrom django.db import models\nfrom django.utils.translation import gettext_lazy as _\n\n\n# Create your models "
},
{
"path": "demo_proj/demo_app/reports.py",
"chars": 29472,
"preview": "import datetime\n\nfrom django.db.models import Sum, Q\nfrom django.http import HttpResponse\nfrom django.utils.translation "
},
{
"path": "demo_proj/demo_app/templatetags/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "demo_proj/demo_app/templatetags/slick_reporting_demo_tags.py",
"chars": 1696,
"preview": "import inspect\n\nfrom django import template\nfrom django.urls import reverse\nfrom django.utils.html import format_html\nfr"
},
{
"path": "demo_proj/demo_app/tests.py",
"chars": 4760,
"preview": "import datetime\n\nfrom django.contrib.auth import get_user_model\nfrom django.db import connection\nfrom django.test import"
},
{
"path": "demo_proj/demo_app/views.py",
"chars": 207,
"preview": "from django.views.generic import TemplateView\n\n\n# Create your views here.\n\nclass HomeView(TemplateView):\n template_na"
},
{
"path": "demo_proj/demo_proj/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "demo_proj/demo_proj/asgi.py",
"chars": 395,
"preview": "\"\"\"\nASGI config for demo_proj project.\n\nIt exposes the ASGI callable as a module-level variable named ``application``.\n\n"
},
{
"path": "demo_proj/demo_proj/settings.py",
"chars": 4161,
"preview": "\"\"\"\nDjango settings for demo_proj project.\n\nGenerated by 'django-admin startproject' using Django 4.2.\n\nFor more informa"
},
{
"path": "demo_proj/demo_proj/urls.py",
"chars": 974,
"preview": "\"\"\"\nURL configuration for demo_proj project.\n\nThe `urlpatterns` list routes URLs to views. For more information please s"
},
{
"path": "demo_proj/demo_proj/wsgi.py",
"chars": 546,
"preview": "\"\"\"\nWSGI config for demo_proj project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\n"
},
{
"path": "demo_proj/manage.py",
"chars": 849,
"preview": "#!/usr/bin/env python\n\"\"\"Django's command-line utility for administrative tasks.\"\"\"\nimport os\nimport sys\n\n\ndef main():\n "
},
{
"path": "demo_proj/requirements.txt",
"chars": 84,
"preview": "django>=4.2\npython-dateutil>=2.8.1\nsimplejson\ndjango-crispy-forms\ncrispy-bootstrap5\n"
},
{
"path": "demo_proj/templates/base.html",
"chars": 3797,
"preview": "<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\"/>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale"
},
{
"path": "demo_proj/templates/dashboard.html",
"chars": 3582,
"preview": "{% extends \"base.html\" %}\n{% load slick_reporting_tags %}\n{% block page_title %} Dashboard {% endblock %}\n{% block meta_"
},
{
"path": "demo_proj/templates/demo/apex_report.html",
"chars": 1823,
"preview": "{% extends \"slick_reporting/report.html\" %}\n{% load slick_reporting_tags %}\n\n{% block content %}\n {{ block.super }}\n"
},
{
"path": "demo_proj/templates/home.html",
"chars": 3023,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <section class=\"jumbotron text-center\">\n <div class=\"container"
},
{
"path": "demo_proj/templates/menu.html",
"chars": 15579,
"preview": "{% load slick_reporting_demo_tags %}\n\n<ul class=\"navbar-nav pt-lg-3\">\n <li class=\"nav-item\">\n <a class=\"nav-li"
},
{
"path": "demo_proj/templates/slick_reporting/base.html",
"chars": 404,
"preview": "{% extends \"base.html\" %}\n\n{% block meta_page_title %} {{ report_title }}{% endblock %}\n{% block page_title %} {{ report"
},
{
"path": "demo_proj/templates/slick_reporting/report_form.html",
"chars": 2251,
"preview": "{% load i18n crispy_forms_tags slick_reporting_demo_tags %}\n<form id=\"reportForm\" class=\"card\">\n <div class=\"card-hea"
},
{
"path": "demo_proj/templates/widget_template_with_pre.html",
"chars": 152,
"preview": "{% extends \"slick_reporting/widget_template.html\" %}\n{% block widget_content %}\n <div>\n <pre id=\"responsePre\">"
},
{
"path": "docs/requirements.txt",
"chars": 104,
"preview": "-r ../requirements.txt\ncrispy_bootstrap4\nsphinx\nsphinx_rtd_theme==1.3.0\nreadthedocs-sphinx-search==0.3.1"
},
{
"path": "docs/source/concept.rst",
"chars": 2736,
"preview": ".. _structure:\n\nWelcome to Django Slick Reporting documentation!\n==================================================\n\nDja"
},
{
"path": "docs/source/conf.py",
"chars": 2156,
"preview": "# Configuration file for the Sphinx documentation builder.\n#\n# This file only contains a selection of the most common op"
},
{
"path": "docs/source/howto/customize_frontend.rst",
"chars": 4311,
"preview": "Charting and Front End Customization\n=====================================\n\n\n\nThe ajax response structure\n--------------"
},
{
"path": "docs/source/howto/index.rst",
"chars": 4860,
"preview": ".. _how_to:\n\n=======\nHow To\n=======\nIn this section we will go over some of the frequent tasks you will need to do when "
},
{
"path": "docs/source/index.rst",
"chars": 2395,
"preview": "Django Slick Reporting\n======================\n\n**Django Slick Reporting** a reporting engine allowing you to create & di"
},
{
"path": "docs/source/ref/computation_field.rst",
"chars": 806,
"preview": ".. _computation_field_ref:\n\nComputationField API\n--------------------\n\n.. autoclass:: slick_reporting.fields.Computation"
},
{
"path": "docs/source/ref/dynamic_model.rst",
"chars": 1591,
"preview": ".. _dynamic_model_ref:\n\n=============\nDynamic Model\n=============\n\n.. module:: slick_reporting.dynamic_model\n\n``get_dyna"
},
{
"path": "docs/source/ref/index.rst",
"chars": 290,
"preview": ".. _reference:\n\nReference\n===========\n\nBelow are links to the reference documentation for the various components of the "
},
{
"path": "docs/source/ref/report_generator.rst",
"chars": 1182,
"preview": ".. _report_generator:\n\nReport Generator API\n====================\n\nThe main class responsible generating the report and m"
},
{
"path": "docs/source/ref/settings.rst",
"chars": 4150,
"preview": ".. _settings:\n\n\nSettings\n========\n\n.. note::\n\n Settings are changed in version 1.1.1 to being a dictionary instea"
},
{
"path": "docs/source/ref/view_options.rst",
"chars": 10890,
"preview": ".. _report_view_options:\n\n================\nThe Report View\n================\n\n\nBelow is the list of options that can be u"
},
{
"path": "docs/source/topics/charts.rst",
"chars": 5231,
"preview": "Charts Customization\n====================\n\nCharts Configuration\n---------------------\n\nReportView ``charts_settings`` is"
},
{
"path": "docs/source/topics/computation_field.rst",
"chars": 6586,
"preview": ".. _computation_field:\n\n\nComputation Field\n=================\n\nComputationFields are the basic unit in a report.they repr"
},
{
"path": "docs/source/topics/crosstab_options.rst",
"chars": 5118,
"preview": ".. _crosstab_reports:\n\nCrosstab Reports\n=================\nUse crosstab reports, also known as matrix reports, to show th"
},
{
"path": "docs/source/topics/dynamic_model.rst",
"chars": 7314,
"preview": ".. _dynamic_model_topic:\n\n==============\nDynamic Models\n==============\n\nGeneral use case\n----------------\n\nSometimes you"
},
{
"path": "docs/source/topics/exporting.rst",
"chars": 1876,
"preview": "Exporting\n=========\n\nExporting to CSV\n-----------------\nTo trigger an export to CSV, just add ``?_export=csv`` to the ur"
},
{
"path": "docs/source/topics/filter_form.rst",
"chars": 4620,
"preview": ".. _filter_form:\n\nCustomizing Filter Form\n=======================\n\nThe filter form is a form that is used to filter the "
},
{
"path": "docs/source/topics/group_by_report.rst",
"chars": 5096,
"preview": ".. _group_by_topic:\n\n================\nGroup By Reports\n================\n\nGeneral use case\n----------------\n\nGroup by rep"
},
{
"path": "docs/source/topics/index.rst",
"chars": 1191,
"preview": ".. _topics:\n\nTopics\n======\n\nReportView is a ``django.views.generic.FromView`` subclass that exposes the **Report Generat"
},
{
"path": "docs/source/topics/integrating_slick_reporting.rst",
"chars": 5066,
"preview": "Integrating reports into your front end\n=======================================\n\nTo integrate Slick Reporting into your "
},
{
"path": "docs/source/topics/list_report_options.rst",
"chars": 1069,
"preview": ".. _list_reports:\n\nList Reports\n============\n\n\nIt's a simple ListView / admin changelist like report to display data in "
},
{
"path": "docs/source/topics/pivot_report.rst",
"chars": 6649,
"preview": ".. _pivot_report_topic:\n\n============================\nPrecomputed Crosstab Reports\n============================\n\nGeneral"
},
{
"path": "docs/source/topics/structure.rst",
"chars": 243,
"preview": ".. _structure:\n\n================\nRows and columns\n================\n\nIt's natural to think of a report as a form of tabul"
},
{
"path": "docs/source/topics/time_series_options.rst",
"chars": 8967,
"preview": ".. _time_series:\n\nTime Series Reports\n===================\n\nA Time series report is a report that is generated for a peri"
},
{
"path": "docs/source/topics/widgets.rst",
"chars": 3709,
"preview": ".. _widgets:\n.. _dashboard:\n\nDashboards\n==========\nYou can use the report data and charts on any other page, for example"
},
{
"path": "docs/source/tour.rst",
"chars": 7494,
"preview": ".. _usage:\n\nA walk through\n==============\n\nUpdate\n~~~~~~\nYou can now go to https://django-slick-reporting for a better a"
},
{
"path": "docs/source/tutorial.rst",
"chars": 13612,
"preview": ".. _tutorial:\n\n=========\nTutorial\n=========\n\nIn this tutorial we will go over how to create different reports using Slic"
},
{
"path": "pyproject.toml",
"chars": 150,
"preview": "[build-system]\nrequires = [\"setuptools>=61.0\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[tool.black]\nline-length = 120\n\n"
},
{
"path": "requirements.txt",
"chars": 65,
"preview": "Django\npython-dateutil>=2.8.1\npytz\nsimplejson\ndjango-crispy-forms"
},
{
"path": "runtests.py",
"chars": 901,
"preview": "#!/usr/bin/env python\nimport os\nimport sys\n\nimport argparse\nimport django\nfrom django.conf import settings\nfrom django.t"
},
{
"path": "scripts/extract_changelog.py",
"chars": 929,
"preview": "#!/usr/bin/env python\n\"\"\"Extract a single version's section from CHANGELOG.md and print it to stdout.\n\nUsage: python scr"
},
{
"path": "setup.cfg",
"chars": 1726,
"preview": "[metadata]\nlicense_file = LICENSE.md\nname = django-slick-reporting\nversion = attr: slick_reporting.__version__\nauthor = "
},
{
"path": "setup.py",
"chars": 38,
"preview": "from setuptools import setup\n\nsetup()\n"
},
{
"path": "slick_reporting/__init__.py",
"chars": 104,
"preview": "default_app_config = \"slick_reporting.apps.ReportAppConfig\"\n\nVERSION = (1, 4, 0)\n\n__version__ = \"1.4.0\"\n"
},
{
"path": "slick_reporting/app_settings.py",
"chars": 4759,
"preview": "from django.conf import settings\nfrom django.urls import get_callable\nfrom django.utils.functional import lazy\nfrom djan"
},
{
"path": "slick_reporting/apps.py",
"chars": 214,
"preview": "from django import apps\n\n\nclass ReportAppConfig(apps.AppConfig):\n verbose_name = \"Slick Reporting\"\n name = \"slick_"
},
{
"path": "slick_reporting/decorators.py",
"chars": 722,
"preview": "def report_field_register(report_field, *args, **kwargs):\n \"\"\"\n Registers the given model(s) classes and wrapped M"
},
{
"path": "slick_reporting/dynamic_model.py",
"chars": 7102,
"preview": "from django.db import models, connections, OperationalError, ProgrammingError\n\n_model_cache = {}\n\nFIELD_TYPE_MAP = {\n "
},
{
"path": "slick_reporting/fields.py",
"chars": 19901,
"preview": "from __future__ import annotations\n\nfrom warnings import warn\n\nfrom django.db.models import Sum, Q\nfrom django.template."
},
{
"path": "slick_reporting/form_factory.py",
"chars": 198,
"preview": "import warnings\n\n# warn deprecated\nwarnings.warn(\n \"slick_reporting.form_factory is deprecated. Use slick_reporting.f"
},
{
"path": "slick_reporting/forms.py",
"chars": 13200,
"preview": "from collections import OrderedDict\n\nfrom crispy_forms.helper import FormHelper\nfrom django import forms\nfrom django.uti"
},
{
"path": "slick_reporting/generator.py",
"chars": 45865,
"preview": "import datetime\nimport logging\nfrom dataclasses import dataclass\nfrom inspect import isclass\n\nfrom django.core.exception"
},
{
"path": "slick_reporting/helpers.py",
"chars": 1875,
"preview": "from collections import OrderedDict\n\nfrom django.conf import settings\nfrom django.contrib.contenttypes.fields import Gen"
},
{
"path": "slick_reporting/locale/ar/LC_MESSAGES/django.po",
"chars": 2188,
"preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
},
{
"path": "slick_reporting/locale/de/LC_MESSAGES/django.po",
"chars": 2118,
"preview": "# SOME DESCRIPTIVE TITLE.\n# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER\n# This file is distributed under the same "
},
{
"path": "slick_reporting/registry.py",
"chars": 1667,
"preview": "from __future__ import unicode_literals\n\nfrom django.contrib.admin.sites import AlreadyRegistered, NotRegistered\n\n\nclass"
},
{
"path": "slick_reporting/static/slick_reporting/slick_reporting.chartsjs.js",
"chars": 10077,
"preview": "// type / title_source / data_source,\n// title\n\n(function ($) {\n\n\n var COLORS = ['#7cb5ec', '#f7a35c', '#90ee7e', '#7"
},
{
"path": "slick_reporting/static/slick_reporting/slick_reporting.datatable.js",
"chars": 6044,
"preview": "/**\n * Created by ramez on 2/5/15.\n * A wrapper around Datatables.net\n *\n */\n\n\n(function ($) {\n let _cache = {};\n "
},
{
"path": "slick_reporting/static/slick_reporting/slick_reporting.highchart.js",
"chars": 17244,
"preview": "/**\n * Created by Ramez on 11/20/14.\n * Updated to support modern Highcharts API (v11+).\n */\n(function ($) {\n\n fu"
},
{
"path": "slick_reporting/static/slick_reporting/slick_reporting.js",
"chars": 3676,
"preview": "(function ($) {\n\n function executeFunctionByName(functionName, context /*, args */) {\n let args = Array.protot"
},
{
"path": "slick_reporting/static/slick_reporting/slick_reporting.report_loader.js",
"chars": 6803,
"preview": "/*jshint esversion: 6 */\n\n/**\n * Created by ramezashraf on 13/08/16.\n */\n\n(function ($) {\n let settings = {};\n\n fu"
},
{
"path": "slick_reporting/templates/slick_reporting/base.html",
"chars": 1176,
"preview": "<!doctype html>\n{% load static %}\n{% load crispy_forms_tags %}\n\n<html lang=\"en\">\n<head>\n <!-- Required meta tags -->\n"
},
{
"path": "slick_reporting/templates/slick_reporting/js_resources.html",
"chars": 357,
"preview": "{% load i18n static slick_reporting_tags %}\n\n{% get_slick_reporting_settings as slick_reporting_settings %}\n\n{% add_jque"
},
{
"path": "slick_reporting/templates/slick_reporting/print_report.html",
"chars": 1395,
"preview": "{% load i18n %}\n{% get_current_language as LANGUAGE_CODE %}\n{% get_current_language_bidi as LANGUAGE_BIDI %}\n<!DOCTYPE h"
},
{
"path": "slick_reporting/templates/slick_reporting/print_report_controls.html",
"chars": 239,
"preview": "{% load i18n %}\n{# Override this template to customise the on-screen controls shown above the printed page. #}\n<div clas"
},
{
"path": "slick_reporting/templates/slick_reporting/print_report_footer.html",
"chars": 167,
"preview": "{# Override this template in your project to add a custom print footer (e.g. signatures, notes, page numbers). #}\n{# Con"
},
{
"path": "slick_reporting/templates/slick_reporting/print_report_header.html",
"chars": 166,
"preview": "{# Override this template in your project to add a custom print header (e.g. company name, logo, date range). #}\n{# Cont"
},
{
"path": "slick_reporting/templates/slick_reporting/report.html",
"chars": 1130,
"preview": "{% extends 'slick_reporting/base.html' %}\n{% load crispy_forms_tags i18n slick_reporting_tags %}\n\n{% block content %}\n "
},
{
"path": "slick_reporting/templates/slick_reporting/report_form.html",
"chars": 1075,
"preview": "{% load i18n crispy_forms_tags %}\n<form id=\"reportForm\" class=\"card\">\n <div class=\"card-header\">\n <h3 class=\"c"
},
{
"path": "slick_reporting/templates/slick_reporting/widget_template.html",
"chars": 1002,
"preview": "{% load slick_reporting_tags %}\n\n\n<div class=\"card\">\n {% if display_title %}\n <div class=\"card-header\">\n "
},
{
"path": "slick_reporting/templatetags/__init__.py",
"chars": 1,
"preview": "\n"
},
{
"path": "slick_reporting/templatetags/slick_reporting_tags.py",
"chars": 3585,
"preview": "from django import template\nfrom django.template.loader import get_template\nfrom django.forms import Media\nfrom django.t"
},
{
"path": "slick_reporting/views.py",
"chars": 23755,
"preview": "import csv\nimport datetime\nimport warnings\n\nimport simplejson as json\nfrom django import forms\nfrom django.conf import s"
},
{
"path": "tests/__init__.py",
"chars": 1,
"preview": "\n"
},
{
"path": "tests/models.py",
"chars": 11011,
"preview": "from django.contrib.contenttypes.fields import GenericForeignKey\nfrom django.contrib.contenttypes.models import ContentT"
},
{
"path": "tests/report_generators.py",
"chars": 11177,
"preview": "from __future__ import annotations\n\nimport datetime\n\nfrom django.db.models import Sum, Count\nfrom django.utils.translati"
},
{
"path": "tests/requirements.txt",
"chars": 40,
"preview": "-r ../requirements.txt\ncrispy-bootstrap4"
},
{
"path": "tests/settings.py",
"chars": 1845,
"preview": "import os\n\nSECRET_KEY = \"fake-key\"\n\nPROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\nBASE_DIR "
},
{
"path": "tests/templates/base.html",
"chars": 166,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Title</title>\n {% block content %}\n "
},
{
"path": "tests/test_dynamic_model.py",
"chars": 8374,
"preview": "import datetime\n\nfrom django.db import connection, models\nfrom django.db.models import Sum, Count\nfrom django.test impor"
},
{
"path": "tests/test_generator.py",
"chars": 17926,
"preview": "from datetime import datetime\n\nfrom django.db.models import Sum\nfrom django.test import TestCase\nfrom django.utils.trans"
},
{
"path": "tests/test_pivot_generator.py",
"chars": 15262,
"preview": "import datetime\n\nfrom django.db import connection\nfrom django.test import TestCase\n\nfrom slick_reporting.dynamic_model i"
},
{
"path": "tests/tests.py",
"chars": 31163,
"preview": "import datetime\nfrom unittest import skip\nfrom unittest.mock import patch\n\nfrom django.contrib.auth import get_user_mode"
},
{
"path": "tests/urls.py",
"chars": 1164,
"preview": "from django.urls import path\nfrom . import views\n\nurlpatterns = [\n path(\"report1/\", views.MonthlyProductSales.as_view"
},
{
"path": "tests/views.py",
"chars": 4183,
"preview": "from slick_reporting.views import ReportView\nfrom slick_reporting.fields import ComputationField, TotalReportField\nfrom "
}
]
About this extraction
This page contains the full source code of the RamezIssac/django-slick-reporting GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 121 files (520.2 KB), approximately 119.8k tokens, and a symbol index with 527 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.