mirror of
https://github.com/inventree/InvenTree.git
synced 2025-07-01 11:10:54 +00:00
Docstring checks in QC checks (#3089)
* Add pre-commit to the stack * exclude static * Add locales to excludes * fix style errors * rename pipeline steps * also wait on precommit * make template matching simpler * Use the same code for python setup everywhere * use step and cache for python setup * move regular settings up into general envs * just use full update * Use invoke instead of static references * make setup actions more similar * use python3 * refactor names to be similar * fix runner version * fix references * remove incidential change * use matrix for os * Github can't do this right now * ignore docstyle errors * Add seperate docstring test * update flake call * do not fail on docstring * refactor setup into workflow * update reference * switch to action * resturcture * add bash statements * remove os from cache * update input checks * make code cleaner * fix boolean * no relative paths * install wheel by python * switch to install * revert back to simple wheel * refactor import export tests * move setup keys back to not disturbe tests * remove docstyle till that is fixed * update references * continue on error * add docstring test * use relativ action references * Change step / job docstrings * update to merge * reformat comments 1 * fix docstrings 2 * fix docstrings 3 * fix docstrings 4 * fix docstrings 5 * fix docstrings 6 * fix docstrings 7 * fix docstrings 8 * fix docstirns 9 * fix docstrings 10 * docstring adjustments * update the remaining docstrings * small docstring changes * fix function name * update support files for docstrings * Add missing args to docstrings * Remove outdated function * Add docstrings for the 'build' app * Make API code cleaner * add more docstrings for plugin app * Remove dead code for plugin settings No idea what that was even intended for * ignore __init__ files for docstrings * More docstrings * Update docstrings for the 'part' directory * Fixes for related_part functionality * Fix removed stuff from merge99676ee
* make more consistent * Show statistics for docstrings * add more docstrings * move specific register statements to make them clearer to understant * More docstrings for common * and more docstrings * and more * simpler call * docstrings for notifications * docstrings for common/tests * Add docs for common/models * Revert "move specific register statements to make them clearer to understant" This reverts commitca96654622
. * use typing here * Revert "Make API code cleaner" This reverts commit24fb68bd3e
. * docstring updates for the 'users' app * Add generic Meta info to simple Meta classes * remove unneeded unique_together statements * More simple metas * Remove unnecessary format specifier * Remove extra json format specifiers * Add docstrings for the 'plugin' app * Docstrings for the 'label' app * Add missing docstrings for the 'report' app * Fix build test regression * Fix top-level files * docstrings for InvenTree/InvenTree * reduce unneeded code * add docstrings * and more docstrings * more docstrings * more docstrings for stock * more docstrings * docstrings for order/views * Docstrings for various files in the 'order' app * Docstrings for order/test_api.py * Docstrings for order/serializers.py * Docstrings for order/admin.py * More docstrings for the order app * Add docstrings for the 'company' app * Add unit tests for rebuilding the reference fields * Prune out some more dead code * remove more dead code Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
This commit is contained in:
@ -1,3 +1,5 @@
|
||||
"""Admin functionality for the 'report' app"""
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import (BillOfMaterialsReport, BuildReport, PurchaseOrderReport,
|
||||
@ -5,17 +7,17 @@ from .models import (BillOfMaterialsReport, BuildReport, PurchaseOrderReport,
|
||||
|
||||
|
||||
class ReportTemplateAdmin(admin.ModelAdmin):
|
||||
|
||||
"""Admin class for the various reporting models"""
|
||||
list_display = ('name', 'description', 'template', 'filters', 'enabled', 'revision')
|
||||
|
||||
|
||||
class ReportSnippetAdmin(admin.ModelAdmin):
|
||||
|
||||
"""Admin class for the ReportSnippet model"""
|
||||
list_display = ('id', 'snippet', 'description')
|
||||
|
||||
|
||||
class ReportAssetAdmin(admin.ModelAdmin):
|
||||
|
||||
"""Admin class for the ReportAsset model"""
|
||||
list_display = ('id', 'asset', 'description')
|
||||
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
"""API functionality for the 'report' app"""
|
||||
|
||||
from django.core.exceptions import FieldError, ValidationError
|
||||
from django.http import HttpResponse
|
||||
@ -24,9 +25,7 @@ from .serializers import (BOMReportSerializer, BuildReportSerializer,
|
||||
|
||||
|
||||
class ReportListView(generics.ListAPIView):
|
||||
"""
|
||||
Generic API class for report templates
|
||||
"""
|
||||
"""Generic API class for report templates."""
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
@ -44,15 +43,10 @@ class ReportListView(generics.ListAPIView):
|
||||
|
||||
|
||||
class StockItemReportMixin:
|
||||
"""
|
||||
Mixin for extracting stock items from query params
|
||||
"""
|
||||
"""Mixin for extracting stock items from query params."""
|
||||
|
||||
def get_items(self):
|
||||
"""
|
||||
Return a list of requested stock items
|
||||
"""
|
||||
|
||||
"""Return a list of requested stock items."""
|
||||
items = []
|
||||
|
||||
params = self.request.query_params
|
||||
@ -77,15 +71,10 @@ class StockItemReportMixin:
|
||||
|
||||
|
||||
class BuildReportMixin:
|
||||
"""
|
||||
Mixin for extracting Build items from query params
|
||||
"""
|
||||
"""Mixin for extracting Build items from query params."""
|
||||
|
||||
def get_builds(self):
|
||||
"""
|
||||
Return a list of requested Build objects
|
||||
"""
|
||||
|
||||
"""Return a list of requested Build objects."""
|
||||
builds = []
|
||||
|
||||
params = self.request.query_params
|
||||
@ -109,17 +98,13 @@ class BuildReportMixin:
|
||||
|
||||
|
||||
class OrderReportMixin:
|
||||
"""
|
||||
Mixin for extracting order items from query params
|
||||
"""Mixin for extracting order items from query params.
|
||||
|
||||
requires the OrderModel class attribute to be set!
|
||||
"""
|
||||
|
||||
def get_orders(self):
|
||||
"""
|
||||
Return a list of order objects
|
||||
"""
|
||||
|
||||
"""Return a list of order objects."""
|
||||
orders = []
|
||||
|
||||
params = self.request.query_params
|
||||
@ -143,15 +128,10 @@ class OrderReportMixin:
|
||||
|
||||
|
||||
class PartReportMixin:
|
||||
"""
|
||||
Mixin for extracting part items from query params
|
||||
"""
|
||||
"""Mixin for extracting part items from query params."""
|
||||
|
||||
def get_parts(self):
|
||||
"""
|
||||
Return a list of requested part objects
|
||||
"""
|
||||
|
||||
"""Return a list of requested part objects."""
|
||||
parts = []
|
||||
|
||||
params = self.request.query_params
|
||||
@ -176,15 +156,10 @@ class PartReportMixin:
|
||||
|
||||
|
||||
class ReportPrintMixin:
|
||||
"""
|
||||
Mixin for printing reports
|
||||
"""
|
||||
"""Mixin for printing reports."""
|
||||
|
||||
def print(self, request, items_to_print):
|
||||
"""
|
||||
Print this report template against a number of pre-validated items.
|
||||
"""
|
||||
|
||||
"""Print this report template against a number of pre-validated items."""
|
||||
if len(items_to_print) == 0:
|
||||
# No valid items provided, return an error message
|
||||
data = {
|
||||
@ -229,19 +204,13 @@ class ReportPrintMixin:
|
||||
report_name += '.pdf'
|
||||
|
||||
if debug_mode:
|
||||
"""
|
||||
Contatenate all rendered templates into a single HTML string,
|
||||
and return the string as a HTML response.
|
||||
"""
|
||||
"""Contatenate all rendered templates into a single HTML string, and return the string as a HTML response."""
|
||||
|
||||
html = "\n".join(outputs)
|
||||
|
||||
return HttpResponse(html)
|
||||
else:
|
||||
"""
|
||||
Concatenate all rendered pages into a single PDF object,
|
||||
and return the resulting document!
|
||||
"""
|
||||
"""Concatenate all rendered pages into a single PDF object, and return the resulting document!"""
|
||||
|
||||
pages = []
|
||||
|
||||
@ -283,21 +252,19 @@ class ReportPrintMixin:
|
||||
|
||||
|
||||
class StockItemTestReportList(ReportListView, StockItemReportMixin):
|
||||
"""
|
||||
API endpoint for viewing list of TestReport objects.
|
||||
"""API endpoint for viewing list of TestReport objects.
|
||||
|
||||
Filterable by:
|
||||
|
||||
- enabled: Filter by enabled / disabled status
|
||||
- item: Filter by stock item(s)
|
||||
|
||||
"""
|
||||
|
||||
queryset = TestReport.objects.all()
|
||||
serializer_class = TestReportSerializer
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
|
||||
"""Custom queryset filtering"""
|
||||
queryset = super().filter_queryset(queryset)
|
||||
|
||||
# List of StockItem objects to match against
|
||||
@ -347,35 +314,27 @@ class StockItemTestReportList(ReportListView, StockItemReportMixin):
|
||||
|
||||
|
||||
class StockItemTestReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API endpoint for a single TestReport object
|
||||
"""
|
||||
"""API endpoint for a single TestReport object."""
|
||||
|
||||
queryset = TestReport.objects.all()
|
||||
serializer_class = TestReportSerializer
|
||||
|
||||
|
||||
class StockItemTestReportPrint(generics.RetrieveAPIView, StockItemReportMixin, ReportPrintMixin):
|
||||
"""
|
||||
API endpoint for printing a TestReport object
|
||||
"""
|
||||
"""API endpoint for printing a TestReport object."""
|
||||
|
||||
queryset = TestReport.objects.all()
|
||||
serializer_class = TestReportSerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Check if valid stock item(s) have been provided.
|
||||
"""
|
||||
|
||||
"""Check if valid stock item(s) have been provided."""
|
||||
items = self.get_items()
|
||||
|
||||
return self.print(request, items)
|
||||
|
||||
|
||||
class BOMReportList(ReportListView, PartReportMixin):
|
||||
"""
|
||||
API endpoint for viewing a list of BillOfMaterialReport objects.
|
||||
"""API endpoint for viewing a list of BillOfMaterialReport objects.
|
||||
|
||||
Filterably by:
|
||||
|
||||
@ -387,7 +346,7 @@ class BOMReportList(ReportListView, PartReportMixin):
|
||||
serializer_class = BOMReportSerializer
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
|
||||
"""Custom queryset filtering"""
|
||||
queryset = super().filter_queryset(queryset)
|
||||
|
||||
# List of Part objects to match against
|
||||
@ -436,35 +395,27 @@ class BOMReportList(ReportListView, PartReportMixin):
|
||||
|
||||
|
||||
class BOMReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API endpoint for a single BillOfMaterialReport object
|
||||
"""
|
||||
"""API endpoint for a single BillOfMaterialReport object."""
|
||||
|
||||
queryset = BillOfMaterialsReport.objects.all()
|
||||
serializer_class = BOMReportSerializer
|
||||
|
||||
|
||||
class BOMReportPrint(generics.RetrieveAPIView, PartReportMixin, ReportPrintMixin):
|
||||
"""
|
||||
API endpoint for printing a BillOfMaterialReport object
|
||||
"""
|
||||
"""API endpoint for printing a BillOfMaterialReport object."""
|
||||
|
||||
queryset = BillOfMaterialsReport.objects.all()
|
||||
serializer_class = BOMReportSerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""
|
||||
Check if valid part item(s) have been provided
|
||||
"""
|
||||
|
||||
"""Check if valid part item(s) have been provided."""
|
||||
parts = self.get_parts()
|
||||
|
||||
return self.print(request, parts)
|
||||
|
||||
|
||||
class BuildReportList(ReportListView, BuildReportMixin):
|
||||
"""
|
||||
API endpoint for viewing a list of BuildReport objects.
|
||||
"""API endpoint for viewing a list of BuildReport objects.
|
||||
|
||||
Can be filtered by:
|
||||
|
||||
@ -476,7 +427,7 @@ class BuildReportList(ReportListView, BuildReportMixin):
|
||||
serializer_class = BuildReportSerializer
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
|
||||
"""Custom queryset filtering"""
|
||||
queryset = super().filter_queryset(queryset)
|
||||
|
||||
# List of Build objects to match against
|
||||
@ -526,45 +477,41 @@ class BuildReportList(ReportListView, BuildReportMixin):
|
||||
|
||||
|
||||
class BuildReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API endpoint for a single BuildReport object
|
||||
"""
|
||||
"""API endpoint for a single BuildReport object."""
|
||||
|
||||
queryset = BuildReport.objects.all()
|
||||
serializer_class = BuildReportSerializer
|
||||
|
||||
|
||||
class BuildReportPrint(generics.RetrieveAPIView, BuildReportMixin, ReportPrintMixin):
|
||||
"""
|
||||
API endpoint for printing a BuildReport
|
||||
"""
|
||||
"""API endpoint for printing a BuildReport."""
|
||||
|
||||
queryset = BuildReport.objects.all()
|
||||
serializer_class = BuildReportSerializer
|
||||
|
||||
def get(self, request, *ars, **kwargs):
|
||||
|
||||
"""Perform a GET action to print the report"""
|
||||
builds = self.get_builds()
|
||||
|
||||
return self.print(request, builds)
|
||||
|
||||
|
||||
class PurchaseOrderReportList(ReportListView, OrderReportMixin):
|
||||
|
||||
"""API list endpoint for the PurchaseOrderReport model"""
|
||||
OrderModel = order.models.PurchaseOrder
|
||||
|
||||
queryset = PurchaseOrderReport.objects.all()
|
||||
serializer_class = PurchaseOrderReportSerializer
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
|
||||
"""Custom queryset filter for the PurchaseOrderReport list"""
|
||||
queryset = super().filter_queryset(queryset)
|
||||
|
||||
orders = self.get_orders()
|
||||
|
||||
if len(orders) > 0:
|
||||
"""
|
||||
We wish to filter by purchase orders
|
||||
We wish to filter by purchase orders.
|
||||
|
||||
We need to compare the 'filters' string of each report,
|
||||
and see if it matches against each of the specified orders.
|
||||
@ -607,18 +554,14 @@ class PurchaseOrderReportList(ReportListView, OrderReportMixin):
|
||||
|
||||
|
||||
class PurchaseOrderReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API endpoint for a single PurchaseOrderReport object
|
||||
"""
|
||||
"""API endpoint for a single PurchaseOrderReport object."""
|
||||
|
||||
queryset = PurchaseOrderReport.objects.all()
|
||||
serializer_class = PurchaseOrderReportSerializer
|
||||
|
||||
|
||||
class PurchaseOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
||||
"""
|
||||
API endpoint for printing a PurchaseOrderReport object
|
||||
"""
|
||||
"""API endpoint for printing a PurchaseOrderReport object."""
|
||||
|
||||
OrderModel = order.models.PurchaseOrder
|
||||
|
||||
@ -626,28 +569,28 @@ class PurchaseOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, Repor
|
||||
serializer_class = PurchaseOrderReportSerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
"""Perform GET request to print the report"""
|
||||
orders = self.get_orders()
|
||||
|
||||
return self.print(request, orders)
|
||||
|
||||
|
||||
class SalesOrderReportList(ReportListView, OrderReportMixin):
|
||||
|
||||
"""API list endpoint for the SalesOrderReport model"""
|
||||
OrderModel = order.models.SalesOrder
|
||||
|
||||
queryset = SalesOrderReport.objects.all()
|
||||
serializer_class = SalesOrderReportSerializer
|
||||
|
||||
def filter_queryset(self, queryset):
|
||||
|
||||
"""Custom queryset filtering for the SalesOrderReport API list"""
|
||||
queryset = super().filter_queryset(queryset)
|
||||
|
||||
orders = self.get_orders()
|
||||
|
||||
if len(orders) > 0:
|
||||
"""
|
||||
We wish to filter by purchase orders
|
||||
We wish to filter by purchase orders.
|
||||
|
||||
We need to compare the 'filters' string of each report,
|
||||
and see if it matches against each of the specified orders.
|
||||
@ -690,18 +633,14 @@ class SalesOrderReportList(ReportListView, OrderReportMixin):
|
||||
|
||||
|
||||
class SalesOrderReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API endpoint for a single SalesOrderReport object
|
||||
"""
|
||||
"""API endpoint for a single SalesOrderReport object."""
|
||||
|
||||
queryset = SalesOrderReport.objects.all()
|
||||
serializer_class = SalesOrderReportSerializer
|
||||
|
||||
|
||||
class SalesOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
||||
"""
|
||||
API endpoint for printing a PurchaseOrderReport object
|
||||
"""
|
||||
"""API endpoint for printing a PurchaseOrderReport object."""
|
||||
|
||||
OrderModel = order.models.SalesOrder
|
||||
|
||||
@ -709,7 +648,7 @@ class SalesOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPr
|
||||
serializer_class = SalesOrderReportSerializer
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
|
||||
"""Perform a GET request to print the report"""
|
||||
orders = self.get_orders()
|
||||
|
||||
return self.print(request, orders)
|
||||
|
@ -1,3 +1,5 @@
|
||||
"""Config options for the 'report' app"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
@ -11,22 +13,17 @@ logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
class ReportConfig(AppConfig):
|
||||
"""Configuration class for the 'report' app"""
|
||||
name = 'report'
|
||||
|
||||
def ready(self):
|
||||
"""
|
||||
This function is called whenever the report app is loaded
|
||||
"""
|
||||
|
||||
"""This function is called whenever the report app is loaded."""
|
||||
if canAppAccessDatabase(allow_test=True):
|
||||
self.create_default_test_reports()
|
||||
self.create_default_build_reports()
|
||||
|
||||
def create_default_reports(self, model, reports):
|
||||
"""
|
||||
Copy defualt report files across to the media directory.
|
||||
"""
|
||||
|
||||
"""Copy defualt report files across to the media directory."""
|
||||
# Source directory for report templates
|
||||
src_dir = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
@ -82,11 +79,7 @@ class ReportConfig(AppConfig):
|
||||
pass
|
||||
|
||||
def create_default_test_reports(self):
|
||||
"""
|
||||
Create database entries for the default TestReport templates,
|
||||
if they do not already exist
|
||||
"""
|
||||
|
||||
"""Create database entries for the default TestReport templates, if they do not already exist."""
|
||||
try:
|
||||
from .models import TestReport
|
||||
except: # pragma: no cover
|
||||
@ -105,11 +98,7 @@ class ReportConfig(AppConfig):
|
||||
self.create_default_reports(TestReport, reports)
|
||||
|
||||
def create_default_build_reports(self):
|
||||
"""
|
||||
Create database entries for the default BuildReport templates
|
||||
(if they do not already exist)
|
||||
"""
|
||||
|
||||
"""Create database entries for the default BuildReport templates (if they do not already exist)"""
|
||||
try:
|
||||
from .models import BuildReport
|
||||
except: # pragma: no cover
|
||||
|
@ -1,6 +1,4 @@
|
||||
"""
|
||||
Report template model definitions
|
||||
"""
|
||||
"""Report template model definitions."""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
@ -36,94 +34,78 @@ logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
def rename_template(instance, filename):
|
||||
"""
|
||||
Helper function for 'renaming' uploaded report files.
|
||||
"""Helper function for 'renaming' uploaded report files.
|
||||
|
||||
Pass responsibility back to the calling class,
|
||||
to ensure that files are uploaded to the correct directory.
|
||||
"""
|
||||
|
||||
return instance.rename_file(filename)
|
||||
|
||||
|
||||
def validate_stock_item_report_filters(filters):
|
||||
"""
|
||||
Validate filter string against StockItem model
|
||||
"""
|
||||
|
||||
"""Validate filter string against StockItem model."""
|
||||
return validateFilterString(filters, model=stock.models.StockItem)
|
||||
|
||||
|
||||
def validate_part_report_filters(filters):
|
||||
"""
|
||||
Validate filter string against Part model
|
||||
"""
|
||||
|
||||
"""Validate filter string against Part model."""
|
||||
return validateFilterString(filters, model=part.models.Part)
|
||||
|
||||
|
||||
def validate_build_report_filters(filters):
|
||||
"""
|
||||
Validate filter string against Build model
|
||||
"""
|
||||
|
||||
"""Validate filter string against Build model."""
|
||||
return validateFilterString(filters, model=build.models.Build)
|
||||
|
||||
|
||||
def validate_purchase_order_filters(filters):
|
||||
"""
|
||||
Validate filter string against PurchaseOrder model
|
||||
"""
|
||||
|
||||
"""Validate filter string against PurchaseOrder model."""
|
||||
return validateFilterString(filters, model=order.models.PurchaseOrder)
|
||||
|
||||
|
||||
def validate_sales_order_filters(filters):
|
||||
"""
|
||||
Validate filter string against SalesOrder model
|
||||
"""
|
||||
|
||||
"""Validate filter string against SalesOrder model."""
|
||||
return validateFilterString(filters, model=order.models.SalesOrder)
|
||||
|
||||
|
||||
class WeasyprintReportMixin(WeasyTemplateResponseMixin):
|
||||
"""
|
||||
Class for rendering a HTML template to a PDF.
|
||||
"""
|
||||
"""Class for rendering a HTML template to a PDF."""
|
||||
|
||||
pdf_filename = 'report.pdf'
|
||||
pdf_attachment = True
|
||||
|
||||
def __init__(self, request, template, **kwargs):
|
||||
|
||||
"""Initialize the report mixin with some standard attributes"""
|
||||
self.request = request
|
||||
self.template_name = template
|
||||
self.pdf_filename = kwargs.get('filename', 'report.pdf')
|
||||
|
||||
|
||||
class ReportBase(models.Model):
|
||||
"""
|
||||
Base class for uploading html templates
|
||||
"""
|
||||
"""Base class for uploading html templates."""
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options. Abstract ensures no database table is created."""
|
||||
|
||||
abstract = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
"""Perform additional actions when the report is saved"""
|
||||
# Increment revision number
|
||||
self.revision += 1
|
||||
|
||||
super().save()
|
||||
|
||||
def __str__(self):
|
||||
"""Format a string representation of a report instance"""
|
||||
return "{n} - {d}".format(n=self.name, d=self.description)
|
||||
|
||||
@classmethod
|
||||
def getSubdir(cls):
|
||||
"""Return the subdirectory where template files for this report model will be located."""
|
||||
return ''
|
||||
|
||||
def rename_file(self, filename):
|
||||
# Function for renaming uploaded file
|
||||
"""Function for renaming uploaded file"""
|
||||
|
||||
filename = os.path.basename(filename)
|
||||
|
||||
@ -147,15 +129,15 @@ class ReportBase(models.Model):
|
||||
|
||||
@property
|
||||
def extension(self):
|
||||
"""Return the filename extension of the associated template file"""
|
||||
return os.path.splitext(self.template.name)[1].lower()
|
||||
|
||||
@property
|
||||
def template_name(self):
|
||||
"""
|
||||
Returns the file system path to the template file.
|
||||
"""Returns the file system path to the template file.
|
||||
|
||||
Required for passing the file to an external process
|
||||
"""
|
||||
|
||||
template = self.template.name
|
||||
template = template.replace('/', os.path.sep)
|
||||
template = template.replace('\\', os.path.sep)
|
||||
@ -192,28 +174,20 @@ class ReportBase(models.Model):
|
||||
|
||||
|
||||
class ReportTemplateBase(ReportBase):
|
||||
"""
|
||||
Reporting template model.
|
||||
"""Reporting template model.
|
||||
|
||||
Able to be passed context data
|
||||
|
||||
"""
|
||||
|
||||
# Pass a single top-level object to the report template
|
||||
object_to_print = None
|
||||
|
||||
def get_context_data(self, request):
|
||||
"""
|
||||
Supply context data to the template for rendering
|
||||
"""
|
||||
|
||||
"""Supply context data to the template for rendering."""
|
||||
return {}
|
||||
|
||||
def context(self, request):
|
||||
"""
|
||||
All context to be passed to the renderer.
|
||||
"""
|
||||
|
||||
"""All context to be passed to the renderer."""
|
||||
# Generate custom context data based on the particular report subclass
|
||||
context = self.get_context_data(request)
|
||||
|
||||
@ -230,10 +204,7 @@ class ReportTemplateBase(ReportBase):
|
||||
return context
|
||||
|
||||
def generate_filename(self, request, **kwargs):
|
||||
"""
|
||||
Generate a filename for this report
|
||||
"""
|
||||
|
||||
"""Generate a filename for this report."""
|
||||
template_string = Template(self.filename_pattern)
|
||||
|
||||
ctx = self.context(request)
|
||||
@ -243,21 +214,17 @@ class ReportTemplateBase(ReportBase):
|
||||
return template_string.render(context)
|
||||
|
||||
def render_as_string(self, request, **kwargs):
|
||||
"""
|
||||
Render the report to a HTML string.
|
||||
"""Render the report to a HTML string.
|
||||
|
||||
Useful for debug mode (viewing generated code)
|
||||
"""
|
||||
|
||||
return render_to_string(self.template_name, self.context(request), request)
|
||||
|
||||
def render(self, request, **kwargs):
|
||||
"""
|
||||
Render the template to a PDF file.
|
||||
"""Render the template to a PDF file.
|
||||
|
||||
Uses django-weasyprint plugin to render HTML template against Weasyprint
|
||||
"""
|
||||
|
||||
# TODO: Support custom filename generation!
|
||||
# filename = kwargs.get('filename', 'report.pdf')
|
||||
|
||||
@ -288,20 +255,22 @@ class ReportTemplateBase(ReportBase):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options. Abstract ensures no database table is created."""
|
||||
|
||||
abstract = True
|
||||
|
||||
|
||||
class TestReport(ReportTemplateBase):
|
||||
"""
|
||||
Render a TestReport against a StockItem object.
|
||||
"""
|
||||
"""Render a TestReport against a StockItem object."""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
"""Return the API URL associated with the TestReport model"""
|
||||
return reverse('api-stockitem-testreport-list')
|
||||
|
||||
@classmethod
|
||||
def getSubdir(cls):
|
||||
"""Return the subdirectory where TestReport templates are located"""
|
||||
return 'test'
|
||||
|
||||
filters = models.CharField(
|
||||
@ -321,10 +290,7 @@ class TestReport(ReportTemplateBase):
|
||||
)
|
||||
|
||||
def matches_stock_item(self, item):
|
||||
"""
|
||||
Test if this report template matches a given StockItem objects
|
||||
"""
|
||||
|
||||
"""Test if this report template matches a given StockItem objects."""
|
||||
try:
|
||||
filters = validateFilterString(self.filters)
|
||||
items = stock.models.StockItem.objects.filter(**filters)
|
||||
@ -337,7 +303,7 @@ class TestReport(ReportTemplateBase):
|
||||
return items.exists()
|
||||
|
||||
def get_context_data(self, request):
|
||||
|
||||
"""Return custom context data for the TestReport template"""
|
||||
stock_item = self.object_to_print
|
||||
|
||||
return {
|
||||
@ -352,16 +318,16 @@ class TestReport(ReportTemplateBase):
|
||||
|
||||
|
||||
class BuildReport(ReportTemplateBase):
|
||||
"""
|
||||
Build order / work order report
|
||||
"""
|
||||
"""Build order / work order report."""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
"""Return the API URL associated with the BuildReport model"""
|
||||
return reverse('api-build-report-list')
|
||||
|
||||
@classmethod
|
||||
def getSubdir(cls):
|
||||
"""Return the subdirectory where BuildReport templates are located"""
|
||||
return 'build'
|
||||
|
||||
filters = models.CharField(
|
||||
@ -375,10 +341,7 @@ class BuildReport(ReportTemplateBase):
|
||||
)
|
||||
|
||||
def get_context_data(self, request):
|
||||
"""
|
||||
Custom context data for the build report
|
||||
"""
|
||||
|
||||
"""Custom context data for the build report."""
|
||||
my_build = self.object_to_print
|
||||
|
||||
if type(my_build) != build.models.Build:
|
||||
@ -395,16 +358,16 @@ class BuildReport(ReportTemplateBase):
|
||||
|
||||
|
||||
class BillOfMaterialsReport(ReportTemplateBase):
|
||||
"""
|
||||
Render a Bill of Materials against a Part object
|
||||
"""
|
||||
"""Render a Bill of Materials against a Part object."""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
"""Return the API URL associated with the BillOfMaterialsReport model"""
|
||||
return reverse('api-bom-report-list')
|
||||
|
||||
@classmethod
|
||||
def getSubdir(cls):
|
||||
"""Retun the directory where BillOfMaterialsReport templates are located"""
|
||||
return 'bom'
|
||||
|
||||
filters = models.CharField(
|
||||
@ -418,7 +381,7 @@ class BillOfMaterialsReport(ReportTemplateBase):
|
||||
)
|
||||
|
||||
def get_context_data(self, request):
|
||||
|
||||
"""Return custom context data for the BillOfMaterialsReport template"""
|
||||
part = self.object_to_print
|
||||
|
||||
return {
|
||||
@ -429,16 +392,16 @@ class BillOfMaterialsReport(ReportTemplateBase):
|
||||
|
||||
|
||||
class PurchaseOrderReport(ReportTemplateBase):
|
||||
"""
|
||||
Render a report against a PurchaseOrder object
|
||||
"""
|
||||
"""Render a report against a PurchaseOrder object."""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
"""Return the API URL associated with the PurchaseOrderReport model"""
|
||||
return reverse('api-po-report-list')
|
||||
|
||||
@classmethod
|
||||
def getSubdir(cls):
|
||||
"""Return the directory where PurchaseOrderReport templates are stored"""
|
||||
return 'purchaseorder'
|
||||
|
||||
filters = models.CharField(
|
||||
@ -452,7 +415,7 @@ class PurchaseOrderReport(ReportTemplateBase):
|
||||
)
|
||||
|
||||
def get_context_data(self, request):
|
||||
|
||||
"""Return custom context data for the PurchaseOrderReport template"""
|
||||
order = self.object_to_print
|
||||
|
||||
return {
|
||||
@ -468,16 +431,16 @@ class PurchaseOrderReport(ReportTemplateBase):
|
||||
|
||||
|
||||
class SalesOrderReport(ReportTemplateBase):
|
||||
"""
|
||||
Render a report against a SalesOrder object
|
||||
"""
|
||||
"""Render a report against a SalesOrder object."""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
"""Return the API URL associated with the SalesOrderReport model"""
|
||||
return reverse('api-so-report-list')
|
||||
|
||||
@classmethod
|
||||
def getSubdir(cls):
|
||||
"""Retun the subdirectory where SalesOrderReport templates are located"""
|
||||
return 'salesorder'
|
||||
|
||||
filters = models.CharField(
|
||||
@ -491,7 +454,7 @@ class SalesOrderReport(ReportTemplateBase):
|
||||
)
|
||||
|
||||
def get_context_data(self, request):
|
||||
|
||||
"""Return custom context data for a SalesOrderReport template"""
|
||||
order = self.object_to_print
|
||||
|
||||
return {
|
||||
@ -507,6 +470,7 @@ class SalesOrderReport(ReportTemplateBase):
|
||||
|
||||
|
||||
def rename_snippet(instance, filename):
|
||||
"""Function to rename a report snippet once uploaded"""
|
||||
|
||||
filename = os.path.basename(filename)
|
||||
|
||||
@ -530,9 +494,7 @@ def rename_snippet(instance, filename):
|
||||
|
||||
|
||||
class ReportSnippet(models.Model):
|
||||
"""
|
||||
Report template 'snippet' which can be used to make templates
|
||||
that can then be included in other reports.
|
||||
"""Report template 'snippet' which can be used to make templates that can then be included in other reports.
|
||||
|
||||
Useful for 'common' template actions, sub-templates, etc
|
||||
"""
|
||||
@ -548,6 +510,7 @@ class ReportSnippet(models.Model):
|
||||
|
||||
|
||||
def rename_asset(instance, filename):
|
||||
"""Function to rename an asset file when uploaded"""
|
||||
|
||||
filename = os.path.basename(filename)
|
||||
|
||||
@ -567,14 +530,15 @@ def rename_asset(instance, filename):
|
||||
|
||||
|
||||
class ReportAsset(models.Model):
|
||||
"""
|
||||
Asset file for use in report templates.
|
||||
"""Asset file for use in report templates.
|
||||
|
||||
For example, an image to use in a header file.
|
||||
Uploaded asset files appear in MEDIA_ROOT/report/assets,
|
||||
and can be loaded in a template using the {% report_asset <filename> %} tag.
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
"""String representation of a ReportAsset instance"""
|
||||
return os.path.basename(self.asset.name)
|
||||
|
||||
asset = models.FileField(
|
||||
|
@ -1,3 +1,4 @@
|
||||
"""API serializers for the reporting models"""
|
||||
|
||||
from InvenTree.serializers import (InvenTreeAttachmentSerializerField,
|
||||
InvenTreeModelSerializer)
|
||||
@ -7,10 +8,13 @@ from .models import (BillOfMaterialsReport, BuildReport, PurchaseOrderReport,
|
||||
|
||||
|
||||
class TestReportSerializer(InvenTreeModelSerializer):
|
||||
"""Serializer class for the TestReport model"""
|
||||
|
||||
template = InvenTreeAttachmentSerializerField(required=True)
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options."""
|
||||
|
||||
model = TestReport
|
||||
fields = [
|
||||
'pk',
|
||||
@ -23,10 +27,13 @@ class TestReportSerializer(InvenTreeModelSerializer):
|
||||
|
||||
|
||||
class BuildReportSerializer(InvenTreeModelSerializer):
|
||||
"""Serializer class for the BuildReport model"""
|
||||
|
||||
template = InvenTreeAttachmentSerializerField(required=True)
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options."""
|
||||
|
||||
model = BuildReport
|
||||
fields = [
|
||||
'pk',
|
||||
@ -39,10 +46,12 @@ class BuildReportSerializer(InvenTreeModelSerializer):
|
||||
|
||||
|
||||
class BOMReportSerializer(InvenTreeModelSerializer):
|
||||
|
||||
"""Serializer class for the BillOfMaterialsReport model"""
|
||||
template = InvenTreeAttachmentSerializerField(required=True)
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options."""
|
||||
|
||||
model = BillOfMaterialsReport
|
||||
fields = [
|
||||
'pk',
|
||||
@ -55,10 +64,12 @@ class BOMReportSerializer(InvenTreeModelSerializer):
|
||||
|
||||
|
||||
class PurchaseOrderReportSerializer(InvenTreeModelSerializer):
|
||||
|
||||
"""Serializer class for the PurchaseOrdeReport model"""
|
||||
template = InvenTreeAttachmentSerializerField(required=True)
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options."""
|
||||
|
||||
model = PurchaseOrderReport
|
||||
fields = [
|
||||
'pk',
|
||||
@ -71,10 +82,12 @@ class PurchaseOrderReportSerializer(InvenTreeModelSerializer):
|
||||
|
||||
|
||||
class SalesOrderReportSerializer(InvenTreeModelSerializer):
|
||||
|
||||
"""Serializer class for the SalesOrderReport model"""
|
||||
template = InvenTreeAttachmentSerializerField(required=True)
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options."""
|
||||
|
||||
model = SalesOrderReport
|
||||
fields = [
|
||||
'pk',
|
||||
|
@ -1,6 +1,4 @@
|
||||
"""
|
||||
Template tags for rendering various barcodes
|
||||
"""
|
||||
"""Template tags for rendering various barcodes."""
|
||||
|
||||
import base64
|
||||
from io import BytesIO
|
||||
@ -14,12 +12,10 @@ register = template.Library()
|
||||
|
||||
|
||||
def image_data(img, fmt='PNG'):
|
||||
"""
|
||||
Convert an image into HTML renderable data
|
||||
"""Convert an image into HTML renderable data.
|
||||
|
||||
Returns a string ``data:image/FMT;base64,xxxxxxxxx`` which can be rendered to an <img> tag
|
||||
"""
|
||||
|
||||
buffered = BytesIO()
|
||||
img.save(buffered, format=fmt)
|
||||
|
||||
@ -30,8 +26,7 @@ def image_data(img, fmt='PNG'):
|
||||
|
||||
@register.simple_tag()
|
||||
def qrcode(data, **kwargs):
|
||||
"""
|
||||
Return a byte-encoded QR code image
|
||||
"""Return a byte-encoded QR code image.
|
||||
|
||||
Optional kwargs
|
||||
---------------
|
||||
@ -39,7 +34,6 @@ def qrcode(data, **kwargs):
|
||||
fill_color: Fill color (default = black)
|
||||
back_color: Background color (default = white)
|
||||
"""
|
||||
|
||||
# Construct "default" values
|
||||
params = dict(
|
||||
box_size=20,
|
||||
@ -63,10 +57,7 @@ def qrcode(data, **kwargs):
|
||||
|
||||
@register.simple_tag()
|
||||
def barcode(data, barcode_class='code128', **kwargs):
|
||||
"""
|
||||
Render a barcode
|
||||
"""
|
||||
|
||||
"""Render a barcode."""
|
||||
constructor = python_barcode.get_barcode_class(barcode_class)
|
||||
|
||||
data = str(data).zfill(constructor.digits)
|
||||
|
@ -1,6 +1,4 @@
|
||||
"""
|
||||
Custom template tags for report generation
|
||||
"""
|
||||
"""Custom template tags for report generation."""
|
||||
|
||||
import os
|
||||
|
||||
@ -19,10 +17,7 @@ register = template.Library()
|
||||
|
||||
@register.simple_tag()
|
||||
def asset(filename):
|
||||
"""
|
||||
Return fully-qualified path for an upload report asset file.
|
||||
"""
|
||||
|
||||
"""Return fully-qualified path for an upload report asset file."""
|
||||
# If in debug mode, return URL to the image, not a local file
|
||||
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
|
||||
|
||||
@ -38,10 +33,7 @@ def asset(filename):
|
||||
|
||||
@register.simple_tag()
|
||||
def part_image(part):
|
||||
"""
|
||||
Return a fully-qualified path for a part image
|
||||
"""
|
||||
|
||||
"""Return a fully-qualified path for a part image."""
|
||||
# If in debug mode, return URL to the image, not a local file
|
||||
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
|
||||
|
||||
@ -75,10 +67,7 @@ def part_image(part):
|
||||
|
||||
@register.simple_tag()
|
||||
def company_image(company):
|
||||
"""
|
||||
Return a fully-qualified path for a company image
|
||||
"""
|
||||
|
||||
"""Return a fully-qualified path for a company image."""
|
||||
# If in debug mode, return the URL to the image, not a local file
|
||||
debug_mode = InvenTreeSetting.get_setting('REPORT_DEBUG_MODE')
|
||||
|
||||
@ -108,15 +97,13 @@ def company_image(company):
|
||||
|
||||
@register.simple_tag()
|
||||
def internal_link(link, text):
|
||||
"""
|
||||
Make a <a></a> href which points to an InvenTree URL.
|
||||
"""Make a <a></a> href which points to an InvenTree URL.
|
||||
|
||||
Important Note: This only works if the INVENTREE_BASE_URL parameter is set!
|
||||
|
||||
If the INVENTREE_BASE_URL parameter is not configured,
|
||||
the text will be returned (unlinked)
|
||||
"""
|
||||
|
||||
text = str(text)
|
||||
|
||||
url = InvenTree.helpers.construct_absolute_url(link)
|
||||
|
@ -1,3 +1,4 @@
|
||||
"""Unit testing for the various report models"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
@ -14,7 +15,7 @@ from stock.models import StockItem
|
||||
|
||||
|
||||
class ReportTest(InvenTreeAPITestCase):
|
||||
|
||||
"""Base class for unit testing reporting models"""
|
||||
fixtures = [
|
||||
'category',
|
||||
'part',
|
||||
@ -32,14 +33,8 @@ class ReportTest(InvenTreeAPITestCase):
|
||||
detail_url = None
|
||||
print_url = None
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
def copyReportTemplate(self, filename, description):
|
||||
"""
|
||||
Copy the provided report template into the required media directory
|
||||
"""
|
||||
|
||||
"""Copy the provided report template into the required media directory."""
|
||||
src_dir = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
'templates',
|
||||
@ -81,10 +76,7 @@ class ReportTest(InvenTreeAPITestCase):
|
||||
)
|
||||
|
||||
def test_list_endpoint(self):
|
||||
"""
|
||||
Test that the LIST endpoint works for each report
|
||||
"""
|
||||
|
||||
"""Test that the LIST endpoint works for each report."""
|
||||
if not self.list_url:
|
||||
return
|
||||
|
||||
@ -121,7 +113,7 @@ class ReportTest(InvenTreeAPITestCase):
|
||||
|
||||
|
||||
class TestReportTest(ReportTest):
|
||||
|
||||
"""Unit testing class for the stock item TestReport model"""
|
||||
model = report_models.TestReport
|
||||
|
||||
list_url = 'api-stockitem-testreport-list'
|
||||
@ -129,16 +121,13 @@ class TestReportTest(ReportTest):
|
||||
print_url = 'api-stockitem-testreport-print'
|
||||
|
||||
def setUp(self):
|
||||
|
||||
"""Setup function for the stock item TestReport"""
|
||||
self.copyReportTemplate('inventree_test_report.html', 'stock item test report')
|
||||
|
||||
return super().setUp()
|
||||
|
||||
def test_print(self):
|
||||
"""
|
||||
Printing tests for the TestReport
|
||||
"""
|
||||
|
||||
"""Printing tests for the TestReport."""
|
||||
report = self.model.objects.first()
|
||||
|
||||
url = reverse(self.print_url, kwargs={'pk': report.pk})
|
||||
@ -163,7 +152,7 @@ class TestReportTest(ReportTest):
|
||||
|
||||
|
||||
class BuildReportTest(ReportTest):
|
||||
|
||||
"""Unit test class for the BuildReport model"""
|
||||
model = report_models.BuildReport
|
||||
|
||||
list_url = 'api-build-report-list'
|
||||
@ -171,16 +160,13 @@ class BuildReportTest(ReportTest):
|
||||
print_url = 'api-build-report-print'
|
||||
|
||||
def setUp(self):
|
||||
|
||||
"""Setup unit testing functions"""
|
||||
self.copyReportTemplate('inventree_build_order.html', 'build order template')
|
||||
|
||||
return super().setUp()
|
||||
|
||||
def test_print(self):
|
||||
"""
|
||||
Printing tests for the BuildReport
|
||||
"""
|
||||
|
||||
"""Printing tests for the BuildReport."""
|
||||
report = self.model.objects.first()
|
||||
|
||||
url = reverse(self.print_url, kwargs={'pk': report.pk})
|
||||
@ -216,7 +202,7 @@ class BuildReportTest(ReportTest):
|
||||
|
||||
|
||||
class BOMReportTest(ReportTest):
|
||||
|
||||
"""Unit test class fot the BillOfMaterialsReport model"""
|
||||
model = report_models.BillOfMaterialsReport
|
||||
|
||||
list_url = 'api-bom-report-list'
|
||||
@ -225,7 +211,7 @@ class BOMReportTest(ReportTest):
|
||||
|
||||
|
||||
class PurchaseOrderReportTest(ReportTest):
|
||||
|
||||
"""Unit test class fort he PurchaseOrderReport model"""
|
||||
model = report_models.PurchaseOrderReport
|
||||
|
||||
list_url = 'api-po-report-list'
|
||||
@ -234,7 +220,7 @@ class PurchaseOrderReportTest(ReportTest):
|
||||
|
||||
|
||||
class SalesOrderReportTest(ReportTest):
|
||||
|
||||
"""Unit test class for the SalesOrderReport model"""
|
||||
model = report_models.SalesOrderReport
|
||||
|
||||
list_url = 'api-so-report-list'
|
||||
|
Reference in New Issue
Block a user