PyInventory 0.22.1__tar.gz → 0.23.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. {pyinventory-0.22.1 → pyinventory-0.23.1}/.github/workflows/tests.yml +2 -0
  2. {pyinventory-0.22.1 → pyinventory-0.23.1}/PKG-INFO +17 -5
  3. {pyinventory-0.22.1 → pyinventory-0.23.1}/README.md +16 -4
  4. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/__init__.py +1 -1
  5. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/admin/item.py +51 -3
  6. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/admin/memo.py +1 -2
  7. pyinventory-0.23.1/inventory/admin/tests/test_item.py +134 -0
  8. pyinventory-0.23.1/inventory/admin/tests/test_item_happy_path_1.snapshot.html +48 -0
  9. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/models/__init__.py +1 -1
  10. pyinventory-0.23.1/inventory/static/PyInventory_Logo.svg +10652 -0
  11. pyinventory-0.23.1/inventory/static/inventory.css +23 -0
  12. pyinventory-0.23.1/inventory/templates/admin/item/mass_change_category_action.html +32 -0
  13. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/templates/admin/base_site.html +3 -2
  14. pyinventory-0.23.1/inventory_project/tests/__init__.py +0 -0
  15. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_admin_item_auto_group_items_1.snapshot.html +3 -0
  16. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_admin_item_normal_user_create_minimal_item_2.snapshot.html +7 -8
  17. {pyinventory-0.22.1 → pyinventory-0.23.1}/pyproject.toml +2 -4
  18. {pyinventory-0.22.1 → pyinventory-0.23.1}/uv.lock +166 -102
  19. pyinventory-0.22.1/inventory/admin/tagulous_fix.py +0 -45
  20. {pyinventory-0.22.1 → pyinventory-0.23.1}/.editorconfig +0 -0
  21. {pyinventory-0.22.1 → pyinventory-0.23.1}/.gitignore +0 -0
  22. {pyinventory-0.22.1 → pyinventory-0.23.1}/.idea/.gitignore +0 -0
  23. {pyinventory-0.22.1 → pyinventory-0.23.1}/.pre-commit-config.yaml +0 -0
  24. {pyinventory-0.22.1 → pyinventory-0.23.1}/.pre-commit-hooks.yaml +0 -0
  25. {pyinventory-0.22.1 → pyinventory-0.23.1}/.run/Template Django tests.run.xml +0 -0
  26. {pyinventory-0.22.1 → pyinventory-0.23.1}/.run/Template Python.run.xml +0 -0
  27. {pyinventory-0.22.1 → pyinventory-0.23.1}/.run/manage.py --help.run.xml +0 -0
  28. {pyinventory-0.22.1 → pyinventory-0.23.1}/.run/manage.py make_messages.run.xml +0 -0
  29. {pyinventory-0.22.1 → pyinventory-0.23.1}/.run/manage.py update_req.run.xml +0 -0
  30. {pyinventory-0.22.1 → pyinventory-0.23.1}/.run/unittests ___all___.run.xml +0 -0
  31. {pyinventory-0.22.1 → pyinventory-0.23.1}/AUTHORS +0 -0
  32. {pyinventory-0.22.1 → pyinventory-0.23.1}/LICENSE +0 -0
  33. {pyinventory-0.22.1 → pyinventory-0.23.1}/dist/.gitignore +0 -0
  34. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/admin/__init__.py +0 -0
  35. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/admin/base.py +0 -0
  36. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/admin/location.py +0 -0
  37. {pyinventory-0.22.1/inventory/management → pyinventory-0.23.1/inventory/admin/tests}/__init__.py +0 -0
  38. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/apps.py +0 -0
  39. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/checks.py +0 -0
  40. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/ckeditor_upload.py +0 -0
  41. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/constants.py +0 -0
  42. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/context_processors.py +0 -0
  43. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/forms.py +0 -0
  44. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/locale/ca/LC_MESSAGES/django.mo +0 -0
  45. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/locale/ca/LC_MESSAGES/django.po +0 -0
  46. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/locale/de/LC_MESSAGES/django.mo +0 -0
  47. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/locale/de/LC_MESSAGES/django.po +0 -0
  48. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/locale/en/LC_MESSAGES/django.mo +0 -0
  49. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/locale/en/LC_MESSAGES/django.po +0 -0
  50. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/locale/es/LC_MESSAGES/django.mo +0 -0
  51. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/locale/es/LC_MESSAGES/django.po +0 -0
  52. {pyinventory-0.22.1/inventory/management/commands → pyinventory-0.23.1/inventory/management}/__init__.py +0 -0
  53. {pyinventory-0.22.1/inventory/migrations → pyinventory-0.23.1/inventory/management/commands}/__init__.py +0 -0
  54. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/management/commands/seed_data.py +0 -0
  55. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/management/commands/tree.py +0 -0
  56. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/middlewares.py +0 -0
  57. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0001_initial.py +0 -0
  58. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0002_auto_20201017_2211.py +0 -0
  59. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0003_auto_20201024_1830.py +0 -0
  60. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0004_item_user_images.py +0 -0
  61. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0005_serve_uploads_by_django_tools.py +0 -0
  62. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0006_refactor_image_model.py +0 -0
  63. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0007_add_file_attachment.py +0 -0
  64. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0008_last_check_datetime.py +0 -0
  65. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0009_add_memo.py +0 -0
  66. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0010_version_protect_models.py +0 -0
  67. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0011_parent_tree1.py +0 -0
  68. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0012_parent_tree2.py +0 -0
  69. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0013_alter_itemmodel_location.py +0 -0
  70. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0014_alter_itemmodel_description_and_more.py +0 -0
  71. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/migrations/0015_itemmaincategory_itemmodel_category.py +0 -0
  72. {pyinventory-0.22.1/inventory/tests/fixtures → pyinventory-0.23.1/inventory/migrations}/__init__.py +0 -0
  73. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/models/base.py +0 -0
  74. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/models/item.py +0 -0
  75. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/models/links.py +0 -0
  76. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/models/location.py +0 -0
  77. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/models/memo.py +0 -0
  78. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/parent_tree.py +0 -0
  79. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/permissions.py +0 -0
  80. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/persistent_filters.py +0 -0
  81. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/request_dict.py +0 -0
  82. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/signals.py +0 -0
  83. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/string_utils.py +0 -0
  84. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/templates/admin/item/related_items.html +0 -0
  85. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/templates/admin/location/items.html +0 -0
  86. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/tests/__init__.py +0 -0
  87. {pyinventory-0.22.1/inventory_project/settings → pyinventory-0.23.1/inventory/tests/fixtures}/__init__.py +0 -0
  88. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/tests/fixtures/users.py +0 -0
  89. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/tests/test_admin_location.py +0 -0
  90. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/tests/test_admin_location_empty_change_list_1.snapshot.html +0 -0
  91. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/tests/test_item_images.py +0 -0
  92. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/tests/test_link_model.py +0 -0
  93. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/tests/test_management_command_seed_data.py +0 -0
  94. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/tests/test_management_command_tree.py +0 -0
  95. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/tests/test_parent_tree.py +0 -0
  96. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory/tests/test_parent_tree_model.py +0 -0
  97. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/__init__.py +0 -0
  98. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/__main__.py +0 -0
  99. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/middlewares.py +0 -0
  100. {pyinventory-0.22.1/inventory_project/tests → pyinventory-0.23.1/inventory_project/settings}/__init__.py +0 -0
  101. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/settings/local.py +0 -0
  102. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/settings/prod.py +0 -0
  103. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/settings/tests.py +0 -0
  104. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/templates/admin/login.html +0 -0
  105. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/fixtures.py +0 -0
  106. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/mocks.py +0 -0
  107. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/playwright_utils.py +0 -0
  108. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_admin.py +0 -0
  109. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_admin_item.py +0 -0
  110. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_admin_item_login_1.snapshot.html +0 -0
  111. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_admin_item_normal_user_create_minimal_item_1.snapshot.html +0 -0
  112. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_admin_memo.py +0 -0
  113. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_admin_memo_normal_user_create_minimal_item_1.snapshot.html +0 -0
  114. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_admin_superuser_admin_index_1.snapshot.html +0 -0
  115. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_inventory_commands.py +0 -0
  116. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_inventory_commands_help_1.snapshot.txt +0 -0
  117. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_migrations.py +0 -0
  118. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_models_item.py +0 -0
  119. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_playwright_admin.py +0 -0
  120. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_project_setup.py +0 -0
  121. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/tests/test_readme_history.py +0 -0
  122. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/urls.py +0 -0
  123. {pyinventory-0.22.1 → pyinventory-0.23.1}/inventory_project/wsgi.py +0 -0
  124. {pyinventory-0.22.1 → pyinventory-0.23.1}/manage.py +0 -0
