Skip to content

Commit

Permalink
Merge branch 'master' into pui-adjust-breadcrumbs
Browse files Browse the repository at this point in the history
  • Loading branch information
matmair authored Sep 16, 2024
2 parents 1b9ddf7 + 62b9aaa commit 66d917c
Show file tree
Hide file tree
Showing 170 changed files with 64,894 additions and 55,316 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/qc_checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,13 @@ jobs:
token: ${{ secrets.CODECOV_TOKEN }}
slug: inventree/InvenTree
flags: pui
- name: Upload bundler info
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
run: |
cd src/frontend
yarn install
yarn run build
platform_ui_build:
name: Build - UI Platform
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scorecard.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,6 @@ jobs:

# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
uses: github/codeql-action/upload-sarif@8214744c546c1e5c8f03dde8fab3a7353211988d # v3.26.7
with:
sarif_file: results.sarif
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{
"label": "worker",
"type": "shell",
"command": "invoke int.worker",
"command": "invoke worker",
"problemMatcher": [],
},
{
Expand Down
8 changes: 8 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@ flag_management:
statuses:
- type: project
target: 45%

comment:
require_bundle_changes: True
bundle_change_threshold: "1Kb"

bundle_analysis:
warning_threshold: "5%"
status: "informational"
2 changes: 1 addition & 1 deletion contrib/container/dev-docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ services:
inventree-dev-worker:
image: inventree-dev-image
build: *build_config
command: invoke int.worker
command: invoke worker
depends_on:
- inventree-dev-server
volumes:
Expand Down
2 changes: 1 addition & 1 deletion contrib/container/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ services:
# If you wish to specify a particular InvenTree version, do so here
image: inventree/inventree:${INVENTREE_TAG:-stable}
container_name: inventree-worker
command: invoke int.worker
command: invoke worker
depends_on:
- inventree-server
env_file:
Expand Down
1 change: 1 addition & 0 deletions docs/docs/demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The demo instance has a number of user accounts which you can use to explore the

| Username | Password | Staff Access | Enabled | Description |
| -------- | -------- | ------------ | ------- | ----------- |
| noaccess | youshallnotpass | No | Yes | Can login, but has no permissions |
| allaccess | nolimits | No | Yes | View / create / edit all pages and items |
| reader | readonly | No | Yes | Can view all pages but cannot create, edit or delete database records |
| engineer | partsonly | No | Yes | Can manage parts, view stock, but no access to purchase orders or sales orders |
Expand Down
6 changes: 6 additions & 0 deletions docs/docs/extend/plugins/panel.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ 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:
Expand Down
102 changes: 102 additions & 0 deletions docs/docs/extend/plugins/ui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
---
title: User Interface Mixin
---

## User Interface Mixin

The *User Interface* mixin class provides a set of methods to implement custom functionality for the InvenTree web interface.

### Enable User Interface Mixin

To enable user interface plugins, the global setting `ENABLE_PLUGINS_INTERFACE` must be enabled, in the [plugin settings](../../settings/global.md#plugin-settings).

## Plugin Context

When rendering certain content in the user interface, the rendering functions are passed a `context` object which contains information about the current page being rendered. The type of the `context` object is defined in the `PluginContext` file:

{{ includefile("src/frontend/src/components/plugins/PluginContext.tsx", title="Plugin Context", fmt="javascript") }}

## Custom Panels

Many of the pages in the InvenTree web interface are built using a series of "panels" which are displayed on the page. Custom panels can be added to these pages, by implementing the `get_custom_panels` method:

::: plugin.base.integration.UserInterfaceMixin.UserInterfaceMixin.get_custom_panels
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_sources: True
summary: False
members: []

The custom panels can display content which is generated either on the server side, or on the client side (see below).

### Server Side Rendering

The panel content can be generated on the server side, by returning a 'content' attribute in the response. This 'content' attribute is expected to be raw HTML, and is rendered directly into the page. This is particularly useful for displaying static content.

Server-side rendering is simple to implement, and can make use of the powerful Django templating system.

Refer to the [sample plugin](#sample-plugin) for an example of how to implement server side rendering for custom panels.

**Advantages:**

- Simple to implement
- Can use Django templates to render content
- Has access to the full InvenTree database, and content not available on the client side (via the API)

**Disadvantages:**

- Content is rendered on the server side, and cannot be updated without a page refresh
- Content is not interactive

### Client Side Rendering

The panel content can also be generated on the client side, by returning a 'source' attribute in the response. This 'source' attribute is expected to be a URL which points to a JavaScript file which will be loaded by the client.

Refer to the [sample plugin](#sample-plugin) for an example of how to implement client side rendering for custom panels.

#### Panel Render Function

The JavaScript file must implement a `renderPanel` function, which is called by the client when the panel is rendered. This function is passed two parameters:

- `target`: The HTML element which the panel content should be rendered into
- `context`: A dictionary of context data which can be used to render the panel content


**Example**

```javascript
export function renderPanel(target, context) {
target.innerHTML = "<h1>Hello, world!</h1>";
}
```

#### Panel Visibility Function

The JavaScript file can also implement a `isPanelHidden` function, which is called by the client to determine if the panel is displayed. This function is passed a single parameter, *context* - which is the same as the context data passed to the `renderPanel` function.

The `isPanelHidden` function should return a boolean value, which determines if the panel is displayed or not, based on the context data.

If the `isPanelHidden` function is not implemented, the panel will be displayed by default.

**Example**

```javascript
export function isPanelHidden(context) {
// Only visible for active parts
return context.model == 'part' && context.instance?.active;
}
```
## Sample Plugin
A sample plugin which implements custom user interface functionality is provided in the InvenTree source code:
::: plugin.samples.integration.user_interface_sample.SampleUserInterfacePlugin
options:
show_bases: False
show_root_heading: False
show_root_toc_entry: False
show_source: True
members: []
2 changes: 1 addition & 1 deletion docs/docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ The background worker process must be started separately to the web-server appli
From the top-level source directory, run the following command from a separate terminal, while the server is already running:

```
invoke int.worker
invoke worker
```

!!! info "Supervisor"
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/settings/global.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ Refer to the [return order settings](../order/return_order.md#return-order-setti

### Plugin Settings


| Name | Description | Default | Units |
| ---- | ----------- | ------- | ----- |
{{ globalsetting("PLUGIN_ON_STARTUP") }}
Expand All @@ -218,3 +217,4 @@ Refer to the [return order settings](../order/return_order.md#return-order-setti
{{ globalsetting("ENABLE_PLUGINS_APP") }}
{{ globalsetting("ENABLE_PLUGINS_SCHEDULE") }}
{{ globalsetting("ENABLE_PLUGINS_EVENTS") }}
{{ globalsetting("ENABLE_PLUGINS_INTERFACE") }}
2 changes: 1 addition & 1 deletion docs/docs/start/bare_dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ source ./env/bin/activate
### Start Background Worker

```
(env) invoke int.worker
(env) invoke worker
```

This will start the background process manager in the current shell.
2 changes: 1 addition & 1 deletion docs/docs/start/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ The InvenTree server tries to locate the `config.yaml` configuration file on sta

The configuration file *template* can be found on [GitHub]({{ sourcefile("src/backend/InvenTree/config_template.yaml") }}), and is shown below:

{{ includefile("src/backend/InvenTree/config_template.yaml", "Configuration File Template", format="yaml") }}
{{ includefile("src/backend/InvenTree/config_template.yaml", "Configuration File Template", fmt="yaml") }}

!!! info "Template File"
The default configuration file (as defined by the template linked above) will be copied to the specified configuration file location on first run, if a configuration file is not found in that location.
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/start/docker_install.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ index 8adee63..dc3993c 100644
- image: inventree/inventree:${INVENTREE_TAG:-stable}
+ image: inventree/inventree:${INVENTREE_TAG:-stable}-custom
+ pull_policy: never
command: invoke int.worker
command: invoke worker
depends_on:
- inventree-server
```
Expand Down
1 change: 1 addition & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ nav:
- Schedule Mixin: extend/plugins/schedule.md
- Settings Mixin: extend/plugins/settings.md
- URL Mixin: extend/plugins/urls.md
- User Interface Mixin: extend/plugins/ui.md
- Validation Mixin: extend/plugins/validation.md
- Machines:
- Overview: extend/machines/overview.md
Expand Down
11 changes: 10 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
"""InvenTree API version information."""

# InvenTree API version
INVENTREE_API_VERSION = 251
INVENTREE_API_VERSION = 254

"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""


INVENTREE_API_TEXT = """
v254 - 2024-09-14 : https://github.com/inventree/InvenTree/pull/7470
- Implements new API endpoints for enabling custom UI functionality via plugins
v253 - 2024-09-14 : https://github.com/inventree/InvenTree/pull/7944
- Adjustments for user API endpoints
v252 - 2024-09-13 : https://github.com/inventree/InvenTree/pull/8040
- Add endpoint for listing all known units
v251 - 2024-09-06 : https://github.com/inventree/InvenTree/pull/8018
- Adds "attach_to_model" field to the ReporTemplate model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Command(BaseCommand):

def handle(self, *args, **kwargs):
"""Run the management command."""
from plugin.staticfiles import collect_plugins_static_files
import plugin.staticfiles

collect_plugins_static_files()
plugin.staticfiles.collect_plugins_static_files()
plugin.staticfiles.clear_plugins_static_files()
45 changes: 40 additions & 5 deletions src/backend/InvenTree/InvenTree/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

import logging

from rest_framework import serializers
from django.core.exceptions import PermissionDenied
from django.http import Http404

from rest_framework import exceptions, serializers
from rest_framework.fields import empty
from rest_framework.metadata import SimpleMetadata
from rest_framework.request import clone_request
from rest_framework.utils import model_meta

import common.models
Expand All @@ -29,6 +33,40 @@ class InvenTreeMetadata(SimpleMetadata):
so we can perform lookup for ForeignKey related fields.
"""

def determine_actions(self, request, view):
"""Determine the 'actions' available to the user for the given view.
Note that this differs from the standard DRF implementation,
in that we also allow annotation for the 'GET' method.
This allows the client to determine what fields are available,
even if they are only for a read (GET) operation.
See SimpleMetadata.determine_actions for more information.
"""
actions = {}

for method in {'PUT', 'POST', 'GET'} & set(view.allowed_methods):
view.request = clone_request(request, method)
try:
# Test global permissions
if hasattr(view, 'check_permissions'):
view.check_permissions(view.request)
# Test object permissions
if method == 'PUT' and hasattr(view, 'get_object'):
view.get_object()
except (exceptions.APIException, PermissionDenied, Http404):
pass
else:
# If user has appropriate permissions for the view, include
# appropriate metadata about the fields that should be supplied.
serializer = view.get_serializer()
actions[method] = self.get_serializer_info(serializer)
finally:
view.request = request

return actions

def determine_metadata(self, request, view):
"""Overwrite the metadata to adapt to the request user."""
self.request = request
Expand Down Expand Up @@ -81,6 +119,7 @@ def determine_metadata(self, request, view):

# Map the request method to a permission type
rolemap = {
'GET': 'view',
'POST': 'add',
'PUT': 'change',
'PATCH': 'change',
Expand All @@ -102,10 +141,6 @@ def determine_metadata(self, request, view):
if 'DELETE' in view.allowed_methods and check(user, table, 'delete'):
actions['DELETE'] = {}

# Add a 'VIEW' action if we are allowed to view
if 'GET' in view.allowed_methods and check(user, table, 'view'):
actions['GET'] = {}

metadata['actions'] = actions

except AttributeError:
Expand Down
2 changes: 1 addition & 1 deletion src/backend/InvenTree/InvenTree/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ def validate_reference_field(cls, value):
)

# Check that the reference field can be rebuild
cls.rebuild_reference_field(value, validate=True)
return cls.rebuild_reference_field(value, validate=True)

@classmethod
def rebuild_reference_field(cls, reference, validate=False):
Expand Down
14 changes: 14 additions & 0 deletions src/backend/InvenTree/InvenTree/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ def has_permission(self, request, view):
# Extract the model name associated with this request
model = get_model_for_view(view)

if model is None:
return True

app_label = model._meta.app_label
model_name = model._meta.model_name

Expand All @@ -99,6 +102,17 @@ def has_permission(self, request, view):
return bool(request.user and request.user.is_superuser)


class IsSuperuserOrReadOnly(permissions.IsAdminUser):
"""Allow read-only access to any user, but write access is restricted to superuser users."""

def has_permission(self, request, view):
"""Check if the user is a superuser."""
return bool(
(request.user and request.user.is_superuser)
or request.method in permissions.SAFE_METHODS
)


class IsStaffOrReadOnly(permissions.IsAdminUser):
"""Allows read-only access to any user, but write access is restricted to staff users."""

Expand Down
Loading

0 comments on commit 66d917c

Please sign in to comment.