inventree / InvenTree

Open Source Inventory Management System
https://docs.inventree.org
MIT License
4.34k stars 786 forks source link

[BUG] Internal server error when disabling one or both frontends #8506

Closed thcrt closed 4 days ago

thcrt commented 4 days ago

The default config.yaml file shipped features the boolean options platform_frontend and classic_frontend, to enable or disable the PUI or CUI respectively. However, setting either of these values to false results in breakage.

Disabling PUI, enabling CUI

Reproduction

To reproduce, set the following values in config.yaml:

classic_frontend: true
platform_frontend: false

Note that setting customize.hide_pui_banner has no effect on this error.

Then restart the InvenTree server and worker to apply changes. Since I'm running under Docker Compose, I use:

docker compose restart server worker

Results

When already logged in, most of the UI remains usable. However, any areas that would normally feature links to try PUI instead return a 500 Internal Server Error when accessed, notably including the settings page and the login page for visitors not yet authenticated:

Internal Server Error

Logs are presented below, in a <details> element for convenience:

Click to toggle logs ```shell server-1 | Internal Server Error: /accounts/login/ server-1 | Traceback (most recent call last): server-1 | File "/root/.local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 55, in inner server-1 | response = get_response(request) server-1 | ^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/core/handlers/base.py", line 220, in _get_response server-1 | response = response.render() server-1 | ^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/response.py", line 114, in render server-1 | self.content = self.rendered_content server-1 | ^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/response.py", line 92, in rendered_content server-1 | return template.render(context, self._request) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/backends/django.py", line 61, in render server-1 | return self.template.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 175, in render server-1 | return self._render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 167, in _render server-1 | return self.nodelist.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in render server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 966, in render_annotated server-1 | return self.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/loader_tags.py", line 157, in render server-1 | return compiled_parent._render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 167, in _render server-1 | return self.nodelist.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in render server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 966, in render_annotated server-1 | return self.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/loader_tags.py", line 63, in render server-1 | result = block.nodelist.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in render server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 966, in render_annotated server-1 | return self.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/loader_tags.py", line 208, in render server-1 | return template.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 177, in render server-1 | return self._render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 167, in _render server-1 | return self.nodelist.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in render server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 966, in render_annotated server-1 | return self.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/defaulttags.py", line 321, in render server-1 | return nodelist.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in render server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 966, in render_annotated server-1 | return self.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/defaulttags.py", line 471, in render server-1 | url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/urls/base.py", line 88, in reverse server-1 | return resolver._reverse_with_prefix(view, prefix, *args, **kwargs) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/urls/resolvers.py", line 828, in _reverse_with_prefix server-1 | raise NoReverseMatch(msg) server-1 | django.urls.exceptions.NoReverseMatch: Reverse for 'platform' not found. 'platform' is not a valid view function or pattern name. ```

Disabling CUI, enabling PUI

Reproduction

Make the opposite changes in config.yaml:

classic_frontend: true
platform_frontend: false

Then restart the server and worker as before.

Results

An already logged-in user will be redirected from / to /platform/ to /platform/home/, preserving functionality in the PUI. From my testing, the entire UI can be used without issue.

A user that is not yet logged in can access /platform/login/ without issue. However, accessing / does not redirect and rather presents another 500 Internal Server Error, this time without styling:

Internal Server Error

Click to toggle logs ```shell server-1 | Traceback (most recent call last): server-1 | File "/root/.local/lib/python3.11/site-packages/gunicorn/workers/gthread.py", line 282, in handle server-1 | keepalive = self.handle_request(req, conn) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/gunicorn/workers/gthread.py", line 334, in handle_request server-1 | respiter = self.wsgi(environ, resp.start_response) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/core/handlers/wsgi.py", line 124, in __call__ server-1 | response = self.get_response(request) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/core/handlers/base.py", line 140, in get_response server-1 | response = self._middleware_chain(request) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 57, in inner server-1 | response = response_for_exception(request, exc) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 140, in response_for_exception server-1 | response = handle_uncaught_exception( server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/core/handlers/exception.py", line 185, in handle_uncaught_exception server-1 | return callback(request) server-1 | ^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/utils/decorators.py", line 134, in _wrapper_view server-1 | response = view_func(request, *args, **kwargs) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/views/defaults.py", line 99, in server_error server-1 | return HttpResponseServerError(template.render()) server-1 | ^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/backends/django.py", line 61, in render server-1 | return self.template.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 175, in render server-1 | return self._render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 167, in _render server-1 | return self.nodelist.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in render server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 966, in render_annotated server-1 | return self.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/loader_tags.py", line 157, in render server-1 | return compiled_parent._render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 167, in _render server-1 | return self.nodelist.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in render server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 966, in render_annotated server-1 | return self.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/loader_tags.py", line 208, in render server-1 | return template.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 177, in render server-1 | return self._render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 167, in _render server-1 | return self.nodelist.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in render server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 1005, in server-1 | return SafeString("".join([node.render_annotated(context) for node in self])) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/base.py", line 966, in render_annotated server-1 | return self.render(context) server-1 | ^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/template/defaulttags.py", line 471, in render server-1 | url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/urls/base.py", line 88, in reverse server-1 | return resolver._reverse_with_prefix(view, prefix, *args, **kwargs) server-1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ server-1 | File "/root/.local/lib/python3.11/site-packages/django/urls/resolvers.py", line 828, in _reverse_with_prefix server-1 | raise NoReverseMatch(msg) server-1 | django.urls.exceptions.NoReverseMatch: Reverse for 'notifications' not found. 'notifications' is not a valid view function or pattern name. ```