@@ -1,6 +1,8 @@
1
1
 
2
2
 
3
3
  name: tests
4
+ permissions:
5
+ contents: read
4
6
 
5
7
  on:
6
8
  push:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyInventory
3
- Version: 0.22.1
3
+ Version: 0.23.1
4
4
  Summary: Web based management to catalog things including state and location etc. using Python/Django.
5
5
  Project-URL: Documentation, https://github.com/jedie/PyInventory
6
6
  Project-URL: Source, https://github.com/jedie/PyInventory
@@ -28,7 +28,10 @@ Requires-Dist: pillow
28
28
  Requires-Dist: requests
29
29
  Description-Content-Type: text/markdown
30
30
 
31
- # PyInventory
31
+ <h1>
32
+ <img src="https://raw.githubusercontent.com/jedie/PyInventory/main/inventory/static/PyInventory_Logo.svg" width="96px" alt="Logo">
33
+ PyInventory
34
+ </h1>
32
35
 
33
36
  Web based management to catalog things including state and location etc. using Python/Django.
34
37
  Store information in WYSIWYG-HTML-Editor field and tag them and add Files, Images and Links to them.
@@ -196,6 +199,15 @@ To make a new release, do this:
196
199
 
197
200
  [comment]: <> (✂✂✂ auto generated history start ✂✂✂)
