2
0
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 merge 99676ee

* 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 commit ca96654622.

* use typing here

* Revert "Make API code cleaner"

This reverts commit 24fb68bd3e.

* 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:
Matthias Mair
2022-06-01 17:37:39 +02:00
committed by GitHub
parent 66a6915213
commit 0c97a50e47
223 changed files with 4416 additions and 6980 deletions

View File

@ -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')

View File

@ -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)

View File

@ -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

View File

@ -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(

View File

@ -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',

View File

@ -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)

View File

@ -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)

View File

@ -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'