Disabling both, for fun (?) and profit (??)

Yes, this is obviously a silly thing to do, but let's try it anyway:

classic_frontend: true
platform_frontend: false

Loading / gives us the Internal Server Error we've already seen: Internal Server Error

Loading /platform (no trailing slash) makes Firefox complain: The page isn't redirecting properly

Loading /platform/ (with a trailing slash) redirects us to /platform/platform. Adding a trailing slash again and loading /platform/platform/ redirects us to /platform/platform/platform. All steps along the way involve the same redirect error shown above.

Further information

stack.env ```env INVENTREE_TAG=0.16.8 INVENTREE_DB_NAME=inventree INVENTREE_DB_HOST=db INVENTREE_DB_PORT=5432 INVENTREE_DB_USER=### REDACTED ### INVENTREE_DB_PASSWORD=### REDACTED ### INVENTREE_GUNICORN_TIMEOUT=90 ```
config.yaml ```yaml debug: false log_level: WARNING db_logging: false language: en-us timezone: Europe/Amsterdam site_url: '### REDACTED ###' sentry_enabled: False tracing: enabled: false plugins_enabled: True auto_update: True allowed_hosts: - '*' trusted_origins: - '### REDACTED ###' use_x_forwarded_host: false use_x_forwarded_port: false cookie: secure: true samesite: false cors: allow_all: false allow_credentials: true whitelist: - '### REDACTED ###' background: workers: 4 timeout: 90 max_attempts: 5 cache: enabled: true host: 'cache' port: 6379 login_confirm_days: 3 login_attempts: 5 login_default_protocol: https logout_redirect_url: '### REDACTED ###' social_backends: - 'allauth.socialaccount.providers.openid_connect' ldap: enabled: false classic_frontend: false # <-- Issue platform_frontend: true # <-- Issue ```
compose.yaml ```yaml services: db: image: postgres:13 restart: unless-stopped volumes: - data:/var/lib/postgresql/data/ environment: PGDATA: /var/lib/postgresql/data/pgdb POSTGRES_USER: ${INVENTREE_DB_USER:?You must provide the 'INVENTREE_DB_USER' variable in the .env file} POSTGRES_PASSWORD: ${INVENTREE_DB_PASSWORD:?You must provide the 'INVENTREE_DB_PASSWORD' variable in the .env file} POSTGRES_DB: ${INVENTREE_DB_NAME:?You must provide the 'INVENTREE_DB_NAME' variable in the .env file} cache: image: redis:7.0 restart: unless-stopped env_file: - stack.env server: image: inventree/inventree:${INVENTREE_TAG:-stable} restart: unless-stopped depends_on: - db - cache networks: - default - exposed volumes: - data:/home/inventree/data - ./config.yaml:/home/inventree/data/config.yaml:z env_file: - stack.env worker: image: inventree/inventree:${INVENTREE_TAG:-stable} restart: unless-stopped depends_on: - server command: invoke worker volumes: - data:/home/inventree/data - ./config.yaml:/home/inventree/data/config.yaml:z env_file: - stack.env proxy: image: caddy:alpine restart: unless-stopped hostname: pottery-proxy depends_on: - server networks: - default - exposed volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro,z - data:/var/log - data:/data - data:/config - type: volume source: data target: /var/www/static volume: subpath: static - type: volume source: data target: /var/www/media volume: subpath: media env_file: - stack.env networks: exposed: external: true volumes: data: ```
Version information ``` InvenTree-Version: 0.16.8 Django Version: 4.2.15 Commit Hash: f23d405 Commit Date: 2024-11-07 Database: postgresql Debug-Mode: False Deployed using Docker: True Platform: Linux-6.1.0-21-arm64-aarch64-with Installer: DOC Active plugins: [{'name': 'InvenTreeBarcode', 'slug': 'inventreebarcode', 'version': '2.1.0'}, {'name': 'InvenTreeCoreNotificationsPlugin', 'slug': 'inventreecorenotificationsplugin', 'version': '1.0.0'}, {'name': 'InvenTreeCurrencyExchange', 'slug': 'inventreecurrencyexchange', 'version': '1.0.0'}, {'name': 'InvenTreeLabel', 'slug': 'inventreelabel', 'version': '1.1.0'}, {'name': 'InvenTreeLabelMachine', 'slug': 'inventreelabelmachine', 'version': '1.0.0'}, {'name': 'InvenTreeLabelSheet', 'slug': 'inventreelabelsheet', 'version': '1.0.0'}, {'name': 'DigiKeyPlugin', 'slug': 'digikeyplugin', 'version': '1.0.0'}, {'name': 'LCSCPlugin', 'slug': 'lcscplugin', 'version': '1.0.0'}, {'name': 'MouserPlugin', 'slug': 'mouserplugin', 'version': '1.0.0'}, {'name': 'TMEPlugin', 'slug': 'tmeplugin', 'version': '1.0.0'}] ```

I checked and didn't find a similar issue.

SchrodingersGat commented 4 days ago

@thcrt thanks for reporting this! Fix incoming in https://github.com/inventree/InvenTree/pull/8508

matmair commented 3 days ago

Just as a note - we are removing CUI after the soon-ish release of 0.17.0 - see https://github.com/inventree/InvenTree/pull/8384 . Please report issues with PUI as it will be the only interface available