198
201
 
202
+ * [v0.23.1](https://github.com/jedie/PyInventory/compare/v0.23.0...v0.23.1)
203
+ * 2025-11-28 - Update README.md
204
+ * 2025-11-28 - Add logo
205
+ * [v0.23.0](https://github.com/jedie/PyInventory/compare/v0.22.1...v0.23.0)
206
+ * 2025-11-25 - Enhance 'Change category for selected items' and add tests for it
207
+ * 2025-11-25 - Update requirements
208
+ * 2025-11-25 - Remove django-tagulous work-a-round, because it's fixed.
209
+ * 2025-11-19 - Add admin action to change the category of selected items
210
+ * 2025-11-19 - Remove "GHSA-4xh5-x5gv-qwph" exception
199
211
  * [v0.22.1](https://github.com/jedie/PyInventory/compare/v0.22.0...v0.22.1)
200
212
  * 2025-11-18 - Fix #207 "All" Category can't be selected
201
213
  * 2025-11-18 - Update requirements
@@ -203,6 +215,9 @@ To make a new release, do this:
203
215
  * 2025-10-16 - Add tests for PersistentRelatedFieldListFilter
204
216
  * 2025-09-21 - NEW: Add a persistent "Main Category"
205
217
  * 2025-09-21 - fix for django-admin-sortable2/issues/363
218
+
219
+ <details><summary>Expand older history entries ...</summary>
220
+
206
221
  * [v0.21.4](https://github.com/jedie/PyInventory/compare/v0.21.3...v0.21.4)
207
222
  * 2025-09-21 - Because of PyPi download errors: release as v0.21.4
208
223
  * [v0.21.3](https://github.com/jedie/PyInventory/compare/v0.21.2...v0.21.3)
@@ -217,9 +232,6 @@ To make a new release, do this:
217
232
  * 2025-09-20 - pre-commit: remove "default_install_hook_types"
218
233
  * 2025-09-20 - Remove obsolete config files
219
234
  * 2025-09-20 - Update ReadmeHistoryTestCase
220
-
221
- <details><summary>Expand older history entries ...</summary>
222
-
223
235
  * [v0.21.2](https://github.com/jedie/PyInventory/compare/v0.21.1...v0.21.2)
224
236
  * 2025-09-09 - Update project, e.g.: Darker -> Ruff and fix tests
225
237
  * 2025-05-01 - Fix local dev server: Don't enforce https
@@ -1,4 +1,7 @@
1
- # PyInventory
1
+ <h1>
2
+ <img src="https://raw.githubusercontent.com/jedie/PyInventory/main/inventory/static/PyInventory_Logo.svg" width="96px" alt="Logo">
3
+ PyInventory
4
+ </h1>
2
5
 
3
6
  Web based management to catalog things including state and location etc. using Python/Django.
4
7
  Store information in WYSIWYG-HTML-Editor field and tag them and add Files, Images and Links to them.
@@ -166,6 +169,15 @@ To make a new release, do this:
166
169
 
167
170
  [comment]: <> (✂✂✂ auto generated history start ✂✂✂)
168
171
 
172
+ * [v0.23.1](https://github.com/jedie/PyInventory/compare/v0.23.0...v0.23.1)
173
+ * 2025-11-28 - Update README.md
174
+ * 2025-11-28 - Add logo
175
+ * [v0.23.0](https://github.com/jedie/PyInventory/compare/v0.22.1...v0.23.0)
176
+ * 2025-11-25 - Enhance 'Change category for selected items' and add tests for it
177
+ * 2025-11-25 - Update requirements
178
+ * 2025-11-25 - Remove django-tagulous work-a-round, because it's fixed.
179
+ * 2025-11-19 - Add admin action to change the category of selected items
180
+ * 2025-11-19 - Remove "GHSA-4xh5-x5gv-qwph" exception
169
181
  * [v0.22.1](https://github.com/jedie/PyInventory/compare/v0.22.0...v0.22.1)
170
182
  * 2025-11-18 - Fix #207 "All" Category can't be selected
171
183
  * 2025-11-18 - Update requirements
@@ -173,6 +185,9 @@ To make a new release, do this:
173
185
  * 2025-10-16 - Add tests for PersistentRelatedFieldListFilter
174
186
  * 2025-09-21 - NEW: Add a persistent "Main Category"
175
187
  * 2025-09-21 - fix for django-admin-sortable2/issues/363
188
+
189
+ <details><summary>Expand older history entries ...</summary>
190
+
176
191
  * [v0.21.4](https://github.com/jedie/PyInventory/compare/v0.21.3...v0.21.4)
177
192
  * 2025-09-21 - Because of PyPi download errors: release as v0.21.4
178
193
  * [v0.21.3](https://github.com/jedie/PyInventory/compare/v0.21.2...v0.21.3)
@@ -187,9 +202,6 @@ To make a new release, do this:
187
202
  * 2025-09-20 - pre-commit: remove "default_install_hook_types"
188
203
  * 2025-09-20 - Remove obsolete config files
189
204
  * 2025-09-20 - Update ReadmeHistoryTestCase
190
-
191
- <details><summary>Expand older history entries ...</summary>
192
-
193
205
  * [v0.21.2](https://github.com/jedie/PyInventory/compare/v0.21.1...v0.21.2)
194
206
  * 2025-09-09 - Update project, e.g.: Darker -> Ruff and fix tests
195
207
  * 2025-05-01 - Fix local dev server: Don't enforce https
@@ -8,5 +8,5 @@
8
8
  """
9
9
 
10
10
  # See https://packaging.python.org/en/latest/specifications/version-specifiers/
11
- __version__ = '0.22.1'
11
+ __version__ = '0.23.1'
12
12
  __author__ = 'Jens Diemer <PyInventory@jensdiemer.de>'
@@ -2,8 +2,11 @@ import logging
2
2
 
3
3
  import tagulous
4
4
  from adminsortable2.admin import SortableAdminBase, SortableAdminMixin, SortableInlineAdminMixin
5
+ from django import forms
5
6
  from django.conf import settings
6
- from django.contrib import admin
7
+ from django.contrib import admin, messages
8
+ from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
9
+ from django.shortcuts import render
7
10
  from django.template.loader import render_to_string
8
11
  from django.urls import reverse
9
12
  from django.utils.html import format_html
@@ -18,7 +21,6 @@ from inventory.admin.base import (
18
21
  LimitTreeDepthListFilter,
19
22
  UserInlineMixin,
20
23
  )
21
- from inventory.admin.tagulous_fix import TagulousModelAdminFix
22
24
  from inventory.models import ItemLinkModel, ItemModel
23
25
  from inventory.models.item import ItemFileModel, ItemImageModel, ItemMainCategory
24
26
  from inventory.persistent_filters import PersistentRelatedFieldListFilter
@@ -51,8 +53,53 @@ class ItemModelResource(ModelResource):
51
53
  model = ItemModel
52
54
 
53
55
 
56
+ class ChangeCategoryForm(forms.Form):
57
+ category = forms.ModelChoiceField(
58
+ queryset=ItemMainCategory.objects.all(),
59
+ label=_('New category'),
60
+ required=True,
61
+ )
62
+
63
+
64
+ @admin.action(description=_('Change category for selected items'))
65
+ def mass_change_category_action(modeladmin, request, queryset):
66
+ item_count = queryset.count()
67
+ assert item_count > 0, 'No items selected for mass category change.'
68
+ if 'category' in request.POST:
69
+ form = ChangeCategoryForm(request.POST)
70
+ if form.is_valid():
71
+ category = form.cleaned_data['category']
72
+ updated = queryset.update(category=category)
73
+ messages.info(
74
+ request,
75
+ _('%(count)d items have been assigned to the category "%(category)s".')
76
+ % {'count': updated, 'category': str(category)},
77
+ )
78
+ return None
79
+ else:
80
+ form = ChangeCategoryForm()
81
+
82
+ # Collect currently used categories from selected items:
83
+ used_categories = queryset.order_by('category__name').values_list('category__name', flat=True).distinct()
84
+
85
+ return render(
86
+ request,
87
+ 'admin/item/mass_change_category_action.html',
88
+ context={
89
+ 'title': _('Change category for selected items'),
90
+ 'opts': modeladmin.model._meta,
91
+ 'item_count': item_count,
92
+ 'items': queryset,
93
+ 'used_categories': used_categories,
94
+ 'form': form,
95
+ 'action_checkbox_name': ACTION_CHECKBOX_NAME,
96
+ **modeladmin.admin_site.each_context(request),
97
+ },
98
+ )
99
+
100
+
54
101
  @admin.register(ItemModel)
55
- class ItemModelAdmin(TagulousModelAdminFix, ImportExportMixin, SortableAdminBase, BaseUserAdmin):
102
+ class ItemModelAdmin(ImportExportMixin, SortableAdminBase, BaseUserAdmin):
56
103
  @admin.display(description=_('Related items'))
57
104
  def related_items(self, obj):
58
105
  if obj.pk is None:
@@ -167,6 +214,7 @@ class ItemModelAdmin(TagulousModelAdminFix, ImportExportMixin, SortableAdminBase
167
214
  autocomplete_fields = ('parent', 'location')
168
215
  readonly_fields = ('id', 'create_dt', 'update_dt', 'user', 'related_items')
169
216
  inlines = (ItemImageModelInline, ItemFileModelInline, ItemLinkModelInline)
217
+ actions = (mass_change_category_action,)
170
218
 
171
219
 
172
220
  tagulous.admin.enhance(ItemModel, ItemModelAdmin)
@@ -8,7 +8,6 @@ from import_export.admin import ImportExportMixin
8
8
  from import_export.resources import ModelResource
9
9
 
10
10
  from inventory.admin.base import BaseFileModelInline, BaseImageModelInline, BaseUserAdmin, UserInlineMixin
11
- from inventory.admin.tagulous_fix import TagulousModelAdminFix
12
11
  from inventory.models import MemoLinkModel, MemoModel
13
12
  from inventory.models.memo import MemoFileModel, MemoImageModel
14
13
 
@@ -35,7 +34,7 @@ class MemoModelResource(ModelResource):
35
34
 
36
35
 
37
36
  @admin.register(MemoModel)
38
- class MemoModelAdmin(TagulousModelAdminFix, ImportExportMixin, SortableAdminBase, BaseUserAdmin):
37
+ class MemoModelAdmin(ImportExportMixin, SortableAdminBase, BaseUserAdmin):
39
38
  def get_max_order(self, request, obj=None):
40
39
  # Work-a-round for: https://github.com/jrief/django-admin-sortable2/issues/341
41
40
  return 0
@@ -0,0 +1,134 @@
1
+ from unittest import mock
2
+ from uuid import UUID
3
+
4
+ from bx_django_utils.test_utils.html_assertion import HtmlAssertionMixin, assert_html_response_snapshot
5
+ from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
6
+ from django.template.defaulttags import CsrfTokenNode, NowNode
7
+ from django.test import TestCase, override_settings
8
+ from django.urls import reverse
9
+ from model_bakery import baker
10
+
11
+ from inventory.admin.item import mass_change_category_action
12
+ from inventory.models import ItemMainCategory, ItemModel
13
+ from inventory_project.tests.fixtures import get_normal_user
14
+ from inventory_project.tests.mocks import MockInventoryVersionString
15
+
16
+
17
+ @override_settings(SECURE_SSL_REDIRECT=False)
18
+ class MassChangeCategoryActionAdminTest(HtmlAssertionMixin, TestCase):
19
+ maxDiff = None
20
+
21
+ def test_happy_path(self):
22
+ normaluser = get_normal_user()
23
+ self.client.force_login(normaluser)
24
+
25
+ category1 = baker.make(ItemMainCategory, order=0, id=1, name='Category 1')
26
+ category2 = baker.make(ItemMainCategory, order=1, id=2, name='Category 2')
27
+ baker.make(
28
+ ItemModel,
29
+ name='A',
30
+ user=normaluser,
31
+ id=UUID('80dddef9-0000-0000-0000-000000000001'),
32
+ category=category1,
33
+ ).full_clean()
34
+ baker.make(
35
+ ItemModel,
36
+ name='B',
37
+ user=normaluser,
38
+ id=UUID('80dddef9-0000-0000-0000-000000000002'),
39
+ category=category1,
40
+ ).full_clean()
41
+ baker.make(
42
+ ItemModel,
43
+ name='C',
44
+ user=normaluser,
45
+ id=UUID('80dddef9-0000-0000-0000-000000000003'),
46
+ category=category2,
47
+ ).full_clean()
48
+ baker.make(
49
+ ItemModel,
50
+ name='D',
51
+ user=normaluser,
52
+ id=UUID('80dddef9-0000-0000-0000-000000000004'),
53
+ category=category2,
54
+ ).full_clean()
55
+
56
+ url = reverse('admin:inventory_itemmodel_changelist')
57
+ self.assertEqual(url, '/admin/inventory/itemmodel/')
58
+
59
+ ########################################################################################
60
+ # Snapshot the Form:
61
+ with (
62
+ mock.patch.object(NowNode, 'render', return_value='MockedNowNode'),
63
+ mock.patch.object(CsrfTokenNode, 'render', return_value='MockedCsrfTokenNode'),
64
+ MockInventoryVersionString(),
65
+ ):
66
+ response = self.client.post(
67
+ '/admin/inventory/itemmodel/',
68
+ data={
69
+ ACTION_CHECKBOX_NAME: [
70
+ '80dddef9-0000-0000-0000-000000000002',
71
+ '80dddef9-0000-0000-0000-000000000003',
72
+ ],
73
+ 'action': mass_change_category_action.__name__,
74
+ },
75
+ )
76
+ self.assertEqual(response.status_code, 200)
77
+ self.assertTemplateUsed(response, 'admin/item/mass_change_category_action.html')
78
+ self.assert_html_parts(
79
+ response,
80
+ parts=(
81
+ '<title>Change category for selected items | PyInventory vMockedVersionString</title>',
82
+ '<p>Number of selected items: 2</p>',
83
+ '<label for="id_category">New category:</label>',
84
+ '<input type="hidden" name="action" value="mass_change_category_action">',
85
+ ),
86
+ )
87
+ assert_html_response_snapshot(response, query_selector='#content', validate=False)
88
+
89
+ ########################################################################################
90
+ # Perform the action
91
+
92
+ def get_info():
93
+ return list(ItemModel.objects.order_by('pk').values_list('pk', 'category__name'))
94
+
95
+ before = get_info()
96
+
97
+ response = self.client.post(
98
+ '/admin/inventory/itemmodel/',
99
+ data={
100
+ ACTION_CHECKBOX_NAME: [
101
+ '80dddef9-0000-0000-0000-000000000002',
102
+ '80dddef9-0000-0000-0000-000000000003',
103
+ ],
104
+ 'action': mass_change_category_action.__name__,
105
+ 'category': category2.pk,
106
+ },
107
+ )
108
+ self.assertRedirects(response, '/admin/inventory/itemmodel/', fetch_redirect_response=False)
109
+ self.assert_messages(
110
+ response,
111
+ expected_messages=[
112
+ '2 items have been assigned to the category "Category 2".',
113
+ ],
114
+ )
115
+
116
+ after = get_info()
117
+ self.assertEqual(
118
+ before,
119
+ [
120
+ (UUID('80dddef9-0000-0000-0000-000000000001'), 'Category 1'),
121
+ (UUID('80dddef9-0000-0000-0000-000000000002'), 'Category 1'),
122
+ (UUID('80dddef9-0000-0000-0000-000000000003'), 'Category 2'),
123
+ (UUID('80dddef9-0000-0000-0000-000000000004'), 'Category 2'),
124
+ ],
125
+ )
126
+ self.assertEqual(
127
+ after,
128
+ [
129
+ (UUID('80dddef9-0000-0000-0000-000000000001'), 'Category 1'), # Not selected
130
+ (UUID('80dddef9-0000-0000-0000-000000000002'), 'Category 2'), # Changed
131
+ (UUID('80dddef9-0000-0000-0000-000000000003'), 'Category 2'), # Already there
132
+ (UUID('80dddef9-0000-0000-0000-000000000004'), 'Category 2'), # Not selected
133
+ ],
134
+ )
@@ -0,0 +1,48 @@
1
+ <div class="colM" id="content">
2
+ <h1>
3
+ Change category for selected items
4
+ </h1>
5
+ <p>
6
+ Number of selected items: 2
7
+ </p>
8
+ <p>
9
+ Currently used categories are:
10
+ </p>
11
+ <ul>
12
+ <li>
13
+ Category 1
14
+ </li>
15
+ <li>
16
+ Category 2
17
+ </li>
18
+ </ul>
19
+ <form method="post">
20
+ MockedCsrfTokenNode
21
+ <div>
22
+ <p>
23
+ <label for="id_category">
24
+ New category:
25
+ </label>
26
+ <select id="id_category" name="category" required="">
27
+ <option selected="" value="">
28
+ ---------
29
+ </option>
30
+ <option value="1">
31
+ Category 1
32
+ </option>
33
+ <option value="2">
34
+ Category 2
35
+ </option>
36
+ </select>
37
+ </p>
38
+ <input name="action" type="hidden" value="mass_change_category_action"/>
39
+ <input name="_selected_action" type="hidden" value="80dddef9-0000-0000-0000-000000000002"/>
40
+ <input name="_selected_action" type="hidden" value="80dddef9-0000-0000-0000-000000000003"/>
41
+ <input type="submit" value="Yes, I’m sure"/>
42
+ <a class="button cancel-link" href="/admin/inventory/itemmodel/">
43
+ No, take me back
44
+ </a>
45
+ </div>
46
+ </form>
47
+ <br class="clear"/>
48
+ </div>
@@ -1,3 +1,3 @@
1
- from inventory.models.item import ItemFileModel, ItemImageModel, ItemLinkModel, ItemModel # noqa
1
+ from inventory.models.item import ItemFileModel, ItemImageModel, ItemLinkModel, ItemModel, ItemMainCategory # noqa
2
2
  from inventory.models.location import LocationModel # noqa
3
3
  from inventory.models.memo import MemoFileModel, MemoImageModel, MemoLinkModel, MemoModel # noqa