PyInventory 0.22.1__tar.gz → 0.23.0__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 (122) hide show
  1. {pyinventory-0.22.1 → pyinventory-0.23.0}/PKG-INFO +10 -4
  2. {pyinventory-0.22.1 → pyinventory-0.23.0}/README.md +9 -3
  3. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/__init__.py +1 -1
  4. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/admin/item.py +51 -3
  5. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/admin/memo.py +1 -2
  6. pyinventory-0.23.0/inventory/admin/tests/test_item.py +134 -0
  7. pyinventory-0.23.0/inventory/admin/tests/test_item_happy_path_1.snapshot.html +48 -0
  8. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/__init__.py +1 -1
  9. pyinventory-0.23.0/inventory/templates/admin/item/mass_change_category_action.html +32 -0
  10. pyinventory-0.23.0/inventory_project/tests/__init__.py +0 -0
  11. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_item_auto_group_items_1.snapshot.html +3 -0
  12. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_item_normal_user_create_minimal_item_2.snapshot.html +3 -3
  13. {pyinventory-0.22.1 → pyinventory-0.23.0}/pyproject.toml +1 -3
  14. {pyinventory-0.22.1 → pyinventory-0.23.0}/uv.lock +62 -62
  15. pyinventory-0.22.1/inventory/admin/tagulous_fix.py +0 -45
  16. {pyinventory-0.22.1 → pyinventory-0.23.0}/.editorconfig +0 -0
  17. {pyinventory-0.22.1 → pyinventory-0.23.0}/.github/workflows/tests.yml +0 -0
  18. {pyinventory-0.22.1 → pyinventory-0.23.0}/.gitignore +0 -0
  19. {pyinventory-0.22.1 → pyinventory-0.23.0}/.idea/.gitignore +0 -0
  20. {pyinventory-0.22.1 → pyinventory-0.23.0}/.pre-commit-config.yaml +0 -0
  21. {pyinventory-0.22.1 → pyinventory-0.23.0}/.pre-commit-hooks.yaml +0 -0
  22. {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/Template Django tests.run.xml +0 -0
  23. {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/Template Python.run.xml +0 -0
  24. {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/manage.py --help.run.xml +0 -0
  25. {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/manage.py make_messages.run.xml +0 -0
  26. {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/manage.py update_req.run.xml +0 -0
  27. {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/unittests ___all___.run.xml +0 -0
  28. {pyinventory-0.22.1 → pyinventory-0.23.0}/AUTHORS +0 -0
  29. {pyinventory-0.22.1 → pyinventory-0.23.0}/LICENSE +0 -0
  30. {pyinventory-0.22.1 → pyinventory-0.23.0}/dist/.gitignore +0 -0
  31. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/admin/__init__.py +0 -0
  32. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/admin/base.py +0 -0
  33. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/admin/location.py +0 -0
  34. {pyinventory-0.22.1/inventory/management → pyinventory-0.23.0/inventory/admin/tests}/__init__.py +0 -0
  35. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/apps.py +0 -0
  36. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/checks.py +0 -0
  37. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/ckeditor_upload.py +0 -0
  38. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/constants.py +0 -0
  39. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/context_processors.py +0 -0
  40. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/forms.py +0 -0
  41. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/ca/LC_MESSAGES/django.mo +0 -0
  42. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/ca/LC_MESSAGES/django.po +0 -0
  43. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/de/LC_MESSAGES/django.mo +0 -0
  44. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/de/LC_MESSAGES/django.po +0 -0
  45. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/en/LC_MESSAGES/django.mo +0 -0
  46. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/en/LC_MESSAGES/django.po +0 -0
  47. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/es/LC_MESSAGES/django.mo +0 -0
  48. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/es/LC_MESSAGES/django.po +0 -0
  49. {pyinventory-0.22.1/inventory/management/commands → pyinventory-0.23.0/inventory/management}/__init__.py +0 -0
  50. {pyinventory-0.22.1/inventory/migrations → pyinventory-0.23.0/inventory/management/commands}/__init__.py +0 -0
  51. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/management/commands/seed_data.py +0 -0
  52. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/management/commands/tree.py +0 -0
  53. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/middlewares.py +0 -0
  54. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0001_initial.py +0 -0
  55. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0002_auto_20201017_2211.py +0 -0
  56. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0003_auto_20201024_1830.py +0 -0
  57. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0004_item_user_images.py +0 -0
  58. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0005_serve_uploads_by_django_tools.py +0 -0
  59. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0006_refactor_image_model.py +0 -0
  60. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0007_add_file_attachment.py +0 -0
  61. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0008_last_check_datetime.py +0 -0
  62. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0009_add_memo.py +0 -0
  63. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0010_version_protect_models.py +0 -0
  64. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0011_parent_tree1.py +0 -0
  65. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0012_parent_tree2.py +0 -0
  66. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0013_alter_itemmodel_location.py +0 -0
  67. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0014_alter_itemmodel_description_and_more.py +0 -0
  68. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0015_itemmaincategory_itemmodel_category.py +0 -0
  69. {pyinventory-0.22.1/inventory/tests/fixtures → pyinventory-0.23.0/inventory/migrations}/__init__.py +0 -0
  70. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/base.py +0 -0
  71. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/item.py +0 -0
  72. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/links.py +0 -0
  73. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/location.py +0 -0
  74. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/memo.py +0 -0
  75. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/parent_tree.py +0 -0
  76. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/permissions.py +0 -0
  77. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/persistent_filters.py +0 -0
  78. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/request_dict.py +0 -0
  79. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/signals.py +0 -0
  80. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/string_utils.py +0 -0
  81. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/templates/admin/item/related_items.html +0 -0
  82. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/templates/admin/location/items.html +0 -0
  83. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/__init__.py +0 -0
  84. {pyinventory-0.22.1/inventory_project/settings → pyinventory-0.23.0/inventory/tests/fixtures}/__init__.py +0 -0
  85. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/fixtures/users.py +0 -0
  86. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_admin_location.py +0 -0
  87. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_admin_location_empty_change_list_1.snapshot.html +0 -0
  88. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_item_images.py +0 -0
  89. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_link_model.py +0 -0
  90. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_management_command_seed_data.py +0 -0
  91. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_management_command_tree.py +0 -0
  92. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_parent_tree.py +0 -0
  93. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_parent_tree_model.py +0 -0
  94. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/__init__.py +0 -0
  95. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/__main__.py +0 -0
  96. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/middlewares.py +0 -0
  97. {pyinventory-0.22.1/inventory_project/tests → pyinventory-0.23.0/inventory_project/settings}/__init__.py +0 -0
  98. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/settings/local.py +0 -0
  99. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/settings/prod.py +0 -0
  100. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/settings/tests.py +0 -0
  101. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/templates/admin/base_site.html +0 -0
  102. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/templates/admin/login.html +0 -0
  103. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/fixtures.py +0 -0
  104. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/mocks.py +0 -0
  105. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/playwright_utils.py +0 -0
  106. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin.py +0 -0
  107. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_item.py +0 -0
  108. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_item_login_1.snapshot.html +0 -0
  109. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_item_normal_user_create_minimal_item_1.snapshot.html +0 -0
  110. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_memo.py +0 -0
  111. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_memo_normal_user_create_minimal_item_1.snapshot.html +0 -0
  112. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_superuser_admin_index_1.snapshot.html +0 -0
  113. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_inventory_commands.py +0 -0
  114. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_inventory_commands_help_1.snapshot.txt +0 -0
  115. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_migrations.py +0 -0
  116. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_models_item.py +0 -0
  117. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_playwright_admin.py +0 -0
  118. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_project_setup.py +0 -0
  119. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_readme_history.py +0 -0
  120. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/urls.py +0 -0
  121. {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/wsgi.py +0 -0
  122. {pyinventory-0.22.1 → pyinventory-0.23.0}/manage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyInventory
3
- Version: 0.22.1
3
+ Version: 0.23.0
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
@@ -196,6 +196,12 @@ To make a new release, do this:
196
196
 
197
197
  [comment]: <> (✂✂✂ auto generated history start ✂✂✂)
198
198
 
199
+ * [v0.23.0](https://github.com/jedie/PyInventory/compare/v0.22.1...v0.23.0)
200
+ * 2025-11-25 - Enhance 'Change category for selected items' and add tests for it
201
+ * 2025-11-25 - Update requirements
202
+ * 2025-11-25 - Remove django-tagulous work-a-round, because it's fixed.
203
+ * 2025-11-19 - Add admin action to change the category of selected items
204
+ * 2025-11-19 - Remove "GHSA-4xh5-x5gv-qwph" exception
199
205
  * [v0.22.1](https://github.com/jedie/PyInventory/compare/v0.22.0...v0.22.1)
200
206
  * 2025-11-18 - Fix #207 "All" Category can't be selected
201
207
  * 2025-11-18 - Update requirements
@@ -205,6 +211,9 @@ To make a new release, do this:
205
211
  * 2025-09-21 - fix for django-admin-sortable2/issues/363
206
212
  * [v0.21.4](https://github.com/jedie/PyInventory/compare/v0.21.3...v0.21.4)
207
213
  * 2025-09-21 - Because of PyPi download errors: release as v0.21.4
214
+
215
+ <details><summary>Expand older history entries ...</summary>
216
+
208
217
  * [v0.21.3](https://github.com/jedie/PyInventory/compare/v0.21.2...v0.21.3)
209
218
  * 2025-09-21 - Enhance change list filters by using RelatedOnlyFieldListFilter
210
219
  * 2025-09-21 - Update requirements
@@ -217,9 +226,6 @@ To make a new release, do this:
217
226
  * 2025-09-20 - pre-commit: remove "default_install_hook_types"
218
227
  * 2025-09-20 - Remove obsolete config files
219
228
  * 2025-09-20 - Update ReadmeHistoryTestCase
220
-
221
- <details><summary>Expand older history entries ...</summary>
222
-
223
229
  * [v0.21.2](https://github.com/jedie/PyInventory/compare/v0.21.1...v0.21.2)
224
230
  * 2025-09-09 - Update project, e.g.: Darker -> Ruff and fix tests
225
231
  * 2025-05-01 - Fix local dev server: Don't enforce https
@@ -166,6 +166,12 @@ To make a new release, do this:
166
166
 
167
167
  [comment]: <> (✂✂✂ auto generated history start ✂✂✂)
168
168
 
169
+ * [v0.23.0](https://github.com/jedie/PyInventory/compare/v0.22.1...v0.23.0)
170
+ * 2025-11-25 - Enhance 'Change category for selected items' and add tests for it
171
+ * 2025-11-25 - Update requirements
172
+ * 2025-11-25 - Remove django-tagulous work-a-round, because it's fixed.
173
+ * 2025-11-19 - Add admin action to change the category of selected items
174
+ * 2025-11-19 - Remove "GHSA-4xh5-x5gv-qwph" exception
169
175
  * [v0.22.1](https://github.com/jedie/PyInventory/compare/v0.22.0...v0.22.1)
170
176
  * 2025-11-18 - Fix #207 "All" Category can't be selected
171
177
  * 2025-11-18 - Update requirements
@@ -175,6 +181,9 @@ To make a new release, do this:
175
181
  * 2025-09-21 - fix for django-admin-sortable2/issues/363
176
182
  * [v0.21.4](https://github.com/jedie/PyInventory/compare/v0.21.3...v0.21.4)
177
183
  * 2025-09-21 - Because of PyPi download errors: release as v0.21.4
184
+
185
+ <details><summary>Expand older history entries ...</summary>
186
+
178
187
  * [v0.21.3](https://github.com/jedie/PyInventory/compare/v0.21.2...v0.21.3)
179
188
  * 2025-09-21 - Enhance change list filters by using RelatedOnlyFieldListFilter
180
189
  * 2025-09-21 - Update requirements
@@ -187,9 +196,6 @@ To make a new release, do this:
187
196
  * 2025-09-20 - pre-commit: remove "default_install_hook_types"
188
197
  * 2025-09-20 - Remove obsolete config files
189
198
  * 2025-09-20 - Update ReadmeHistoryTestCase
190
-
191
- <details><summary>Expand older history entries ...</summary>
192
-
193
199
  * [v0.21.2](https://github.com/jedie/PyInventory/compare/v0.21.1...v0.21.2)
194
200
  * 2025-09-09 - Update project, e.g.: Darker -> Ruff and fix tests
195
201
  * 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.0'
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
@@ -0,0 +1,32 @@
1
+ {% extends "admin/base_site.html" %}
2
+ {% load i18n l10n admin_urls %}
3
+
4
+ {% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}
5
+
6
+ {% block breadcrumbs %}
7
+ <div class="breadcrumbs">
8
+ <a href="{% url 'admin:index' %}">{% translate 'Home' %}</a>
9
+ &rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
10
+ &rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
11
+ &rsaquo; {% translate 'Change category for selected items' %}
12
+ </div>
13
+ {% endblock %}
14
+
15
+ {% block content %}
16
+ <p>{% trans "Number of selected items:" %} {{ item_count }}</p>
17
+ <p>{% trans "Currently used categories are:" %}</p>
18
+ <ul>
19
+ {% for category_name in used_categories %}
20
+ <li>{{ category_name }}</li>
21
+ {% endfor %}
22
+ </ul>
23
+ <form method="post">{% csrf_token %}
24
+ <div>
25
+ {{ form.as_p }}
26
+ <input type="hidden" name="action" value="mass_change_category_action">
27
+ {% for obj in items %}<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk }}">{% endfor %}
28
+ <input type="submit" value="{% translate 'Yes, I’m sure' %}">
29
+ <a href="{% url opts|admin_urlname:'changelist' %}" class="button cancel-link">{% translate "No, take me back" %}</a>
30
+ </div>
31
+ </form>
32
+ {% endblock %}
File without changes
@@ -59,6 +59,9 @@
59
59
  <option value="delete_selected">
60
60
  Delete selected Items
61
61
  </option>
62
+ <option value="mass_change_category_action">
63
+ Change category for selected items
64
+ </option>
62
65
  </select>
63
66
  </label>
64
67
  <input class="select-across" name="select_across" type="hidden" value="0"/>
@@ -23,8 +23,6 @@
23
23
  <link href="/static/admin/css/autocomplete.css" media="screen" rel="stylesheet"/>
24
24
  <script src="/static/admin/js/vendor/jquery/jquery.min.js">
25
25
  </script>
26
- <script src="/static/adminsortable2/js/adminsortable2.min.js">
27
- </script>
28
26
  <script src="/static/tagulous/tagulous.js">
29
27
  </script>
30
28
  <script src="/static/tinymce/tinymce.min.js">
@@ -51,7 +49,7 @@
51
49
  </script>
52
50
  <script src="/static/admin/js/admin/RelatedObjectLookups.js">
53
51
  </script>
54
- <script src="/static/admin/js/actions.js">
52
+ <script src="/static/adminsortable2/js/actions-5.1.js">
55
53
  </script>
56
54
  <script src="/static/admin/js/urlify.js">
57
55
  </script>
@@ -59,6 +57,8 @@
59
57
  </script>
60
58
  <script src="/static/admin/js/vendor/xregexp/xregexp.min.js">
61
59
  </script>
60
+ <script src="/static/adminsortable2/js/adminsortable2.min.js">
61
+ </script>
62
62
  <meta content="width=device-width, initial-scale=1.0" name="viewport"/>
63
63
  <link href="/static/admin/css/responsive.css" rel="stylesheet"/>
64
64
  <meta content="NONE,NOARCHIVE" name="robots"/>
@@ -100,9 +100,7 @@ version_module_name = "inventory" # Used by "update-readme-history" pre-commit h
100
100
  requirements=["requirements.dev.txt"]
101
101
  strict=true
102
102
  require_hashes=true
103
- ignore-vuln=[
104
- "GHSA-4xh5-x5gv-qwph", # https://github.com/advisories/GHSA-4xh5-x5gv-qwph
105
- ]
103
+ ignore-vuln=[]
106
104
 
107
105
 
108
106
  [tool.ruff]