2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-17 20:45:44 +00:00

Documentation integration (#4653)

* Add documentation under docs/ directory

* Add CI workflow for mkdocs configuration checking

* Add documentation issue template

* update pip-tools?

* Update .gitignore files

* Fix .gitignore rules

* Improve release notes page

* remove references to old repo
This commit is contained in:
Oliver
2023-04-22 22:40:29 +10:00
committed by GitHub
parent 20f01e8741
commit 2ffd2354eb
487 changed files with 44875 additions and 12 deletions

View File

@ -0,0 +1,121 @@
---
title: How to plugin
---
## How to write a plugin
A short introductory guide for plugin beginners.
### Should it be a plugin?
First of all figure out what your plugin / code should do.
If you want to change how InvenTree base mechanics and business logic work, a plugin will not be sufficient. Maybe fork the project or better [start a discussion](https://github.com/inventree/InvenTree/discussions) on GitHub. There might be an easier / established way to do what you want.
If you want to remove parts of the user interface -> remove the permissions for those objects / actions and the users will not see them.
If you add a lot of code (over ~1000 LOC) maybe split it into multiple plugins to make upgrading and testing simpler.
### It will be a plugin!
Great. Now please read the [plugin documentation](./plugins.md) to get an overview of the architecture. It is rather short as a the (builtin) mixins come with extensive docstrings.
### Pick your building blocks
Consider the usecase for your plugin and define the exact function of the plugin, maybe wrtie it down in a short readme. Then pick the mixins you need (they help reduce custom code and keep the system reliable if internal calls change).
- Is it just a simple REST-endpoint that runs a function ([ActionMixin](./plugins/action.md)) or a parser for a custom barcode format ([BarcodeMixin](./plugins/barcode.md))?
- How does the user interact with the plugin? Is it a UI separate from the main InvenTree UI ([UrlsMixin](./plugins/urls.md)), does it need multiple pages with navigation-links ([NavigationMixin](./plugins/navigation.md)).
- Do you need to extend reporting functionality? Check out the [ReportMixin](./plugins/report.md).
- Will it make calls to external APIs ([APICallMixin](./plugins/api.md) helps there)?
- Do you need to run in the background ([ScheduleMixin](./plugins/schedule.md)) or when things in InvenTree change ([EventMixin](./plugins/event.md))?
- Does the plugin need configuration that should be user changeable ([SettingsMixin](./plugins/settings.md)) or static (just use a yaml in the config dir)?
- You want to receive webhooks? Do not code your own untested function, use the WebhookEndpoint model as a base and override the perform_action method.
- Do you need the full power of Django with custom models and all the complexity that comes with that welcome to the danger zone and [AppMixin](./plugins/app.md). The plugin will be treated as a app by django and can maybe rack the whole instance.
### Define the metadata
Do not forget to [declare the metadata](./plugins.md#plugin-options) for your plugin, those will be used in the settings. At least provide a weblink so users can file issues / reach you.
### Development guidelines
If you want to make your life easier, try to follow these guidelines; break where it makes sense for your use case.
- keep it simple - more that 1000 LOC are normally to much for a plugin
- use mixins where possible - we try to keep coverage high for them so they are not likely to break
- do not use internal functions - if a functions name starts with `_` it is internal and might change at any time
- keep you imports clean - the APIs for plugins and mixins are young and evolving (see [here](plugins.md#imports)). Use
```
from plugin import InvenTreePlugin, registry
from plugin.mixins import APICallMixin, SettingsMixin, ScheduleMixin, BarcodeMixin
```
- deliver as a package (see [below](#packaging))
- if you need to use a private infrastructure, use the 'Releases' functions in GitHub or Gitlab. Point to the 'latest' release endpoint when installing to make sure the update function works
- tag your GitHub repo with `inventree` and `inventreeplugins` to make discovery easier. A discovery mechanism using these tags is on the roadmap.
- use GitHub actions to test your plugin regularly (you can [schedule actions](https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#schedule)) against the 'latest' [docker-build](https://hub.docker.com/r/inventree/inventree) of InvenTree
- if you use the AppMixin pin your plugin against the stable branch of InvenTree, your migrations might get messed up otherwise
### Packaging
!!! tip "Package-Discovery can be tricky"
Most problems with packaging stem from problems with dicovery. [This guide](https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#automatic-discovery) by the PyPA contains a lot of information about discovery during packaging. Theses mechanisms generally apply to most discovery processes in InvenTree and the wider Django ecosystem.
The recommended way of distribution is as a [PEP 561](https://peps.python.org/pep-0561/) compliant package. If you can use the official Package Index (PyPi - [official website](https://pypi.org/)) as a registry.
Please follow PyPAs official [packaging guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/) to ensure your package installs correctly suing InvenTrees install mechanisms.
Your package must expose you plugin class as an [entrypoint](https://setuptools.pypa.io/en/latest/userguide/entry_point.html) with the name `inventree_plugins` to work with InvenTree.
```setup.cfg
# Example setup.cfg
[options.entry_points]
inventree_plugins =
ShopifyIntegrationPlugin = path.to.source:ShopifyIntegrationPluginClass
```
```setup.py
# Example setup.py
import setuptools
# ...
setuptools.setup(
name='ShopifyIntegrationPlugin'
.# ..
entry_points={"inventree_plugins": ["ShopifyIntegrationPlugin = path.to.source:ShopifyIntegrationPluginClass"]}
```
### A simple example
This example adds a new action under `/api/action/sample` using the ActionMixin.
``` py
# -*- coding: utf-8 -*-
"""sample implementation for ActionPlugin"""
from plugin import InvenTreePlugin
from plugin.mixins import ActionMixin
class SampleActionPlugin(ActionMixin, InvenTreePlugin):
"""
Use docstrings for everything... pls
"""
NAME = "SampleActionPlugin"
ACTION_NAME = "sample"
# metadata
AUTHOR = "Sample Author"
DESCRIPTION = "A very basic plugin with one mixin"
PUBLISH_DATE = "22.02.2222"
VERSION = "1.2.3" # We recommend semver and increase the major version with each new major release of InvenTree
WEBSITE = "https://example.com/"
LICENSE = "MIT" # use what you want - OSI approved is ♥
# Everything form here is for the ActionMixin
def perform_action(self):
print("Action plugin in action!")
def get_info(self):
return {
"user": self.user.username,
"hello": "world",
}
def get_result(self):
return True # This is returned to the client
```

View File

@ -0,0 +1,43 @@
---
title: Third Party Integrations
---
## Third Party Integrations
A list of known third-party InvenTree extensions is provided below. If you have an extension that should be listed here, contact the InvenTree team on [GitHub](https://github.com/inventree/).
### Ki-nTree
[Ki-nTree](https://github.com/sparkmicro/Ki-nTree/) is a fantastic tool for automated creation of [KiCad](https://kicad-pcb.org/) library parts, with direct integration with InvenTree.
### PK2InvenTree
[PK2InvenTree](https://github.com/rgilham/PK2InvenTree) is an open-source tool for migrating an existing [PartKeepr](https://github.com/partkeepr/PartKeepr) database to InvenTree.
### Digikey-Inventree-Integration
[Digikey-Inventree-Integration](https://github.com/EUdds/Digikey-Inventree-Integration) is a simple project that takes a digikey part number to creates a part in InvenTree.
### F360-InvenTree
[F360-InvenTree](https://github.com/matmair/F360-InvenTree/) is a tool for creating links between Autodesk Fusion 360 components and InvenTree parts.
Still under heavy development.
### DigitalOcean droplet
[InvenTree droplet](https://marketplace.digitalocean.com/apps/inventree?refcode=d6172576d014) is a 1-click solution to deploy InvenTree in the cloud with DigitalOcean. You still have to administer and update your instance.
The source code for this droplet can be found in [inventree_droplet](https://github.com/invenhost/inventree_droplet).
### InvenTree zebra plugin
[InvenTree zebra plugin](https://github.com/SergeoLacruz/inventree-zebra-plugin) is a plugin to print labels with zebra printers.
Currently only the GK420T printer is supported.
### InvenTree Apprise
[InvenTree Apprise](https://github.com/matmair/inventree-apprise) is a plugin to send notifications via Apprise. This enables a wide variety of targets.
## First party plugins
### InvenTree brother plugin
[InvenTree brother plugin](https://github.com/inventree/inventree-brother-plugin) is a plugin to print labels with brother Q series printers.

105
docs/docs/extend/plugins.md Normal file
View File

@ -0,0 +1,105 @@
---
title: Plugins
---
## InvenTree Plugin Architecture
The InvenTree server code supports an extensible plugin architecture, allowing custom plugins to be integrated directly into the database server. This allows development of complex behaviours which are decoupled from core InvenTree code.
Plugins can be added from multiple sources:
- Plugins can be installed in InvenTrees venv via PIP (python package manager)
- Custom plugins should be placed in the directory `./InvenTree/plugins`.
- InvenTree built-in plugins are located in the directory `./InvenTree/plugin/builtin`.
For further information, read more about [installing plugins](./plugins/install.md).
### Plugin Base Class
Custom plugins must inherit from the [InvenTreePlugin class](https://github.com/inventree/InvenTree/blob/2d1776a151721d65d0ae007049d358085b2fcfd5/InvenTree/plugin/plugin.py#L204). Any plugins installed via the methods outlined above will be "discovered" when the InvenTree server launches.
!!! warning "Namechange"
The name of the base class was changed with `0.7.0` from `IntegrationPluginBase` to `InvenTreePlugin`. While the old name is still available till `0.8.0` we strongly suggest upgrading your plugins. Deprecation warnings are raised if the old name is used.
### Imports
As the code base is evolving import paths might change. Therefore we provide stable import targets for important python APIs.
Please read all release notes and watch out for warnings - we generally provide backports for depreciated interfaces for at least one minor release.
#### Plugins
General classes and mechanisms are provided under the `plugin` [namespaces](https://github.com/inventree/InvenTree/blob/master/InvenTree/plugin/__init__.py). These include:
```python
# Management objects
registry # Object that manages all plugin states and integrations
# Base classes
InvenTreePlugin # Base class for all plugins
# Errors
MixinImplementationError # Is raised if a mixin is implemented wrong (default not overwritten for example)
MixinNotImplementedError # Is raised if a mixin was not implemented (core mechanisms are missing from the plugin)
```
#### Mixins
Mixins are split up internally to keep the source tree clean and enable better testing separation. All public APIs that should be used are exposed under `plugin.mixins`. These include all built-in mixins and notification methods. An up-to-date reference can be found in the source code (current master can be [found here](https://github.com/inventree/InvenTree/blob/master/InvenTree/plugin/mixins/__init__.py)).
#### Models and other internal InvenTree APIs
!!! warning "Danger Zone"
The APIs outside of the `plugin` namespace are not structured for public usage and require a more in-depth knowledge of the Django framework. Please ask in GitHub discussions of the `ÌnvenTree` org if you are not sure you are using something the intended way.
We do not provide stable interfaces to models or any other internal python APIs. If you need to integrate into these parts please make yourself familiar with the codebase. We follow general Django patterns and only stray from them in limited, special cases.
If you need to react to state changes please use the [EventMixin](./plugins/event.md).
### Plugin Options
Some metadata options can be defined as constants in the plugins class.
``` python
NAME = '' # Used as a general reference to the plugin
SLUG = None # Used in URLs, setting-names etc. when a unique slug as a reference is needed -> the plugin name is used if not set
TITLE = None # A nice human friendly name for the plugin -> used in titles, as plugin name etc.
AUTHOR = None # Author of the plugin, git commit information is used if not present
PUBLISH_DATE = None # Publishing date of the plugin, git commit information is used if not present
WEBSITE = None # Website for the plugin, developer etc. -> is shown in plugin overview if set
VERSION = None # Version of the plugin
MIN_VERSION = None # Lowest InvenTree version number that is supported by the plugin
MAX_VERSION = None # Highest InvenTree version number that is supported by the plugin
```
Refer to the [sample plugins](https://github.com/inventree/InvenTree/tree/master/InvenTree/plugin/samples) for further examples.
### Plugin Config
A *PluginConfig* database entry will be created for each plugin "discovered" when the server launches. This configuration entry is used to determine if a particular plugin is enabled.
The configuration entries must be enabled via the [InvenTree admin interface](../settings/admin.md).
!!! warning "Disabled by Default"
Newly discovered plugins are disabled by default, and must be manually enabled (in the admin interface) by a user with staff privileges.
### Plugin Mixins
Common use cases are covered by pre-supplied modules in the form of *mixins* (similar to how [Django](https://docs.djangoproject.com/en/stable/topics/class-based-views/mixins/) does it). Each mixin enables the integration into a specific area of InvenTree. Sometimes it also enhances the plugin with helper functions to supply often used functions out-of-the-box.
Supported mixin classes are:
- [ActionMixin](./plugins/action.md)
- [APICallMixin](./plugins/api.md)
- [AppMixin](./plugins/app.md)
- [BarcodeMixin](./plugins/barcode.md)
- [EventMixin](./plugins/event.md)
- [LabelPrintingMixin](./plugins/label.md)
- [LocateMixin](./plugins/locate.md)
- [NavigationMixin](./plugins/navigation.md)
- [PanelMixin](./plugins/panel.md)
- [ReportMixin](./plugins/report.md)
- [ScheduleMixin](./plugins/schedule.md)
- [SettingsMixin](./plugins/settings.md)
- [UrlsMixin](./plugins/urls.md)
- [ValidationMixin](./plugins/validation.md)

View File

@ -0,0 +1,18 @@
---
title: Action Plugins
---
## ActionMixin
Arbitrary "actions" can be called by POSTing data to the `/api/action/` endpoint. The POST request must include the name of the action to be performed, and a matching ActionPlugin plugin must be loaded by the server. Arbitrary data can also be provided to the action plugin via the POST data:
```
POST {
action: "MyCustomAction",
data: {
foo: "bar",
}
}
```
For an example of a very simple action plugin, refer to `/InvenTree/plugin/builtin/action/simpleactionplugin.py`

View File

@ -0,0 +1,7 @@
---
title: Schedule Mixin
---
## APICallMixin
The APICallMixin class provides basic functionality for integration with an external API.

View File

@ -0,0 +1,10 @@
---
title: App Mixin
---
## AppMixin
If this mixin is added to a plugin the directory the plugin class is defined in is added to the list of `INSTALLED_APPS` in the InvenTree server configuration.
!!! warning "Danger Zone"
Only use this mixin if you have an understanding of djangos [app system](https://docs.djangoproject.com/en/stable/ref/applications). Plugins with this mixin are deeply integrated into InvenTree and can cause difficult to reproduce or long-running errors. Use the built-in testing functions of django to make sure your code does not cause unwanted behaviour in InvenTree before releasing.

View File

@ -0,0 +1,25 @@
---
title: Barcode Mixin
---
### Barcode Plugins
InvenTree supports decoding of arbitrary barcode data via a **Barcode Plugin** interface. Barcode data POSTed to the `/api/barcode/` endpoint will be supplied to all loaded barcode plugins, and the first plugin to successfully interpret the barcode data will return a response to the client.
InvenTree can generate native QR codes to represent database objects (e.g. a single StockItem). This barcode can then be used to perform quick lookup of a stock item or location in the database. A client application (for example the InvenTree mobile app) scans a barcode, and sends the barcode data to the InvenTree server. The server then uses the **InvenTreeBarcodePlugin** (found at `/InvenTree/plugins/barcode/inventree.py`) to decode the supplied barcode data.
Any third-party barcodes can be decoded by writing a matching plugin to decode the barcode data. These plugins could then perform a server-side action or render a JSON response back to the client for further action.
Some examples of possible uses for barcode integration:
- Stock lookup by scanning a barcode on a box of items
- Receiving goods against a PurchaseOrder by scanning a supplier barcode
- Perform a stock adjustment action (e.g. take 10 parts from stock whenever a barcode is scanned)
Barcode data are POSTed to the server as follows:
```
POST {
barcode_data: "[(>someBarcodeDataWhichThePluginKnowsHowToDealWith"
}
```

View File

@ -0,0 +1,36 @@
---
title: Event Mixin
---
## EventMixin
The `EventMixin` class enables plugins to respond to certain triggered events.
When a certain (server-side) event occurs, the background worker passes the event information to any plugins which inherit from the `EventMixin` base class.
Implementing classes must provide a `process_event` function:
```python
class EventPlugin(EventMixin, InvenTreePlugin):
"""
A simple example plugin which responds to events on the InvenTree server.
This example simply prints out the event information.
A more complex plugin could respond to specific events however it wanted.
"""
NAME = "EventPlugin"
SLUG = "event"
TITLE = "Triggered Events"
def process_event(self, event, *args, **kwargs):
print(f"Processing triggered event: '{event}'")
```
### Events
Events are passed through using a string identifier, e.g. 'build.completed'
The arguments (and keyword arguments) passed to the receiving function depend entirely on the type of event.
Implementing a response to a particular event requires a working knowledge of the InvenTree code base, especially related to that event being received.

View File

@ -0,0 +1,92 @@
---
title: Installing Plugins
---
## Installing a Plugin
Plugins can either be loaded from paths in the InvenTree install directory or as a plugin installed via pip. We recommend installation via pip as this enables hassle-free upgrades.
### Common Issues
Installing plugins can be complex! Some common issues are outlined below:
#### Enable Plugin Support
To enable custom plugins, plugin support must be activated in the [server configuration](../../start/config.md#plugin-options). This step must be performed by a system administrator before the InvenTree server is started.
#### Restart Server
Plugins are discovered and loaded only when the server is started. When new plugins are installed (and activated), both the web server and background worker must be restarted.
#### Container Environments
In certain container environments (such as docker), plugins are installed into an *ephemeral* virtual environment which persists only for the lifetime of the container. To allow for this, InvenTree provides a configurable setting which can automatically install plugins whenever the container is loaded.
!!! tip "Check Plugins on Startup"
Ensure the **Check Plugins on Startup** option is enabled, when running InvenTree in a container environment!
{% with id="check_plugins", url="plugin/check_on_startup.png", description="Check plugins on startup" %}
{% include 'img.html' %}
{% endwith %}
### Installation Methods
#### Builtin Plugins
Builtin plugins ship in `src/InvenTree/plugin/builtin`. To achive full unit-testing for all mixins there are some sample implementations in `src/InvenTree/plugin/samples`.
!!! success "Builtin Plugins"
Builtin plugins are always enabled, as they are required for core InvenTree functionality
!!! info "Debug Only"
The sample plugins are not loaded in production mode.
#### Plugin Installation File (PIP)
Plugins installation can be simplified by providing a list of plugins in a plugin configuration file. This file (by default, *plugins.txt* in the same directory as the server configuration file) contains a list of required plugin packages.
Plugins can be then installed from this file by simply running the command `invoke plugins`.
Installation via PIP (using the *plugins.txt* file) provides a number of advantages:
- Any required secondary packages are installed automatically
- You can update plugins simply by specifying version numbers in *plugins.txt*
- Migrating plugins between systems is simplified
- You can install plugins via any source supported by PIP
!!! success "Auto Update"
When the server installation is updated via the `invoke update` command, the plugins (as specified in *plugins.txt*) will also be updated automatically.
!!! info "Plugin File Location"
The location of your plugin configuration file will depend on your [server configuration](../../start/config.md)
#### Web Interface
Admin users can install plugins directly from the web interface, via the "Plugin Settings" view:
{% with id="plugin_install", url="plugin/plugin_install_web.png", description="Install via web interface" %}
{% include 'img.html' %}
{% endwith %}
!!! success "Plugin File"
A plugin installed via the web interface is added to the [plugins.txt](#plugin-installation-file-pip) plugin file.
#### Local Directory
Custom plugins can be placed in the `src/InvenTree/plugins/` directory, where they will be automatically discovered. This can be useful for developing and testing plugins, but can prove more difficult in production (e.g. when using Docker).
!!! info "Git Tracking"
The `src/InvenTree/plugins/` directory is excluded from Git version tracking - any plugin files here will be hidden from Git
!!! warning "Not Recommended For Production"
Loading plugins via the local *plugins* directory is not recommended for production. If you cannot use PIP installation (above), specify a custom plugin directory (below) or use a [VCS](https://pip.pypa.io/en/stable/topics/vcs-support/) as a plugin install source.
#### Custom Directory
If you wish to install plugins from local source, rather than PIP, it is better to place your plugins in a directory outside the InvenTree source directory.
To achieve this, set the `INVENTREE_PLUGIN_DIR` environment variable to the directory where locally sourced plugins are located. Refer to the [configuration options](../../start/config.md#plugin-options) for further information.
!!! info "Docker"
When running InvenTree in docker, a *plugins* directory is automatically created in the mounted data volume. Any plugins can be placed there, and will be automatically loaded when the server is started.

View File

@ -0,0 +1,66 @@
---
title: Label Mixin
---
## LabelPrintingMixin
The `LabelPrintingMixin` class enables plugins to print labels directly to a connected printer. Custom plugins can be written to support any printer backend.
An example of this is the [inventree-brother-plugin](https://github.com/inventree/inventree-brother-plugin) which provides native support for the Brother QL and PT series of networked label printers.
### Web Integration
If label printing plugins are enabled, they are able to be used directly from the InvenTree web interface:
{% with id="label_print", url="plugin/print_label_select_plugin.png", description="Print label via plugin" %}
{% include 'img.html' %}
{% endwith %}
### App Integration
Label printing plugins also allow direct printing of labels via the [mobile app](../../app/stock.md#print-label)
## Implementation
Plugins which implement the `LabelPrintingMixin` mixin class must provide a `print_label` function:
```python
from dummy_printer import printer_backend
class MyLabelPrinter(LabelPrintingMixin, InvenTreePlugin):
"""
A simple example plugin which provides support for a dummy printer.
A more complex plugin would communicate with an actual printer!
"""
NAME = "MyLabelPrinter"
SLUG = "mylabel"
TITLE = "A dummy printer"
def print_label(self, **kwargs):
"""
Send the label to the printer
kwargs:
pdf_data: An in-memory PDF file of the label
png_file: An in-memory PIL (pillow) Image file of the label
filename: The filename of the printed label (if applicable)
label_instance: The Label model instance
width: width of the label (in mm)
height: height of the label (in mm)
user: The user who printed this label
"""
width = kwargs['width']
height = kwargs['height']
# This dummy printer supports printing of raw image files
printer_backend.print(png_file, w=width, h=height)
```
### Available Data
The *label* data are supplied to the plugin in both `PDF` and `PNG` formats. This provides compatibility with a great range of label printers "out of the box". Conversion to other formats, if required, is left as an exercise for the plugin developer.
Other arguments provided to the `print_label` function are documented in the code sample above.

View File

@ -0,0 +1,31 @@
---
title: Locate Mixin
---
## LocateMixin
The `LocateMixin` class enables plugins to "locate" stock items (or stock locations) via an entirely custom method.
For example, a warehouse could be arranged with each individual 'parts bin' having an audio-visual indicator (e.g. RGB LED and buzzer). "Locating" a particular stock item causes the LED to flash and the buzzer to sound.
Another example might be a parts retrieval system, where "locating" a stock item causes the stock item to be "delivered" to the user via a conveyor.
The possibilities are endless!
### Web Integration
{% with id="web_locate", url="plugin/web_locate.png", description="Locate stock item from web interface", maxheight="400px" %}
{% include 'img.html' %}
{% endwith %}
### App Integration
If a locate plugin is installed and activated, the [InvenTree mobile app](../../app/app.md) displays a button for locating a StockItem or StockLocation (see below):
{% with id="app_locate", url="plugin/app_locate.png", description="Locate stock item from app", maxheight="400px" %}
{% include 'img.html' %}
{% endwith %}
### Implementation
Refer to the [InvenTree source code](https://github.com/inventree/InvenTree/blob/master/InvenTree/plugin/samples/locate/locate_sample.py) for a simple implementation example.

View File

@ -0,0 +1,23 @@
---
title: Navigation Mixin
---
## NavigationMixin
Use the class constant `NAVIGATION` for a array of links that should be added to InvenTrees navigation header.
The array must contain at least one dict that at least define a name and a link for each element. The link must be formatted for a URL pattern name lookup - links to external sites are not possible directly. The optional icon must be a class reference to an icon (InvenTree ships with fontawesome 4 by default).
``` python
class MyNavigationPlugin(NavigationMixin, InvenTreePlugin):
NAME = "NavigationPlugin"
NAVIGATION = [
{'name': 'SampleIntegration', 'link': 'plugin:sample:hi', 'icon': 'fas fa-box'},
]
NAVIGATION_TAB_NAME = "Sample Nav"
NAVIGATION_TAB_ICON = 'fas fa-plus'
```
The optional class constants `NAVIGATION_TAB_NAME` and `NAVIGATION_TAB_ICON` can be used to change the name and icon for the parent navigation node.

View File

@ -0,0 +1,161 @@
---
title: Panel Mixin
---
## PanelMixin
The `PanelMixin` enables plugins to render custom content to "panels" on individual pages in the web interface.
Most pages in the web interface support multiple panels, which are selected via the sidebar menu on the left side of the screen:
{% with id="panels", url="plugin/panels.png", description="Display panels" %}
{% include 'img.html' %}
{% endwith %}
Each plugin which implements this mixin can return zero or more custom panels for a particular page. The plugin can decide (at runtime) which panels it wishes to render. This determination can be made based on the page routing, the item being viewed, the particular user, or other considerations.
### Panel Content
Panel content can be rendered by returning HTML directly, or by rendering from a template file.
Each plugin can register templates simply by providing a 'templates' directory in its root path.
The convention is that each 'templates' directory contains a subdirectory with the same name as the plugin :
* e.g. templates/myplugin/my_template.html
In this case, the template can then be loaded (from any plugin!) by loading "myplugin/my_template.html".
### Javascript
Custom code can be provided which will run when the particular panel is first loaded (by selecting it from the side menu).
To add some javascript code, you can add a reference to a function that will be called when the panel is loaded with the 'javascript' key in the panel description :
```
{
'title': "Updates",
'description': "Latest updates for this part",
'javascript': 'alert("You just loaded this panel!")',
}
```
Or to add a template file that will be rendered as javascript code, from the plugin template folder, with the 'javascript_template' key in the panel description :
```
{
'title': "Updates",
'description': "Latest updates for this part",
'javascript_template': 'pluginTemplatePath/myJavascriptFile.js',
}
```
note : see convention for template directory above.
## Example Implementation
Refer to the `CustomPanelSample` example class in the `./plugin/samples/integration/` directory, for a fully worked example of how custom UI panels can be implemented.
### An example with button
Let's have a look at another example. We like to have a new panel that contains a button.
Each time the button is clicked, a python function in our plugin shall be executed and
do something useful. The result will look like that:
{% with id="mouser", url="plugin/mouser.png", description="Panel example with button" %} {% include "img.html" %} {% endwith %}
First we need to write the plugin code, similar as in the example above.
```python
from django.conf.urls import url
from django.http import HttpResponse
from order.views import PurchaseOrderDetail
from plugin import InvenTreePlugin
from plugin.mixins import PanelMixin, SettingsMixin, UrlsMixin
class MouserCartPanel(PanelMixin, SettingsMixin, InvenTreePlugin, UrlsMixin):
value=1
NAME = "MouserCart"
SLUG = "mousercart"
TITLE = "Create Mouser Cart"
DESCRIPTION = "An example plugin demonstrating a button calling a python function."
VERSION = "0.1"
def get_custom_panels(self, view, request):
panels = []
# This panel will *only* display on the PurchaseOrder view,
if isinstance(view, PurchaseOrderDetail):
panels.append({
'title': 'Mouser Actions',
'icon': 'fa-user',
'content_template': 'mouser/mouser.html',
})
return panels
def setup_urls(self):
return [
url(r'mouser/getcart', self.GetCart, name='get-cart'),
]
#----------------------------------------------------------------------------
def GetCart(self,request):
print('User:',request.user)
self.value=self.value+1
return HttpResponse(f'OK')
```
The code is simple and really stripped down to the minimum. In the plugin class we first define the plugin metadata.
Afterwards we define the custom panel. Here we use a html template to describe the content of the panel. We need to
add the path here because the template resides in the subdirectory templates/mouser.
Then we setup the url. This is important. The url connects the http request with the function to be executed.
* mouser/getcart: Path of the url together with SLUG
* self.GetCart: This is the function to be called. It is defined further down
* get-cart: This is the name of the url that needs to be referenced in the html template. We see that later.
Finally we define the function. This is a simple increment of a class value.
New lets have a look at the template file mouser.html
```html
{% raw %}
{% load i18n %}
<script>
async function JGetCart(){
response = await fetch( '{% url "plugin:mousercart:get-cart" %}');
location.reload();
}
</script>
<button type='button' class='btn btn-info' onclick="JGetCart()" title='{% trans "Get Mouser shopping Cart" %}'>
<span class='fas fa-redo-alt'></span> {% trans "Get Cart" %}
</button>
<br>
{{ order.description }}
{{ plugin.value }}
{% endraw %}
```
We start with a bit of javascript. The function JGetCart just calls the url that has been defined in the python code above.
The url consists of a full path `plugin:plugin-name:url-name`. The plugin-name is the SLUG that was defined in the plugin code.
Then just a reload.
The button is defined with `class="btn btn-info` This is an InvenTree predefined button. There a are lots of others available.
Here are some examples of available colors:
{% with id="buttons", url="plugin/buttons.png", description="Button examples" %} {% include "img.html" %} {% endwith %}
Please have a look at the css files for more options. The last line renders the value that was defined in the plugin.
!!! tip "Give it a try"
Each time you press the button, the value will be increased.

View File

@ -0,0 +1,52 @@
---
title: Report Mixin
---
## ReportMixin
The ReportMixin class provides a plugin with the ability to extend the functionality of custom [report templates](../../report/report.md). A plugin which implements the ReportMixin mixin class can add custom context data to a report template for rendering.
### Example
A sample plugin which provides additional context data to the report templates can be found [in the InvenTree source code](https://github.com/inventree/InvenTree/blob/master/InvenTree/plugin/samples/integration/report_plugin_sample.py):
```python
"""Sample plugin for extending reporting functionality"""
import random
from plugin import InvenTreePlugin
from plugin.mixins import ReportMixin
from report.models import PurchaseOrderReport
class SampleReportPlugin(ReportMixin, InvenTreePlugin):
"""Sample plugin which provides extra context data to a report"""
NAME = "Report Plugin"
SLUG = "reportexample"
TITLE = "Sample Report Plugin"
DESCRIPTION = "A sample plugin which provides extra context data to a report"
VERSION = "1.0"
def some_custom_function(self):
"""Some custom function which is not required for the plugin to function"""
return random.randint(0, 100)
def add_report_context(self, instance, request, context):
"""Add example content to the report instance"""
# We can add any extra context data we want to the report
# Generate a random string of data
context['random_text'] = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz', k=20))
# Call a custom method
context['random_int'] = self.some_custom_function()
# We can also add extra data to the context which is specific to the report type
context['is_purchase_order'] = isinstance(instance, PurchaseOrderReport)
# We can also use the 'request' object to add extra context data
context['request_method'] = request.method
```

View File

@ -0,0 +1,52 @@
---
title: Schedule Mixin
---
## ScheduleMixin
The ScheduleMixin class provides a plugin with the ability to call functions at regular intervals.
- Functions are registered with the InvenTree worker which runs as a background process.
- Scheduled functions do not accept any arguments
- Plugin member functions can be called
- Global functions can be specified using dotted notation
### Example
An example of a plugin which supports scheduled tasks:
```python
class ScheduledTaskPlugin(ScheduleMixin, SettingsMixin, InvenTreePlugin):
"""
Sample plugin which runs a scheduled task, and provides user configuration.
"""
NAME = "Scheduled Tasks"
SLUG = 'schedule'
SCHEDULED_TASKS = {
'global': {
'func': 'some_module.function',
'schedule': 'H', # Run every hour
},
'member': {
'func': 'foo',
'schedule': 'I', # Minutes
'minutes': 15,
},
}
SETTINGS = {
'SECRET': {
'name': 'A secret',
'description': 'User configurable value',
},
}
def foo(self):
"""
This function runs every 15 minutes
"""
secret_value = self.get_setting('SECRET')
print(f"foo - SECRET = {secret_value})
```

View File

@ -0,0 +1,64 @@
---
title: Settings Mixin
---
## SettingsMixin
The *SettingsMixin* allows the plugin to save and load persistent settings to the database.
- Plugin settings are stored against the individual plugin, and thus do not have to be unique
- Plugin settings are stored using a "key:value" pair
Use the class constant `SETTINGS` for a dict of settings that should be added as global database settings.
The dict must be formatted similar to the following sample that shows how to use validator choices and default. Take a look at the settings defined in `InvenTree.common.models.InvenTreeSetting` for all possible parameters.
``` python
class PluginWithSettings(SettingsMixin, InvenTreePlugin):
NAME = "PluginWithSettings"
SETTINGS = {
'API_ENABLE': {
'name': 'API Functionality',
'description': 'Enable remote API queries',
'validator': bool,
'default': True,
},
'API_KEY': {
'name': 'API Key',
'description': 'Security key for accessing remote API',
'default': '',
},
'API_URL': {
'name': _('API URL'),
'description': _('Base URL for remote server'),
'default': 'http://remote.url/api',
},
'CONNECTION': {
'name': _('Printer Interface'),
'description': _('Select local or network printer'),
'choices': [('local','Local printer e.g. USB'),('network','Network printer with IP address')],
'default': 'local',
},
'NUMBER': {
'name': _('A Name'),
'description': _('Descripe me here'),
'default': 6,
'validator': [
int,
MinValueValidator(2),
MaxValueValidator(25)
]
},
}
```
This mixin defines the helper functions `plugin.get_setting` and `plugin.set_setting` to access all plugin specific settings:
```python
api_url = self.get_setting('API_URL', cache = False)
self.set_setting('API_URL', 'some value')
```
`get_setting` has an additional parameter which lets control if the value is taken directly from the database or from the cache. If it is left away `False` ist the default that means the value is taken directly from the database.

View File

@ -0,0 +1,21 @@
---
title: URLs Mixin
---
## UrlsMixin
Use the class constant `URLS` for a array of URLs that should be added to InvenTrees URL paths or override the `plugin.setup_urls` function.
The array has to contain valid URL patterns as defined in the [django documentation](https://docs.djangoproject.com/en/stable/topics/http/urls/).
``` python
class MyUrlsPlugin(UrlsMixin, InvenTreePlugin):
NAME = "UrlsMixin"
URLS = [
url(r'increase/(?P<location>\d+)/(?P<pk>\d+)/', self.view_increase, name='increase-level'),
]
```
The URLs get exposed under `/plugin/{plugin.slug}/*` and get exposed to the template engine with the prefix `plugin:{plugin.slug}:` (for usage with the [url tag](https://docs.djangoproject.com/en/stable/ref/templates/builtins/#url)).

View File

@ -0,0 +1,98 @@
---
title: Validation Mixin
---
## ValidationMixin
The `ValidationMixin` class enables plugins to perform custom validation of various fields.
Any of the methods described below can be implemented in a custom plugin to provide functionality as required.
!!! info "More Info"
For more information on any of the methods described below, refer to the InvenTree source code.
!!! info "Multi Plugin Support"
It is possible to have multiple plugins loaded simultaneously which support validation methods. For example when validating a field, if one plugin returns a null value (`None`) then the *next* plugin (if available) will be queried.
### Part Name
By default, part names are not subject to any particular naming conventions or requirements. However if custom validation is required, the `validate_part_name` method can be implemente to ensure that a part name conforms to a required convention.
If the custom method determines that the part name is *objectionable*, it should throw a `ValidationError` which will be handled upstream by parent calling methods.
### Part IPN
Validation of the Part IPN (Internal Part Number) field is exposed to custom plugins via the `validate_part_IPN` method. Any plugins which extend the `ValidationMixin` class can implement this method, and raise a `ValidationError` if the IPN value does not match a required convention.
### Batch Codes
[Batch codes](../../stock/tracking.md#batch-codes) can be generated and/or validated by custom plugins.
The `validate_batch_code` method allows plugins to raise an error if a batch code input by the user does not meet a particular pattern.
The `generate_batch_code` method can be implemented to generate a new batch code.
### Serial Numbers
Requirements for serial numbers can vary greatly depending on the application. Rather than attempting to provide a "one size fits all" serial number implementation, InvenTree allows custom serial number schemes to be implemented via plugins.
The default InvenTree [serial numbering system](../../stock/tracking.md#serial-numbers) uses a simple algorithm to validate and increment serial numbers. More complex behaviours can be implemented using the `ValidationMixin` plugin class and the following custom methods:
#### Serial Number Validation
Custom serial number validation can be implemented using the `validate_serial_number` method. A *proposed* serial number is passed to this method, which then has the opportunity to raise a `ValidationError` to indicate that the serial number is not valid.
##### Example
A plugin which requires all serial numbers to be valid hexadecimal values may implement this method as follows:
```python
def validate_serial_number(self, serial: str, part: Part):
"""Validate the supplied serial number
Arguments:
serial: The proposed serial number (string)
part: The Part instance for which this serial number is being validated
"""
try:
# Attempt integer conversion
int(serial, 16)
except ValueError:
raise ValidationError("Serial number must be a valid hex value")
```
#### Serial Number Sorting
While InvenTree supports arbitrary text values in the serial number fields, behind the scenes it attempts to "coerce" these values into an integer representation for more efficient sorting.
A custom plugin can implement the `convert_serial_to_int` method to determine how a particular serial number is converted to an integer representation.
!!! info "Not Required"
If this method is not implemented, or the serial number cannot be converted to an integer, then the sorting algorithm falls back to the text (string) value
#### Serial Number Incrementing
A core component of the InvenTree serial number system is the ability to *increment* serial numbers - to determine the *next* serial number value in a sequence.
For custom serial number schemes, it is important to provide a method to generate the *next* serial number given a current value. The `increment_serial_number` method can be implemented by a plugin to achieve this.
!!! info "Invalid Increment"
If the provided number cannot be incremented (or an error occurs) the method should return `None`
##### Example
Continuing with the hexadecimal example as above, the method may be implemented as follows:
```python
def increment_serial_number(self, serial: str):
"""Provide the next hexadecimal number in sequence"""
try:
val = int(serial, 16) + 1
val = hex(val).upper()[2:]
except ValueError:
val = None
return val
```

View File

@ -0,0 +1,37 @@
---
title: Changing color theme
---
## Color Themes
You can customize the look of InvenTree via the color themes feature.
### Select Color Theme
Navigate to the "Settings" page and click on the "Display" tab, you should see the following:
{% with id="theme_default", url="settings/theme_default.png", description="Default InvenTree color theme" %}
{% include 'img.html' %}
{% endwith %}
The drop-down list let's you select any other color theme found in your static folder (see next section to find out how to [add color themes](#add-color-themes)). Once selected, click on the "Apply Theme" button for the new color theme to be activated.
!!! info "Per-user Setting"
Color themes are "user specific" which means that changing the color theme in your own settings won't affect other users.
Here is an example what the "Dark Reader" theme looks like:
{% with id="theme_dark", url="settings/theme_dark.png", description="Dark Reader InvenTree color theme" %}
{% include 'img.html' %}
{% endwith %}
### Add Color Theme
#### Local Installation
To add a color theme, you'll need to add a new CSS sheet in your static folder (the default folder is located at `{{ static_folder_local_default }}css/color-themes/`).
InvenTree automatically lists all CSS sheets found in the `{{ static_folder_local_default }}css/color-themes/` folder and present them inside the dropdown list on the "Settings > Theme" page.
#### InvenTree Source Code
If you would like a CSS sheet to be permanently added to InvenTree's source code so that other users can benefit too, add it to the `{{ static_folder_source }}css/color-themes/` folder then submit a pull request.