mirror of
https://github.com/inventree/InvenTree.git
synced 2025-05-16 03:53:07 +00:00
Typed report context (#9431)
* add typed report context * make it py3.9 compatible * fix docs * debug docs * fix for py 3.9 * add requested error codes
This commit is contained in:
parent
b2db0b67e0
commit
75b47f8d09
5
docs/.gitignore
vendored
5
docs/.gitignore
vendored
@ -25,3 +25,8 @@ inventree_settings.json
|
||||
inventree_tags.yml
|
||||
|
||||
.vscode/
|
||||
|
||||
inventree_filters.yml
|
||||
inventree_report_context.json
|
||||
inventree_settings.json
|
||||
inventree_tags.yml
|
||||
|
@ -11,16 +11,7 @@ Context variables are provided to each template when it is rendered. The availab
|
||||
|
||||
In addition to the model-specific context variables, the following global context variables are available to all templates:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| base_url | The base URL for the InvenTree instance |
|
||||
| date | Current date, represented as a Python datetime.date object |
|
||||
| datetime | Current datetime, represented as a Python datetime object |
|
||||
| template | The report template instance which is being rendered against |
|
||||
| template_description | Description of the report template |
|
||||
| template_name | Name of the report template |
|
||||
| template_revision | Revision of the report template |
|
||||
| user | User who made the request to render the template |
|
||||
{{ report_context("base", "global") }}
|
||||
|
||||
::: report.models.ReportTemplateBase.base_context
|
||||
options:
|
||||
@ -30,10 +21,7 @@ In addition to the model-specific context variables, the following global contex
|
||||
|
||||
In addition to the [global context](#global-context), all *report* templates have access to the following context variables:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| page_size | The page size of the report |
|
||||
| landscape | Boolean value, True if the report is in landscape mode |
|
||||
{{ report_context("base", "report") }}
|
||||
|
||||
Note that custom plugins may also add additional context variables to the report context.
|
||||
|
||||
@ -45,10 +33,7 @@ Note that custom plugins may also add additional context variables to the report
|
||||
|
||||
In addition to the [global context](#global-context), all *label* templates have access to the following context variables:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| width | The width of the label (in mm) |
|
||||
| height | The height of the label (in mm) |
|
||||
{{ report_context("base", "label") }}
|
||||
|
||||
Note that custom plugins may also add additional context variables to the label context.
|
||||
|
||||
@ -56,7 +41,6 @@ Note that custom plugins may also add additional context variables to the label
|
||||
options:
|
||||
show_source: True
|
||||
|
||||
|
||||
## Template Types
|
||||
|
||||
Templates (whether for generating [reports](./report.md) or [labels](./labels.md)) are rendered against a particular "model" type. The following model types are supported, and can have templates renderer against them:
|
||||
@ -76,19 +60,7 @@ Templates (whether for generating [reports](./report.md) or [labels](./labels.md
|
||||
|
||||
When printing a report or label against a [Build Order](../build/build.md) object, the following context variables are available:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| bom_items | Query set of all BuildItem objects associated with the BuildOrder |
|
||||
| build | The BuildOrder instance itself |
|
||||
| build_outputs | Query set of all BuildItem objects associated with the BuildOrder |
|
||||
| line_items | Query set of all build line items associated with the BuildOrder |
|
||||
| part | The Part object which is being assembled in the build order |
|
||||
| quantity | The total quantity of the part being assembled |
|
||||
| reference | The reference field of the BuildOrder |
|
||||
| title | The title field of the BuildOrder |
|
||||
| build.creation_date | The date when the build was created |
|
||||
| build.target_date | The given target date of the build order |
|
||||
| build.completion_date | The date when the build was completed |
|
||||
{{ report_context("models", "build") }}
|
||||
|
||||
::: build.models.Build.report_context
|
||||
options:
|
||||
@ -98,145 +70,69 @@ When printing a report or label against a [Build Order](../build/build.md) objec
|
||||
|
||||
When printing a report or label against a [BuildOrderLineItem](../build/build.md) object, the following context variables are available:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| allocated_quantity | The quantity of the part which has been allocated to this build |
|
||||
| allocations | A query set of all StockItem objects which have been allocated to this build line |
|
||||
| bom_item | The BomItem associated with this line item |
|
||||
| build | The BuildOrder instance associated with this line item |
|
||||
| build_line | The build line instance itself |
|
||||
| part | The sub-part (component) associated with the linked BomItem instance |
|
||||
| quantity | The quantity required for this line item |
|
||||
{{ report_context("models", "buildline") }}
|
||||
|
||||
::: build.models.BuildLine.report_context
|
||||
options:
|
||||
show_source: True
|
||||
|
||||
|
||||
### Sales Order
|
||||
|
||||
When printing a report or label against a [SalesOrder](../order/sales_order.md) object, the following context variables are available:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| customer | The customer object associated with the SalesOrder |
|
||||
| description | The description field of the SalesOrder |
|
||||
| extra_lines | Query set of all extra lines associated with the SalesOrder |
|
||||
| lines | Query set of all line items associated with the SalesOrder |
|
||||
| order | The SalesOrder instance itself |
|
||||
| reference | The reference field of the SalesOrder |
|
||||
| title | The title (string representation) of the SalesOrder |
|
||||
| order.creation_date | The date when the order was created |
|
||||
| order.target_date | The given target date |
|
||||
| order.shipment_date | The date when the order was shipped to the customer |
|
||||
{{ report_context("models", "salesorder") }}
|
||||
|
||||
::: order.models.Order.report_context
|
||||
options:
|
||||
show_source: True
|
||||
|
||||
### Sales Order Shipment
|
||||
|
||||
When printing a report or label against a [SalesOrderShipment](../order/sales_order.md#sales-order-shipments) object, the following context variables are available:
|
||||
|
||||
{{ report_context("models", "salesordershipment") }}
|
||||
|
||||
::: order.models.SalesOrderShipment.report_context
|
||||
options:
|
||||
show_source: True
|
||||
|
||||
### Return Order
|
||||
|
||||
When printing a report or label against a [ReturnOrder](../order/return_order.md) object, the following context variables are available:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| customer | The customer object associated with the ReturnOrder |
|
||||
| description | The description field of the ReturnOrder |
|
||||
| extra_lines | Query set of all extra lines associated with the ReturnOrder |
|
||||
| lines | Query set of all line items associated with the ReturnOrder |
|
||||
| order | The ReturnOrder instance itself |
|
||||
| reference | The reference field of the ReturnOrder |
|
||||
| title | The title (string representation) of the ReturnOrder |
|
||||
| order.creation_date | The date when the order was created |
|
||||
| order.target_date | The given target date |
|
||||
| order.issue_date | The date when the return order was issued |
|
||||
{{ report_context("models", "returnorder") }}
|
||||
|
||||
### Purchase Order
|
||||
|
||||
When printing a report or label against a [PurchaseOrder](../order/purchase_order.md) object, the following context variables are available:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| description | The description field of the PurchaseOrder |
|
||||
| extra_lines | Query set of all extra lines associated with the PurchaseOrder |
|
||||
| lines | Query set of all line items associated with the PurchaseOrder |
|
||||
| order | The PurchaseOrder instance itself |
|
||||
| reference | The reference field of the PurchaseOrder |
|
||||
| supplier | The supplier object associated with the PurchaseOrder |
|
||||
| title | The title (string representation) of the PurchaseOrder |
|
||||
| order.creation_date | The date when the order was created |
|
||||
| order.target_date | The given target date |
|
||||
| order.issue_date | The date then the order was issued |
|
||||
| order.complete_date | The date then the order was received and completed |
|
||||
{{ report_context("models", "purchaseorder") }}
|
||||
|
||||
### Stock Item
|
||||
|
||||
When printing a report or label against a [StockItem](../stock/stock.md#stock-item) object, the following context variables are available:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| barcode_data | Generated barcode data for the StockItem |
|
||||
| barcode_hash | Hash of the barcode data |
|
||||
| batch | The batch code for the StockItem |
|
||||
| child_items | Query set of all StockItem objects which are children of this StockItem |
|
||||
| ipn | The IPN (internal part number) of the associated Part |
|
||||
| installed_items | Query set of all StockItem objects which are installed in this StockItem |
|
||||
| item | The StockItem object itself |
|
||||
| name | The name of the associated Part |
|
||||
| part | The Part object which is associated with the StockItem |
|
||||
| qr_data | Generated QR code data for the StockItem |
|
||||
| qr_url | Generated URL for embedding in a QR code |
|
||||
| parameters | Dict object containing the parameters associated with the base Part |
|
||||
| quantity | The quantity of the StockItem |
|
||||
| result_list | FLattened list of TestResult data associated with the stock item |
|
||||
| results | Dict object of TestResult data associated with the StockItem |
|
||||
| serial | The serial number of the StockItem |
|
||||
| stock_item | The StockItem object itself (shadow of 'item') |
|
||||
| tests | Dict object of TestResult data associated with the StockItem (shadow of 'results') |
|
||||
| test_keys | List of test keys associated with the StockItem |
|
||||
| test_template_list | List of test templates associated with the StockItem |
|
||||
| test_templates | Dict object of test templates associated with the StockItem |
|
||||
{{ report_context("models", "stockitem") }}
|
||||
|
||||
::: stock.models.StockItem.report_context
|
||||
options:
|
||||
show_source: True
|
||||
|
||||
|
||||
### Stock Location
|
||||
|
||||
When printing a report or label against a [StockLocation](../stock/stock.md#stock-location) object, the following context variables are available:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| location | The StockLocation object itself |
|
||||
| qr_data | Formatted QR code data for the StockLocation |
|
||||
| parent | The parent StockLocation object |
|
||||
| stock_location | The StockLocation object itself (shadow of 'location') |
|
||||
| stock_items | Query set of all StockItem objects which are located in the StockLocation |
|
||||
{{ report_context("models", "stocklocation") }}
|
||||
|
||||
::: stock.models.StockLocation.report_context
|
||||
options:
|
||||
show_source: True
|
||||
|
||||
|
||||
### Part
|
||||
|
||||
When printing a report or label against a [Part](../part/part.md) object, the following context variables are available:
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| bom_items | Query set of all BomItem objects associated with the Part |
|
||||
| category | The PartCategory object associated with the Part |
|
||||
| description | The description field of the Part |
|
||||
| IPN | The IPN (internal part number) of the Part |
|
||||
| name | The name of the Part |
|
||||
| parameters | Dict object containing the parameters associated with the Part |
|
||||
| part | The Part object itself |
|
||||
| qr_data | Formatted QR code data for the Part |
|
||||
| qr_url | Generated URL for embedding in a QR code |
|
||||
| revision | The revision of the Part |
|
||||
| test_template_list | List of test templates associated with the Part |
|
||||
| test_templates | Dict object of test templates associated with the Part |
|
||||
{{ report_context("models", "part") }}
|
||||
|
||||
::: part.models.Part.report_context
|
||||
options:
|
||||
|
@ -22,6 +22,16 @@ Raise an issue if none of these options work.
|
||||
The used invoke executable is the wrong one. InvenTree needs to have
|
||||
You probably have a reference to invoke or a directory with invoke in your PATH variable that is not in InvenTrees virtual environment. You can check this by running `which invoke` and `which python` in your installations base directory and compare the output. If they are not the same, you need to adjust your PATH variable to point to the correct virtual environment before it lists other directories with invoke.
|
||||
|
||||
#### INVE-E3
|
||||
**Report Context use custom QuerySet**
|
||||
|
||||
As the `django.db.models.QuerySet` is not a generic class, we would loose type information without `django-stubs`. Therefore use the `report.mixins.QuerySet` generic class when typing a report context.
|
||||
|
||||
#### INVE-E4
|
||||
**Model missing report_context return type annotation**
|
||||
|
||||
Models that implement the `InvenTreeReportMixin` must have an explicit return type annotation for the `report_context` function.
|
||||
|
||||
### INVE-W (InvenTree Warning)
|
||||
Warnings - These are non-critical errors which should be addressed when possible.
|
||||
|
||||
|
18
docs/main.py
18
docs/main.py
@ -4,6 +4,7 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
import textwrap
|
||||
from typing import Literal
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
@ -33,6 +34,7 @@ global GLOBAL_SETTINGS
|
||||
global USER_SETTINGS
|
||||
global TAGS
|
||||
global FILTERS
|
||||
global REPORT_CONTEXT
|
||||
|
||||
# Read in the InvenTree settings file
|
||||
here = os.path.dirname(__file__)
|
||||
@ -50,6 +52,9 @@ with open(os.path.join(here, 'inventree_tags.yml'), encoding='utf-8') as f:
|
||||
# Filters
|
||||
with open(os.path.join(here, 'inventree_filters.yml'), encoding='utf-8') as f:
|
||||
FILTERS = yaml.load(f, yaml.BaseLoader)
|
||||
# Report context
|
||||
with open(os.path.join(here, 'inventree_report_context.json'), encoding='utf-8') as f:
|
||||
REPORT_CONTEXT = json.load(f)
|
||||
|
||||
|
||||
def get_repo_url(raw=False):
|
||||
@ -334,3 +339,16 @@ def define_env(env):
|
||||
ret_data += '\n'
|
||||
|
||||
return ret_data
|
||||
|
||||
@env.macro
|
||||
def report_context(type_: Literal['models', 'base'], model: str):
|
||||
"""Extract information on a particular report context."""
|
||||
global REPORT_CONTEXT
|
||||
|
||||
context = REPORT_CONTEXT.get(type_).get(model)
|
||||
|
||||
ret_data = '| Variable | Type | Description |\n| --- | --- | --- |\n'
|
||||
for k, v in context['context'].items():
|
||||
ret_data += f'| {k} | `{v["type"]}` | {v["description"]} |\n'
|
||||
|
||||
return ret_data
|
||||
|
@ -56,6 +56,8 @@ ignore = [
|
||||
# - RUF032 - decimal-from-float-literal
|
||||
"RUF032",
|
||||
"RUF045",
|
||||
# - UP045 - Use `X | None` instead of `Optional[X]`
|
||||
"UP045",
|
||||
|
||||
# TODO These should be followed up and fixed
|
||||
# - B904 Within an `except` clause, raise exceptions
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import io
|
||||
from decimal import Decimal
|
||||
from typing import Optional
|
||||
from typing import Optional, cast
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from django.conf import settings
|
||||
@ -27,7 +27,7 @@ from InvenTree.format import format_money
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
|
||||
def get_base_url(request=None):
|
||||
def get_base_url(request=None) -> str:
|
||||
"""Return the base URL for the InvenTree server.
|
||||
|
||||
The base URL is determined in the following order of decreasing priority:
|
||||
@ -56,7 +56,7 @@ def get_base_url(request=None):
|
||||
# Check if a global InvenTree setting is provided
|
||||
try:
|
||||
if site_url := get_global_setting('INVENTREE_BASE_URL', create=False):
|
||||
return site_url
|
||||
return cast(str, site_url)
|
||||
except (ProgrammingError, OperationalError):
|
||||
pass
|
||||
|
||||
|
@ -0,0 +1,141 @@
|
||||
"""Custom management command to export the available report context.
|
||||
|
||||
This is used to generate a JSON file which contains all available report
|
||||
context, so that they can be introspected by the InvenTree documentation system.
|
||||
|
||||
This in turn allows report context to be documented in the InvenTree documentation,
|
||||
without having to manually duplicate the information in multiple places.
|
||||
"""
|
||||
|
||||
import json
|
||||
from typing import get_args, get_origin, get_type_hints
|
||||
|
||||
import django.db.models
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from report.mixins import QuerySet
|
||||
|
||||
|
||||
def get_type_str(type_obj):
|
||||
"""Get the type str of a type object, including any generic parameters."""
|
||||
if type_obj is django.db.models.QuerySet:
|
||||
raise CommandError(
|
||||
'INVE-E3 - Do not use django.db.models.QuerySet directly for typing, use report.mixins.QuerySet instead.'
|
||||
)
|
||||
|
||||
if origin := get_origin(type_obj):
|
||||
# use abbreviated name for QuerySet to save space
|
||||
origin_str = 'QuerySet' if origin is QuerySet else get_type_str(origin)
|
||||
|
||||
return f'{origin_str}[{", ".join(get_type_str(arg) for arg in get_args(type_obj))}]'
|
||||
|
||||
if type_obj is type(None):
|
||||
return 'None'
|
||||
|
||||
if type_obj.__module__ == 'builtins':
|
||||
return type_obj.__name__
|
||||
|
||||
# in python3.9, typing.Union has no __name__
|
||||
if not hasattr(type_obj, '__module__') or not hasattr(type_obj, '__name__'):
|
||||
return str(type_obj)
|
||||
|
||||
return f'{type_obj.__module__}.{type_obj.__name__}'
|
||||
|
||||
|
||||
def parse_docstring(docstring: str):
|
||||
"""Parse the docstring of a type object and return a dictionary of sections."""
|
||||
sections = {}
|
||||
current_section = None
|
||||
|
||||
for line in docstring.splitlines():
|
||||
stripped = line.strip()
|
||||
|
||||
if not stripped:
|
||||
continue
|
||||
|
||||
if stripped.endswith(':'):
|
||||
current_section = stripped.rstrip(':')
|
||||
sections[current_section] = {}
|
||||
elif ':' in stripped and current_section:
|
||||
name, doc = stripped.split(':', 1)
|
||||
sections[current_section][name.strip()] = doc.strip()
|
||||
|
||||
return sections
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""Extract report context information, and export to a JSON file."""
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""Add custom arguments for this command."""
|
||||
parser.add_argument(
|
||||
'filename',
|
||||
type=str,
|
||||
help='Output filename for the report context definitions',
|
||||
)
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
"""Export report context information to a JSON file."""
|
||||
from report.helpers import report_model_types
|
||||
from report.models import (
|
||||
BaseContextExtension,
|
||||
LabelContextExtension,
|
||||
ReportContextExtension,
|
||||
)
|
||||
|
||||
context = {'models': {}, 'base': {}}
|
||||
is_error = False
|
||||
|
||||
# Base context models
|
||||
for key, model in [
|
||||
('global', BaseContextExtension),
|
||||
('report', ReportContextExtension),
|
||||
('label', LabelContextExtension),
|
||||
]:
|
||||
context['base'][key] = {'key': key, 'context': {}}
|
||||
|
||||
attributes = parse_docstring(model.__doc__).get('Attributes', {})
|
||||
for k, v in get_type_hints(model).items():
|
||||
context['base'][key]['context'][k] = {
|
||||
'description': attributes.get(k, ''),
|
||||
'type': get_type_str(v),
|
||||
}
|
||||
|
||||
# Report context models
|
||||
for model in report_model_types():
|
||||
model_key = model.__name__.lower()
|
||||
model_name = str(model._meta.verbose_name)
|
||||
|
||||
if (
|
||||
ctx_type := get_type_hints(model.report_context).get('return', None)
|
||||
) is None:
|
||||
print(
|
||||
f'Error: Model {model}.report_context does not have a return type annotation'
|
||||
)
|
||||
is_error = True
|
||||
continue
|
||||
|
||||
context['models'][model_key] = {
|
||||
'key': model_key,
|
||||
'name': model_name,
|
||||
'context': {},
|
||||
}
|
||||
|
||||
attributes = parse_docstring(ctx_type.__doc__).get('Attributes', {})
|
||||
for k, v in get_type_hints(ctx_type).items():
|
||||
context['models'][model_key]['context'][k] = {
|
||||
'description': attributes.get(k, ''),
|
||||
'type': get_type_str(v),
|
||||
}
|
||||
|
||||
if is_error:
|
||||
raise CommandError(
|
||||
'INVE-E4 - Some models associated with the `InvenTreeReportMixin` do not have a valid `report_context` return type annotation.'
|
||||
)
|
||||
|
||||
filename = kwargs.get('filename', 'inventree_report_context.json')
|
||||
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
json.dump(context, f, indent=4)
|
||||
|
||||
print(f"Exported InvenTree report context definitions to '{filename}'")
|
@ -947,7 +947,7 @@ class InvenTreeBarcodeMixin(models.Model):
|
||||
)
|
||||
|
||||
def format_barcode(self, **kwargs):
|
||||
"""Return a JSON string for formatting a QR code for this model instance."""
|
||||
"""Return a string for formatting a QR code for this model instance."""
|
||||
from plugin.base.barcodes.helper import generate_barcode
|
||||
|
||||
return generate_barcode(self)
|
||||
@ -966,7 +966,7 @@ class InvenTreeBarcodeMixin(models.Model):
|
||||
return data
|
||||
|
||||
@property
|
||||
def barcode(self):
|
||||
def barcode(self) -> str:
|
||||
"""Format a minimal barcode string (e.g. for label printing)."""
|
||||
return self.format_barcode()
|
||||
|
||||
|
@ -49,6 +49,30 @@ from stock.status_codes import StockHistoryCode, StockStatus
|
||||
logger = structlog.get_logger('inventree')
|
||||
|
||||
|
||||
class BuildReportContext(report.mixins.BaseReportContext):
|
||||
"""Context for the Build model.
|
||||
|
||||
Attributes:
|
||||
bom_items: Query set of all BuildItem objects associated with the BuildOrder
|
||||
build: The BuildOrder instance itself
|
||||
build_outputs: Query set of all BuildItem objects associated with the BuildOrder
|
||||
line_items: Query set of all build line items associated with the BuildOrder
|
||||
part: The Part object which is being assembled in the build order
|
||||
quantity: The total quantity of the part being assembled
|
||||
reference: The reference field of the BuildOrder
|
||||
title: The title field of the BuildOrder
|
||||
"""
|
||||
|
||||
bom_items: report.mixins.QuerySet[part.models.BomItem]
|
||||
build: 'Build'
|
||||
build_outputs: report.mixins.QuerySet[stock.models.StockItem]
|
||||
line_items: report.mixins.QuerySet['BuildLine']
|
||||
part: part.models.Part
|
||||
quantity: int
|
||||
reference: str
|
||||
title: str
|
||||
|
||||
|
||||
class Build(
|
||||
report.mixins.InvenTreeReportMixin,
|
||||
InvenTree.models.InvenTreeAttachmentMixin,
|
||||
@ -183,7 +207,7 @@ class Build(
|
||||
'target_date': _('Target date must be after start date')
|
||||
})
|
||||
|
||||
def report_context(self) -> dict:
|
||||
def report_context(self) -> BuildReportContext:
|
||||
"""Generate custom report context data."""
|
||||
return {
|
||||
'bom_items': self.part.get_bom_items(),
|
||||
@ -1454,6 +1478,28 @@ def after_save_build(sender, instance: Build, created: bool, **kwargs):
|
||||
instance.update_build_line_items()
|
||||
|
||||
|
||||
class BuildLineReportContext(report.mixins.BaseReportContext):
|
||||
"""Context for the BuildLine model.
|
||||
|
||||
Attributes:
|
||||
allocated_quantity: The quantity of the part which has been allocated to this build
|
||||
allocations: A query set of all StockItem objects which have been allocated to this build line
|
||||
bom_item: The BomItem associated with this line item
|
||||
build: The BuildOrder instance associated with this line item
|
||||
build_line: The build line instance itself
|
||||
part: The sub-part (component) associated with the linked BomItem instance
|
||||
quantity: The quantity required for this line item
|
||||
"""
|
||||
|
||||
allocated_quantity: decimal.Decimal
|
||||
allocations: report.mixins.QuerySet['BuildItem']
|
||||
bom_item: part.models.BomItem
|
||||
build: Build
|
||||
build_line: 'BuildLine'
|
||||
part: part.models.Part
|
||||
quantity: decimal.Decimal
|
||||
|
||||
|
||||
class BuildLine(report.mixins.InvenTreeReportMixin, InvenTree.models.InvenTreeModel):
|
||||
"""A BuildLine object links a BOMItem to a Build.
|
||||
|
||||
@ -1481,7 +1527,7 @@ class BuildLine(report.mixins.InvenTreeReportMixin, InvenTree.models.InvenTreeMo
|
||||
"""Return the API URL used to access this model."""
|
||||
return reverse('api-build-line-list')
|
||||
|
||||
def report_context(self):
|
||||
def report_context(self) -> BuildLineReportContext:
|
||||
"""Generate custom report context for this BuildLine object."""
|
||||
return {
|
||||
'allocated_quantity': self.allocated_quantity,
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Order model definitions."""
|
||||
|
||||
from decimal import Decimal
|
||||
from typing import Any, Optional
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
@ -177,6 +178,92 @@ class TotalPriceMixin(models.Model):
|
||||
return total
|
||||
|
||||
|
||||
class BaseOrderReportContext(report.mixins.BaseReportContext):
|
||||
"""Base context for all order models.
|
||||
|
||||
Attributes:
|
||||
description: The description field of the order
|
||||
extra_lines: Query set of all extra lines associated with the order
|
||||
lines: Query set of all line items associated with the order
|
||||
order: The order instance itself
|
||||
reference: The reference field of the order
|
||||
title: The title (string representation) of the order
|
||||
"""
|
||||
|
||||
description: str
|
||||
extra_lines: Any
|
||||
lines: Any
|
||||
order: Any
|
||||
reference: str
|
||||
title: str
|
||||
|
||||
|
||||
class PurchaseOrderReportContext(report.mixins.BaseReportContext):
|
||||
"""Context for the purchase order model.
|
||||
|
||||
Attributes:
|
||||
description: The description field of the PurchaseOrder
|
||||
reference: The reference field of the PurchaseOrder
|
||||
title: The title (string representation) of the PurchaseOrder
|
||||
extra_lines: Query set of all extra lines associated with the PurchaseOrder
|
||||
lines: Query set of all line items associated with the PurchaseOrder
|
||||
order: The PurchaseOrder instance itself
|
||||
supplier: The supplier object associated with the PurchaseOrder
|
||||
"""
|
||||
|
||||
description: str
|
||||
reference: str
|
||||
title: str
|
||||
extra_lines: report.mixins.QuerySet['PurchaseOrderExtraLine']
|
||||
lines: report.mixins.QuerySet['PurchaseOrderLineItem']
|
||||
order: 'PurchaseOrder'
|
||||
supplier: Optional[Company]
|
||||
|
||||
|
||||
class SalesOrderReportContext(report.mixins.BaseReportContext):
|
||||
"""Context for the sales order model.
|
||||
|
||||
Attributes:
|
||||
description: The description field of the SalesOrder
|
||||
reference: The reference field of the SalesOrder
|
||||
title: The title (string representation) of the SalesOrder
|
||||
extra_lines: Query set of all extra lines associated with the SalesOrder
|
||||
lines: Query set of all line items associated with the SalesOrder
|
||||
order: The SalesOrder instance itself
|
||||
customer: The customer object associated with the SalesOrder
|
||||
"""
|
||||
|
||||
description: str
|
||||
reference: str
|
||||
title: str
|
||||
extra_lines: report.mixins.QuerySet['SalesOrderExtraLine']
|
||||
lines: report.mixins.QuerySet['SalesOrderLineItem']
|
||||
order: 'SalesOrder'
|
||||
customer: Optional[Company]
|
||||
|
||||
|
||||
class ReturnOrderReportContext(report.mixins.BaseReportContext):
|
||||
"""Context for the return order model.
|
||||
|
||||
Attributes:
|
||||
description: The description field of the ReturnOrder
|
||||
reference: The reference field of the ReturnOrder
|
||||
title: The title (string representation) of the ReturnOrder
|
||||
extra_lines: Query set of all extra lines associated with the ReturnOrder
|
||||
lines: Query set of all line items associated with the ReturnOrder
|
||||
order: The ReturnOrder instance itself
|
||||
customer: The customer object associated with the ReturnOrder
|
||||
"""
|
||||
|
||||
description: str
|
||||
reference: str
|
||||
title: str
|
||||
extra_lines: report.mixins.QuerySet['ReturnOrderExtraLine']
|
||||
lines: report.mixins.QuerySet['ReturnOrderLineItem']
|
||||
order: 'ReturnOrder'
|
||||
customer: Optional[Company]
|
||||
|
||||
|
||||
class Order(
|
||||
StatusCodeMixin,
|
||||
StateTransitionMixin,
|
||||
@ -300,7 +387,7 @@ class Order(
|
||||
line.target_date = None
|
||||
line.order = self
|
||||
|
||||
def report_context(self):
|
||||
def report_context(self) -> BaseOrderReportContext:
|
||||
"""Generate context data for the reporting interface."""
|
||||
return {
|
||||
'description': self.description,
|
||||
@ -456,7 +543,7 @@ class PurchaseOrder(TotalPriceMixin, Order):
|
||||
super().clean_line_item(line)
|
||||
line.received = 0
|
||||
|
||||
def report_context(self):
|
||||
def report_context(self) -> PurchaseOrderReportContext:
|
||||
"""Return report context data for this PurchaseOrder."""
|
||||
return {**super().report_context(), 'supplier': self.supplier}
|
||||
|
||||
@ -979,7 +1066,7 @@ class SalesOrder(TotalPriceMixin, Order):
|
||||
super().clean_line_item(line)
|
||||
line.shipped = 0
|
||||
|
||||
def report_context(self):
|
||||
def report_context(self) -> SalesOrderReportContext:
|
||||
"""Generate report context data for this SalesOrder."""
|
||||
return {**super().report_context(), 'customer': self.customer}
|
||||
|
||||
@ -1802,6 +1889,26 @@ class SalesOrderLineItem(OrderLineItem):
|
||||
return self.shipped >= self.quantity
|
||||
|
||||
|
||||
class SalesOrderShipmentReportContext(report.mixins.BaseReportContext):
|
||||
"""Context for the SalesOrderShipment model.
|
||||
|
||||
Attributes:
|
||||
allocations: QuerySet of SalesOrderAllocation objects
|
||||
order: The associated SalesOrder object
|
||||
reference: Shipment reference string
|
||||
shipment: The SalesOrderShipment object itself
|
||||
tracking_number: Shipment tracking number string
|
||||
title: Title for the report
|
||||
"""
|
||||
|
||||
allocations: report.mixins.QuerySet['SalesOrderAllocation']
|
||||
order: 'SalesOrder'
|
||||
reference: str
|
||||
shipment: 'SalesOrderShipment'
|
||||
tracking_number: str
|
||||
title: str
|
||||
|
||||
|
||||
class SalesOrderShipment(
|
||||
InvenTree.models.InvenTreeAttachmentMixin,
|
||||
InvenTree.models.InvenTreeNotesMixin,
|
||||
@ -1835,7 +1942,7 @@ class SalesOrderShipment(
|
||||
"""Return the API URL associated with the SalesOrderShipment model."""
|
||||
return reverse('api-so-shipment-list')
|
||||
|
||||
def report_context(self):
|
||||
def report_context(self) -> SalesOrderShipmentReportContext:
|
||||
"""Generate context data for the reporting interface."""
|
||||
return {
|
||||
'allocations': self.allocations,
|
||||
@ -2198,7 +2305,7 @@ class ReturnOrder(TotalPriceMixin, Order):
|
||||
line.received_date = None
|
||||
line.outcome = ReturnOrderLineStatus.PENDING.value
|
||||
|
||||
def report_context(self):
|
||||
def report_context(self) -> ReturnOrderReportContext:
|
||||
"""Generate report context data for this ReturnOrder."""
|
||||
return {**super().report_context(), 'customer': self.customer}
|
||||
|
||||
|
@ -10,13 +10,14 @@ import os
|
||||
import re
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from typing import Optional, cast
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MinLengthValidator, MinValueValidator
|
||||
from django.db import models, transaction
|
||||
from django.db.models import ExpressionWrapper, F, Q, Sum, UniqueConstraint
|
||||
from django.db.models import ExpressionWrapper, F, Q, QuerySet, Sum, UniqueConstraint
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models.signals import post_delete, post_save
|
||||
from django.db.utils import IntegrityError
|
||||
@ -359,6 +360,38 @@ class PartManager(TreeManager):
|
||||
)
|
||||
|
||||
|
||||
class PartReportContext(report.mixins.BaseReportContext):
|
||||
"""Context for the part model.
|
||||
|
||||
Attributes:
|
||||
bom_items: Query set of all BomItem objects associated with the Part
|
||||
category: The PartCategory object associated with the Part
|
||||
description: The description field of the Part
|
||||
IPN: The IPN (internal part number) of the Part
|
||||
name: The name of the Part
|
||||
parameters: Dict object containing the parameters associated with the Part
|
||||
part: The Part object itself
|
||||
qr_data: Formatted QR code data for the Part
|
||||
qr_url: Generated URL for embedding in a QR code
|
||||
revision: The revision of the Part
|
||||
test_template_list: List of test templates associated with the Part
|
||||
test_templates: Dict object of test templates associated with the Part
|
||||
"""
|
||||
|
||||
bom_items: report.mixins.QuerySet[BomItem]
|
||||
category: Optional[PartCategory]
|
||||
description: str
|
||||
IPN: Optional[str]
|
||||
name: str
|
||||
parameters: dict[str, str]
|
||||
part: Part
|
||||
qr_data: str
|
||||
qr_url: str
|
||||
revision: Optional[str]
|
||||
test_template_list: report.mixins.QuerySet[PartTestTemplate]
|
||||
test_templates: dict[str, PartTestTemplate]
|
||||
|
||||
|
||||
@cleanup.ignore
|
||||
class Part(
|
||||
InvenTree.models.InvenTreeAttachmentMixin,
|
||||
@ -441,10 +474,10 @@ class Part(
|
||||
"""Return the associated barcode model type code for this model."""
|
||||
return 'PA'
|
||||
|
||||
def report_context(self):
|
||||
def report_context(self) -> PartReportContext:
|
||||
"""Return custom report context information."""
|
||||
return {
|
||||
'bom_items': self.get_bom_items(),
|
||||
'bom_items': cast(report.mixins.QuerySet['BomItem'], self.get_bom_items()),
|
||||
'category': self.category,
|
||||
'description': self.description,
|
||||
'IPN': self.IPN,
|
||||
@ -1731,7 +1764,7 @@ class Part(
|
||||
|
||||
return bom_filter
|
||||
|
||||
def get_bom_items(self, include_inherited=True):
|
||||
def get_bom_items(self, include_inherited=True) -> QuerySet[BomItem]:
|
||||
"""Return a queryset containing all BOM items for this part.
|
||||
|
||||
By default, will include inherited BOM items
|
||||
@ -2294,7 +2327,9 @@ class Part(
|
||||
|
||||
parameter.save()
|
||||
|
||||
def getTestTemplates(self, required=None, include_parent=True, enabled=None):
|
||||
def getTestTemplates(
|
||||
self, required=None, include_parent=True, enabled=None
|
||||
) -> QuerySet[PartTestTemplate]:
|
||||
"""Return a list of all test templates associated with this Part.
|
||||
|
||||
These are used for validation of a StockItem.
|
||||
|
@ -69,7 +69,7 @@ class BarcodeMixin:
|
||||
|
||||
return True
|
||||
|
||||
def generate(self, model_instance: InvenTreeBarcodeMixin):
|
||||
def generate(self, model_instance: InvenTreeBarcodeMixin) -> str:
|
||||
"""Generate barcode data for the given model instance.
|
||||
|
||||
Arguments:
|
||||
|
@ -1,7 +1,24 @@
|
||||
"""Report mixin classes."""
|
||||
|
||||
from typing import Generic, TypedDict, TypeVar
|
||||
|
||||
from django.db import models
|
||||
|
||||
_Model = TypeVar('_Model', bound=models.Model, covariant=True)
|
||||
|
||||
|
||||
class QuerySet(Generic[_Model]):
|
||||
"""A custom QuerySet class used for type hinting in report context definitions.
|
||||
|
||||
This will later be replaced by django.db.models.QuerySet, but as
|
||||
django's QuerySet is not a generic class, we need to create our own to not
|
||||
loose type data.
|
||||
"""
|
||||
|
||||
|
||||
class BaseReportContext(TypedDict):
|
||||
"""Base context for a report model."""
|
||||
|
||||
|
||||
class InvenTreeReportMixin(models.Model):
|
||||
"""A mixin class for adding report generation functionality to a model class.
|
||||
@ -15,9 +32,28 @@ class InvenTreeReportMixin(models.Model):
|
||||
|
||||
abstract = True
|
||||
|
||||
def report_context(self) -> dict:
|
||||
def report_context(self) -> BaseReportContext:
|
||||
"""Generate a dict of context data to provide to the reporting framework.
|
||||
|
||||
The default implementation returns an empty dict object.
|
||||
|
||||
This method must contain a type annotation, as it is used by the report generation framework
|
||||
to determine the type of context data that is provided to the report template.
|
||||
|
||||
Example:
|
||||
```python
|
||||
class MyModelReportContext(BaseReportContext):
|
||||
my_field: str
|
||||
po: order.models.PurchaseOrder
|
||||
bom_items: report.mixins.QuerySet[part.models.BomItem]
|
||||
|
||||
class MyModel(report.mixins.InvenTreeReportMixin):
|
||||
...
|
||||
def report_context(self) -> MyModelReportContext:
|
||||
return {
|
||||
'my_field': self.my_field,
|
||||
'po': self.po,
|
||||
}
|
||||
```
|
||||
"""
|
||||
return {}
|
||||
|
@ -3,8 +3,11 @@
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
from datetime import date, datetime
|
||||
from typing import Optional, TypedDict, cast
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.base import ContentFile
|
||||
from django.core.files.storage import default_storage
|
||||
@ -123,6 +126,56 @@ class TemplateUploadMixin:
|
||||
return super().validate_unique(exclude)
|
||||
|
||||
|
||||
class BaseContextExtension(TypedDict):
|
||||
"""Base context extension.
|
||||
|
||||
Attributes:
|
||||
base_url: The base URL for the InvenTree instance
|
||||
date: Current date, represented as a Python datetime.date object
|
||||
datetime: Current datetime, represented as a Python datetime object
|
||||
template: The report template instance which is being rendered against
|
||||
template_description: Description of the report template
|
||||
template_name: Name of the report template
|
||||
template_revision: Revision of the report template
|
||||
user: User who made the request to render the template
|
||||
"""
|
||||
|
||||
base_url: str
|
||||
date: date
|
||||
datetime: datetime
|
||||
template: 'ReportTemplateBase'
|
||||
template_description: str
|
||||
template_name: str
|
||||
template_revision: int
|
||||
user: Optional[AbstractUser]
|
||||
|
||||
|
||||
class LabelContextExtension(TypedDict):
|
||||
"""Label report context extension.
|
||||
|
||||
Attributes:
|
||||
width: The width of the label (in mm)
|
||||
height: The height of the label (in mm)
|
||||
page_style: The CSS @page style for the label template. This is used to be inserted at the top of the style block for a given label
|
||||
"""
|
||||
|
||||
width: float
|
||||
height: float
|
||||
page_style: Optional[str]
|
||||
|
||||
|
||||
class ReportContextExtension(TypedDict):
|
||||
"""Report context extension.
|
||||
|
||||
Attributes:
|
||||
page_size: The page size of the report
|
||||
landscape: Boolean value, True if the report is in landscape mode
|
||||
"""
|
||||
|
||||
page_size: str
|
||||
landscape: bool
|
||||
|
||||
|
||||
class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel):
|
||||
"""Base class for reports, labels."""
|
||||
|
||||
@ -245,7 +298,7 @@ class ReportTemplateBase(MetadataMixin, InvenTree.models.InvenTreeModel):
|
||||
"""Return a filter dict which can be applied to the target model."""
|
||||
return report.validators.validate_filters(self.filters, model=self.get_model())
|
||||
|
||||
def base_context(self, request=None):
|
||||
def base_context(self, request=None) -> BaseContextExtension:
|
||||
"""Return base context data (available to all templates)."""
|
||||
return {
|
||||
'base_url': get_base_url(request=request),
|
||||
@ -313,11 +366,11 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
help_text=_('Render report in landscape orientation'),
|
||||
)
|
||||
|
||||
def get_report_size(self):
|
||||
def get_report_size(self) -> str:
|
||||
"""Return the printable page size for this report."""
|
||||
try:
|
||||
page_size_default = get_global_setting(
|
||||
'REPORT_DEFAULT_PAGE_SIZE', 'A4', create=False
|
||||
page_size_default = cast(
|
||||
str, get_global_setting('REPORT_DEFAULT_PAGE_SIZE', 'A4', create=False)
|
||||
)
|
||||
except Exception:
|
||||
page_size_default = 'A4'
|
||||
@ -331,12 +384,14 @@ class ReportTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
|
||||
def get_context(self, instance, request=None, **kwargs):
|
||||
"""Supply context data to the report template for rendering."""
|
||||
context = {
|
||||
**super().get_context(instance, request),
|
||||
base_context = super().get_context(instance, request)
|
||||
report_context: ReportContextExtension = {
|
||||
'page_size': self.get_report_size(),
|
||||
'landscape': self.landscape,
|
||||
}
|
||||
|
||||
context = {**base_context, **report_context}
|
||||
|
||||
# Pass the context through to the plugin registry for any additional information
|
||||
for plugin in registry.with_mixin(PluginMixinEnum.REPORT):
|
||||
try:
|
||||
@ -536,12 +591,15 @@ class LabelTemplate(TemplateUploadMixin, ReportTemplateBase):
|
||||
|
||||
def get_context(self, instance, request=None, **kwargs):
|
||||
"""Supply context data to the label template for rendering."""
|
||||
context = {
|
||||
**super().get_context(instance, request, **kwargs),
|
||||
base_context = super().get_context(instance, request, **kwargs)
|
||||
label_context: LabelContextExtension = {
|
||||
'width': self.width,
|
||||
'height': self.height,
|
||||
'page_style': None,
|
||||
}
|
||||
|
||||
context = {**base_context, **label_context}
|
||||
|
||||
if kwargs.pop('insert_page_style', True):
|
||||
context['page_style'] = self.generate_page_style()
|
||||
|
||||
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal, InvalidOperation
|
||||
from typing import Optional
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
@ -114,6 +115,24 @@ class StockLocationManager(TreeManager):
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
class StockLocationReportContext(report.mixins.BaseReportContext):
|
||||
"""Report context for the StockLocation model.
|
||||
|
||||
Attributes:
|
||||
location: The StockLocation object itself
|
||||
qr_data: Formatted QR code data for the StockLocation
|
||||
parent: The parent StockLocation object
|
||||
stock_location: The StockLocation object itself (shadow of 'location')
|
||||
stock_items: Query set of all StockItem objects which are located in the StockLocation
|
||||
"""
|
||||
|
||||
location: StockLocation
|
||||
qr_data: str
|
||||
parent: Optional[StockLocation]
|
||||
stock_location: StockLocation
|
||||
stock_items: report.mixins.QuerySet[StockItem]
|
||||
|
||||
|
||||
class StockLocation(
|
||||
InvenTree.models.InvenTreeBarcodeMixin,
|
||||
report.mixins.InvenTreeReportMixin,
|
||||
@ -159,7 +178,7 @@ class StockLocation(
|
||||
"""Return the associated barcode model type code for this model."""
|
||||
return 'SL'
|
||||
|
||||
def report_context(self):
|
||||
def report_context(self) -> StockLocationReportContext:
|
||||
"""Return report context data for this StockLocation."""
|
||||
return {
|
||||
'location': self,
|
||||
@ -338,6 +357,56 @@ def default_delete_on_deplete():
|
||||
return True
|
||||
|
||||
|
||||
class StockItemReportContext(report.mixins.BaseReportContext):
|
||||
"""Report context for the StockItem model.
|
||||
|
||||
Attributes:
|
||||
barcode_data: Generated barcode data for the StockItem
|
||||
barcode_hash: Hash of the barcode data
|
||||
batch: The batch code for the StockItem
|
||||
child_items: Query set of all StockItem objects which are children of this StockItem
|
||||
ipn: The IPN (internal part number) of the associated Part
|
||||
installed_items: Query set of all StockItem objects which are installed in this StockItem
|
||||
item: The StockItem object itself
|
||||
name: The name of the associated Part
|
||||
part: The Part object which is associated with the StockItem
|
||||
qr_data: Generated QR code data for the StockItem
|
||||
qr_url: Generated URL for embedding in a QR code
|
||||
parameters: Dict object containing the parameters associated with the base Part
|
||||
quantity: The quantity of the StockItem
|
||||
result_list: FLattened list of TestResult data associated with the stock item
|
||||
results: Dict object of TestResult data associated with the StockItem
|
||||
serial: The serial number of the StockItem
|
||||
stock_item: The StockItem object itself (shadow of 'item')
|
||||
tests: Dict object of TestResult data associated with the StockItem (shadow of 'results')
|
||||
test_keys: List of test keys associated with the StockItem
|
||||
test_template_list: List of test templates associated with the StockItem
|
||||
test_templates: Dict object of test templates associated with the StockItem
|
||||
"""
|
||||
|
||||
barcode_data: str
|
||||
barcode_hash: str
|
||||
batch: str
|
||||
child_items: report.mixins.QuerySet[StockItem]
|
||||
ipn: Optional[str]
|
||||
installed_items: set[StockItem]
|
||||
item: StockItem
|
||||
name: str
|
||||
part: PartModels.Part
|
||||
qr_data: str
|
||||
qr_url: str
|
||||
parameters: dict[str, str]
|
||||
quantity: Decimal
|
||||
result_list: list[StockItemTestResult]
|
||||
results: dict[str, StockItemTestResult]
|
||||
serial: Optional[str]
|
||||
stock_item: StockItem
|
||||
tests: dict[str, StockItemTestResult]
|
||||
test_keys: list[str]
|
||||
test_template_list: report.mixins.QuerySet[PartModels.PartTestTemplate]
|
||||
test_templates: dict[str, PartModels.PartTestTemplate]
|
||||
|
||||
|
||||
class StockItem(
|
||||
InvenTree.models.InvenTreeAttachmentMixin,
|
||||
InvenTree.models.InvenTreeBarcodeMixin,
|
||||
@ -415,7 +484,7 @@ class StockItem(
|
||||
|
||||
return list(keys)
|
||||
|
||||
def report_context(self):
|
||||
def report_context(self) -> StockItemReportContext:
|
||||
"""Generate custom report context data for this StockItem."""
|
||||
return {
|
||||
'barcode_data': self.barcode_data,
|
||||
|
4
tasks.py
4
tasks.py
@ -1275,6 +1275,7 @@ def export_definitions(c, basedir: str = ''):
|
||||
Path(basedir + 'inventree_settings.json').resolve(),
|
||||
Path(basedir + 'inventree_tags.yml').resolve(),
|
||||
Path(basedir + 'inventree_filters.yml').resolve(),
|
||||
Path(basedir + 'inventree_report_context.json').resolve(),
|
||||
]
|
||||
|
||||
info('Exporting definitions...')
|
||||
@ -1286,6 +1287,9 @@ def export_definitions(c, basedir: str = ''):
|
||||
check_file_existence(filenames[2], overwrite=True)
|
||||
manage(c, f'export_filters {filenames[2]}', pty=True)
|
||||
|
||||
check_file_existence(filenames[3], overwrite=True)
|
||||
manage(c, f'export_report_context {filenames[3]}', pty=True)
|
||||
|
||||
info('Exporting definitions complete')
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user