2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-14 19:15:41 +00:00

Remove CUI (#8384)

* remove CUI

* fix loading

* fix login middleware

* remove css template functions

* tmp fix for recurtion

* remove old test

* fix assertations

* fix middleware tests

* re-add plugin tags

* remove thirdpartjs

* re-add mfa urls

* remove old js

* remove cui tags

* simplify error testing

* use license endpoint for testing instead

* disable successful test

* revert de-activation

* remove references to old UI customisation

* update docs to remove reference to removed page

* disable availabilty check

* possible fix to importing problem

* Revert "possible fix to importing problem"

This reverts commit ee9fccdc8c.

* remove old get_context_data

* fix migration

* remove unused function

* remove unused stuff

* remove unused template

* fix formatting of readme
This commit is contained in:
Matthias Mair
2024-12-17 02:30:41 +01:00
committed by GitHub
parent bf8113a33e
commit 24f433c948
2327 changed files with 67 additions and 376706 deletions

View File

@ -100,7 +100,6 @@ Supported mixin classes are:
| [LabelPrintingMixin](./plugins/label.md) | Custom label printing support |
| [LocateMixin](./plugins/locate.md) | Locate and identify stock items |
| [NavigationMixin](./plugins/navigation.md) | Add custom pages to the web interface |
| [PanelMixin](./plugins/panel.md) | Add custom panels to web views |
| [ReportMixin](./plugins/report.md) | Add custom context data to reports |
| [ScheduleMixin](./plugins/schedule.md) | Schedule periodic tasks |
| [SettingsMixin](./plugins/settings.md) | Integrate user configurable settings |

View File

@ -1,390 +0,0 @@
---
title: Panel Mixin
---
## PanelMixin
!!! warning "Legacy User Interface"
This plugin mixin class is designed specifically for the the *legacy* user interface (which is rendered on the server using django templates). The new user interface (which is rendered on the client using React) does not support this mixin class. Instead, refer to the new [User Interface Mixin](./ui.md) class.
!!! warning "Deprecated Class"
This mixin class is considered deprecated, and will be removed in the 1.0.0 release.
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:
```python
{
'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:
```python
{
'title': "Updates",
'description': "Latest updates for this part",
'javascript_template': 'pluginTemplatePath/myJavascriptFile.js',
}
```
Note : see convention for template directory above.
## Sample Plugin
A sample plugin is provided in the InvenTree code base:
::: plugin.samples.integration.custom_panel_sample.CustomPanelSample
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_source: True
members: []
## Example Implementations
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 and parameter
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
a parameter shall be transferred . 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.urls import re_path
from django.http import HttpResponse
from order.views import PurchaseOrderDetail
from plugin import InvenTreePlugin
from plugin.mixins import PanelMixin, UrlsMixin
class MouserCartPanel(PanelMixin, 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 [
re_path(r'transfercart/(?P<pk>\d+)/', self.TransferCart, name='get-cart')
]
#----------------------------------------------------------------------------
def TransferCart(self,request,pk):
print('User,pk:',request.user,pk)
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.
May be it is worth to leave a few more words on this because the string looks a bit like white noise.
*transfercart* is the url which can be chosen freely. The ? is well known for parameters. In this case we
get just one parameter, the orders primary key.* \d+* is a regular expression that limits the parameters
to a digital number with n digits. Let's have a look on the names and how they belong together:
{% with id="plugin_dataflow", url="plugin/plugin_dataflow.png", description="Dataflow between Javascript and Python" %} {% include "img.html" %} {% endwith %}
Finally we define the function. This is a simple increment of a class value.
Now 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" order.pk %}');
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. order.pk is the parameter that is passed to python.
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.
### Handling user input
A common user case is user input that needs to be passed from the panel into
the plugin for further processing. Lets have a look at another example. We
will define two user input fields. One is an integer the other one a string.
A button will be defined to submit the data. Something like that:
{% with id="panel_with_userinput", url="plugin/panel_with_userinput.png", description="Panel with user input" %} {% include "img.html" %} {% endwith %}
Here is the plugin code:
```python
from django.urls import path
from django.http import HttpResponse
from plugin import InvenTreePlugin
from plugin.mixins import PanelMixin, UrlsMixin
class ExamplePanel(PanelMixin, InvenTreePlugin, UrlsMixin):
NAME = "ExamplePanel"
SLUG = "examplepanel"
TITLE = "Example for data input"
AUTHOR = "Michael"
DESCRIPTION = "This plugin passes user input from the panel to the plugin"
# Create the panel that will display on build detail view
def get_custom_panels(self, view, request):
panels = []
if isinstance(view, BuildDetail):
self.build=view.get_object()
panels.append({
'title': 'Example Info',
'icon': 'fa-industry',
'content_template': 'example_panel/example.html',
})
return panels
def setup_urls(self):
return [
path("example/<int:layer>/<path:size>/",
self.do_something, name = 'transfer'),
]
# Define the function that will be called.
def do_something(self, request, layer, size):
print('Example panel received:', layer, size)
return HttpResponse(f'OK')
```
The start is easy because it is the same as in the example above.
Lets concentrate on the setup_urls. This time we use
path (imported from django.urls) instead of url for definition. Using path makes it easier to
define the data types. No regular expressions. The URL takes two parameters,
layer and size, and passes them to the python function do_something for further processing.
Now the html template:
```html
{% raw %}
<script>
async function example_select(){
const layer_number = parseInt(document.getElementById("layer_number").value)
const size = document.getElementById("string").value
response = inventreeFormDataUpload(url="{% url 'plugin:examplepanel:transfer' '9999' 'Size' %}"
.replace("9999", layer_number)
.replace("Size", size)
);
}
</script>
<form>
Number of Layers<br>
<input id="layer_number" type="number" value="2"><br>
Size of Board in mm<br>
<input id="string" type="text" value="100x160">
</form>
<input type="submit" value="Save" onclick="example_select()" title='Save Data'>
{% endraw %}
```
The HTML defines the form for user input, one number and one string. Each
form has an ID that is used in the javascript code to get the input of the form.
The response URL must match the URL defined in the plugin. Here we have a number
(999999) and a string (Size). These get replaced with the content of the fields
upon execution using replace. Watch out for the ticks around the 999999 and Size. They prevent
them from being interpreted by the django template engine and replaced by
something else.
The function inventreeFormDataUpload is a helper function defined by InvenTree
that does the POST request, handles errors and the csrftoken.
!!! tip "Give it a try"
change the values in the fields and push Save. You will see the values
in the InvenTree log.
#### If things are getting more complicated
In the example above we code all parameters into the URL. This is easy and OK
if you transfer just a few values. But the method has at least two disadvantages:
* When you have more parameters, things will get messy.
* When you have free text input fields, the user might enter characters that are not allowed in URL.
For those cases it is better to pack the data into a json container and transfer
this in the body of the request message. The changes are simple. Lets start with
the javascript:
```html
{% raw %}
<script>
async function example_select(){
const layer_number = parseInt(document.getElementById("layer_number").value)
const size = document.getElementById("string").value
const cmd_url="{% url 'plugin:examplepanel:transfer' %}";
data = {
layer_number: layer_number,
size: size
}
response = inventreeFormDataUpload(url=cmd_url, data=JSON.stringify(data))
}
</script>
{% endraw %}
```
Here we create a json container (data). The function stringify converts this to the
proper string format for transfer. That's all. The function inventreeFormDataUpload
does the rest of the work.
The python code in the plugin also needs minor changes:
```python
from django.urls import re_path
import json
...
def setup_urls(self):
return [
re_path(r'example(?:\.(?P<format>json))?$', self.do_something, name='transfer'),
]
# Define the function that will be called.
def do_something(self, request):
data=json.loads(request.body)
print('Data received:', data)
```
The URL and the called function have no parameter names any longer. All data is in the
request message and can be extracted from this using json.loads. If more data is needed
just add it to the json container. No further changes are needed. It's really simple :-)
#### Populate a drop down field
Now we add a dropdown menu and fill it with values from the InvenTree database.
{% with id="panel_with_dropwdown", url="plugin/panel_with_dropdown.png", description="Panel with dropdown menu" %}
{% include "img.html" %}
{% endwith %}
```python
from company.models import Company
...
def get_custom_panels(self, view, request):
panels = []
if isinstance(view, BuildDetail):
self.build=view.get_object()
self.companies=Company.objects.filter(is_supplier=True)
panels.append({
...
```
Here we create self.companies and fill it with all companies that have the is_supplier flag
set to true. This is available in the context of the template. A drop down menu can be created
by looping.
```html
{% raw %}
<select id="ems">
{% for company in plugin.companies %}
<option value="{{ company.id }}"> {{ company.name }} </option>
{% endfor %}
</select>
{% endraw %}
```
The value of the select is the pk of the company. It can simply be added to the
json container and transferred to the plugin.
#### Store the Data
I case you plugin needs to store data permanently, InvenTree has a nice feature called
[metadata](metadata.md). You can easily store your values by adding a few lines
to the do_something function.
code:
```python
def do_something(self, request):
data=json.loads(request.body)
print('Data received:', data)
for key in data:
self.build.metadata[key]=data[key]
self.build.save()
```

View File

@ -43,40 +43,6 @@ def setup_urls(self):
]
```
### Implementing the Page Base
Some plugins require a page with a navbar, sidebar, and content similar to other InvenTree pages.
This can be done within a templated HTML file by extending the file "page_base.html". To do this, place the following line at the top of your template file.
``` HTML
{% raw %}
{% extends "page_base.html" %}
{% endraw %}
```
Additionally, add the following imports after the extended line.
``` HTML
{% raw %}
{% load static %}
{% load inventree_extras %}
{% load plugin_extras %}
{% load i18n %}
{% endraw %}
```
#### Blocks
The page_base file is split into multiple sections called blocks. This allows you to implement sections of the webpage while getting many items like navbars, sidebars, and general layout provided for you.
The current default page base can be found [here]({{ sourcefile("src/backend/InvenTree/templates/page_base.html") }}). Look through this file to determine overridable blocks. The [stock app]({{ sourcedir("src/backend/InvenTree/stock") }}) offers a great example of implementing these blocks.
!!! warning "Sidebar Block"
You may notice that implementing the `sidebar` block doesn't initially work. Be sure to enable the sidebar using JavaScript. This can be achieved by appending the following code, replacing `label` with a label of your choosing, to the end of your template file.
``` HTML
{% raw %}
{% block js_ready %}
{{ block.super }}
enableSidebar('label');
{% endblock js_ready %}
{% endraw %}
```
#### Panels
InvenTree uses bootstrap panels to display the page's content. These panels are locate inside the block `page_content`.

View File

@ -89,7 +89,7 @@ Multiple improvements have been made to the docker installation process, most no
### Panel Plugins
[#2937](https://github.com/inventree/InvenTree/pull/2937) adds a new type of plugin mixin, which allows rendering of custom "panels" on certain pages. This is a powerful new plugin feature which allows custom UI elements to be generated with ease. Read more about this new mixin [here](../extend/plugins/panel.md).
[#2937](https://github.com/inventree/InvenTree/pull/2937) adds a new type of plugin mixin, which allows rendering of custom "panels" on certain pages. This is a powerful new plugin feature which allows custom UI elements to be generated with ease. REMOVED AFTER 0.17.0
## Bug Fixes

View File

@ -211,7 +211,6 @@ nav:
- Label Printing Mixin: extend/plugins/label.md
- Locate Mixin: extend/plugins/locate.md
- Navigation Mixin: extend/plugins/navigation.md
- Panel Mixin: extend/plugins/panel.md
- Report Mixin: extend/plugins/report.md
- Schedule Mixin: extend/plugins/schedule.md
- Settings Mixin: extend/plugins/settings.md