Skip to content

fix(plugin): use app_name instead of plugin_path when deregistering models#11536

Open
nino-tan-smartee wants to merge 6 commits intoinventree:masterfrom
nino-tan-smartee:master
Open

fix(plugin): use app_name instead of plugin_path when deregistering models#11536
nino-tan-smartee wants to merge 6 commits intoinventree:masterfrom
nino-tan-smartee:master

Conversation

@nino-tan-smartee
Copy link

Summary

  • _deactivate_mixin uses plugin_path (full dotted module path) as the key into apps.all_models when removing plugin models during reload
  • Django registers models under the app_label (short app_name), not the full plugin_path
  • For plugins with nested module paths (e.g. myplugin.myplugin), plugin_path != app_name
  • Since apps.all_models is a defaultdict, looking up plugin_path silently creates an empty OrderedDict, then .pop(model) raises KeyError
  • This causes recurring KeyError crashes every plugin reload cycle (~1 minute) for any external plugin with a nested package structure

Fix

  • Use app_name (already computed at line 98) instead of plugin_path on line 118
  • Add default None to .pop() for defensive safety
  • Consistent with line 123 which already correctly uses app_name

Reproduction

  1. Create an external plugin with a nested module structure (e.g. purchase_quotation/purchase_quotation/)
  2. Install and activate the plugin
  3. Observe recurring KeyError: '<model_name>' in error reports every plugin reload cycle

Test plan

  • Verify external plugins with nested module paths no longer crash during reload
  • Verify built-in plugins continue to work correctly (no regression)
  • Verify plugin model cleanup still works (models are properly deregistered)
from setuptools import find_packages, setup

setup(
    name="inventree-purchase-quotation",
    version="0.1.0",
    description="RFQ and Purchase Quotation management for InvenTree",
    author="....",
    packages=find_packages(),
    entry_points={
        "inventree_plugins": [
            "PurchaseQuotationPlugin = "
            "purchase_quotation.plugin:PurchaseQuotationPlugin",
        ],
    },
    install_requires=[
        ...
    ],
)

…odels

_deactivate_mixin uses plugin_path (the full dotted module path) as the
key into Django's apps.all_models when removing plugin models during
reload. However, Django registers models under the app_label (the short
app_name), not the full plugin_path.

For plugins with nested module paths (e.g. "myplugin.myplugin"),
plugin_path != app_name. Since apps.all_models is a defaultdict, looking
up plugin_path silently creates an empty OrderedDict, then .pop(model)
raises KeyError because the model was never in that dict — it was
registered under app_name.

This causes recurring KeyError crashes every plugin reload cycle
(~1 minute) for any external plugin with a nested package structure.

The fix:
- Use app_name (already computed at line 98) instead of plugin_path
- Add default None to .pop() for defensive safety
- Consistent with line 123 which already correctly uses app_name
…eyerror

fix(plugin): use app_name instead of plugin_path when deregistering models
@netlify
Copy link

netlify bot commented Mar 16, 2026

Deploy Preview for inventree-web-pui-preview canceled.

Name Link
🔨 Latest commit eea70e5
🔍 Latest deploy log https://app.netlify.com/projects/inventree-web-pui-preview/deploys/69b8b7a4c83bf100087c70f7

@matmair
Copy link
Member

matmair commented Mar 16, 2026

@nino-tan-smartee thank you for the PR; please add a unit test that ensures the fixed issues does not appear again

@SchrodingersGat SchrodingersGat added bug Identifies a bug which needs to be addressed plugin Plugin ecosystem backport Apply this label to a PR to enable auto-backport action backport-to-1.2.x labels Mar 16, 2026
@SchrodingersGat SchrodingersGat modified the milestones: 1.2.5, 1.3.0 Mar 16, 2026
@codecov
Copy link

codecov bot commented Mar 16, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.08%. Comparing base (97aec82) to head (3a76fac).

Additional details and impacted files
@@            Coverage Diff             @@
##           master   #11536      +/-   ##
==========================================
- Coverage   88.08%   88.08%   -0.01%     
==========================================
  Files        1296     1296              
  Lines       59182    59182              
  Branches     1934     1934              
==========================================
- Hits        52133    52132       -1     
- Misses       6569     6570       +1     
  Partials      480      480              
Flag Coverage Δ
backend 89.26% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Backend Apps 91.70% <100.00%> (ø)
Backend General 93.43% <ø> (ø)
Frontend 70.92% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Ensures _deactivate_mixin uses app_name (last path component) instead
of the full plugin_path when looking up models in apps.all_models,
preventing KeyError for external plugins with nested module structures.
…eyerror

test(plugin): add unit test for nested plugin path model deregistration
@nino-tan-smartee
Copy link
Author

@nino-tan-smartee thank you for the PR; please add a unit test that ensures the fixed issues does not appear again

@matmair I added the unit test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport Apply this label to a PR to enable auto-backport action backport-to-1.2.x bug Identifies a bug which needs to be addressed plugin Plugin ecosystem

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants