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.
- {pyinventory-0.22.1 → pyinventory-0.23.0}/PKG-INFO +10 -4
- {pyinventory-0.22.1 → pyinventory-0.23.0}/README.md +9 -3
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/__init__.py +1 -1
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/admin/item.py +51 -3
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/admin/memo.py +1 -2
- pyinventory-0.23.0/inventory/admin/tests/test_item.py +134 -0
- pyinventory-0.23.0/inventory/admin/tests/test_item_happy_path_1.snapshot.html +48 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/__init__.py +1 -1
- pyinventory-0.23.0/inventory/templates/admin/item/mass_change_category_action.html +32 -0
- pyinventory-0.23.0/inventory_project/tests/__init__.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_item_auto_group_items_1.snapshot.html +3 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_item_normal_user_create_minimal_item_2.snapshot.html +3 -3
- {pyinventory-0.22.1 → pyinventory-0.23.0}/pyproject.toml +1 -3
- {pyinventory-0.22.1 → pyinventory-0.23.0}/uv.lock +62 -62
- pyinventory-0.22.1/inventory/admin/tagulous_fix.py +0 -45
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.editorconfig +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.github/workflows/tests.yml +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.gitignore +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.idea/.gitignore +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.pre-commit-config.yaml +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.pre-commit-hooks.yaml +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/Template Django tests.run.xml +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/Template Python.run.xml +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/manage.py --help.run.xml +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/manage.py make_messages.run.xml +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/manage.py update_req.run.xml +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/.run/unittests ___all___.run.xml +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/AUTHORS +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/LICENSE +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/dist/.gitignore +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/admin/__init__.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/admin/base.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/admin/location.py +0 -0
- {pyinventory-0.22.1/inventory/management → pyinventory-0.23.0/inventory/admin/tests}/__init__.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/apps.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/checks.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/ckeditor_upload.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/constants.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/context_processors.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/forms.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/ca/LC_MESSAGES/django.mo +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/ca/LC_MESSAGES/django.po +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/de/LC_MESSAGES/django.mo +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/de/LC_MESSAGES/django.po +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/en/LC_MESSAGES/django.mo +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/en/LC_MESSAGES/django.po +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/es/LC_MESSAGES/django.mo +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/locale/es/LC_MESSAGES/django.po +0 -0
- {pyinventory-0.22.1/inventory/management/commands → pyinventory-0.23.0/inventory/management}/__init__.py +0 -0
- {pyinventory-0.22.1/inventory/migrations → pyinventory-0.23.0/inventory/management/commands}/__init__.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/management/commands/seed_data.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/management/commands/tree.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/middlewares.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0001_initial.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0002_auto_20201017_2211.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0003_auto_20201024_1830.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0004_item_user_images.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0005_serve_uploads_by_django_tools.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0006_refactor_image_model.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0007_add_file_attachment.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0008_last_check_datetime.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0009_add_memo.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0010_version_protect_models.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0011_parent_tree1.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0012_parent_tree2.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0013_alter_itemmodel_location.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0014_alter_itemmodel_description_and_more.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/migrations/0015_itemmaincategory_itemmodel_category.py +0 -0
- {pyinventory-0.22.1/inventory/tests/fixtures → pyinventory-0.23.0/inventory/migrations}/__init__.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/base.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/item.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/links.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/location.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/models/memo.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/parent_tree.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/permissions.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/persistent_filters.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/request_dict.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/signals.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/string_utils.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/templates/admin/item/related_items.html +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/templates/admin/location/items.html +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/__init__.py +0 -0
- {pyinventory-0.22.1/inventory_project/settings → pyinventory-0.23.0/inventory/tests/fixtures}/__init__.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/fixtures/users.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_admin_location.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_admin_location_empty_change_list_1.snapshot.html +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_item_images.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_link_model.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_management_command_seed_data.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_management_command_tree.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_parent_tree.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory/tests/test_parent_tree_model.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/__init__.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/__main__.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/middlewares.py +0 -0
- {pyinventory-0.22.1/inventory_project/tests → pyinventory-0.23.0/inventory_project/settings}/__init__.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/settings/local.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/settings/prod.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/settings/tests.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/templates/admin/base_site.html +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/templates/admin/login.html +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/fixtures.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/mocks.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/playwright_utils.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_item.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_item_login_1.snapshot.html +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_item_normal_user_create_minimal_item_1.snapshot.html +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_memo.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_memo_normal_user_create_minimal_item_1.snapshot.html +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_admin_superuser_admin_index_1.snapshot.html +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_inventory_commands.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_inventory_commands_help_1.snapshot.txt +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_migrations.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_models_item.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_playwright_admin.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_project_setup.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/tests/test_readme_history.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/urls.py +0 -0
- {pyinventory-0.22.1 → pyinventory-0.23.0}/inventory_project/wsgi.py +0 -0
- {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.
|
|
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
|
|
@@ -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(
|
|
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(
|
|
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
|
+
› <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
|
|
10
|
+
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
|
11
|
+
› {% 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/
|
|
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]
|