PyInventory 0.19.0__py3-none-any.whl
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.19.0.dist-info/AUTHORS +14 -0
- PyInventory-0.19.0.dist-info/LICENSE +674 -0
- PyInventory-0.19.0.dist-info/METADATA +347 -0
- PyInventory-0.19.0.dist-info/RECORD +101 -0
- PyInventory-0.19.0.dist-info/WHEEL +5 -0
- PyInventory-0.19.0.dist-info/entry_points.txt +2 -0
- PyInventory-0.19.0.dist-info/top_level.txt +2 -0
- inventory/__init__.py +7 -0
- inventory/admin/__init__.py +3 -0
- inventory/admin/base.py +104 -0
- inventory/admin/item.py +169 -0
- inventory/admin/location.py +78 -0
- inventory/admin/memo.py +76 -0
- inventory/admin/tagulous_fix.py +45 -0
- inventory/apps.py +18 -0
- inventory/ckeditor_upload.py +15 -0
- inventory/context_processors.py +5 -0
- inventory/forms.py +36 -0
- inventory/locale/ca/LC_MESSAGES/django.mo +0 -0
- inventory/locale/ca/LC_MESSAGES/django.po +297 -0
- inventory/locale/de/LC_MESSAGES/django.mo +0 -0
- inventory/locale/de/LC_MESSAGES/django.po +294 -0
- inventory/locale/en/LC_MESSAGES/django.mo +0 -0
- inventory/locale/en/LC_MESSAGES/django.po +294 -0
- inventory/locale/es/LC_MESSAGES/django.mo +0 -0
- inventory/locale/es/LC_MESSAGES/django.po +297 -0
- inventory/management/__init__.py +0 -0
- inventory/management/commands/__init__.py +0 -0
- inventory/management/commands/seed_data.py +135 -0
- inventory/management/commands/tree.py +62 -0
- inventory/middlewares.py +21 -0
- inventory/migrations/0001_initial.py +596 -0
- inventory/migrations/0002_auto_20201017_2211.py +87 -0
- inventory/migrations/0003_auto_20201024_1830.py +23 -0
- inventory/migrations/0004_item_user_images.py +129 -0
- inventory/migrations/0005_serve_uploads_by_django_tools.py +77 -0
- inventory/migrations/0006_refactor_image_model.py +46 -0
- inventory/migrations/0007_add_file_attachment.py +128 -0
- inventory/migrations/0008_last_check_datetime.py +23 -0
- inventory/migrations/0009_add_memo.py +517 -0
- inventory/migrations/0010_version_protect_models.py +37 -0
- inventory/migrations/0011_parent_tree1.py +97 -0
- inventory/migrations/0012_parent_tree2.py +20 -0
- inventory/migrations/0013_alter_itemmodel_location.py +26 -0
- inventory/migrations/__init__.py +0 -0
- inventory/models/__init__.py +3 -0
- inventory/models/base.py +239 -0
- inventory/models/item.py +228 -0
- inventory/models/links.py +104 -0
- inventory/models/location.py +24 -0
- inventory/models/memo.py +109 -0
- inventory/parent_tree.py +71 -0
- inventory/permissions.py +60 -0
- inventory/request_dict.py +16 -0
- inventory/signals.py +15 -0
- inventory/string_utils.py +15 -0
- inventory/templates/admin/item/related_items.html +18 -0
- inventory/templates/admin/location/items.html +18 -0
- inventory/tests/__init__.py +0 -0
- inventory/tests/fixtures/__init__.py +0 -0
- inventory/tests/fixtures/users.py +11 -0
- inventory/tests/test_admin_location.py +34 -0
- inventory/tests/test_admin_location_empty_change_list_1.snapshot.html +84 -0
- inventory/tests/test_item_images.py +76 -0
- inventory/tests/test_link_model.py +72 -0
- inventory/tests/test_management_command_seed_data.py +49 -0
- inventory/tests/test_management_command_tree.py +27 -0
- inventory/tests/test_parent_tree.py +40 -0
- inventory/tests/test_parent_tree_model.py +139 -0
- inventory_project/__init__.py +12 -0
- inventory_project/__main__.py +17 -0
- inventory_project/manage.py +41 -0
- inventory_project/middlewares.py +23 -0
- inventory_project/publish.py +21 -0
- inventory_project/settings/__init__.py +0 -0
- inventory_project/settings/local.py +74 -0
- inventory_project/settings/prod.py +393 -0
- inventory_project/settings/tests.py +45 -0
- inventory_project/templates/admin/base_site.html +22 -0
- inventory_project/templates/admin/login.html +32 -0
- inventory_project/tests/__init__.py +0 -0
- inventory_project/tests/fixtures.py +40 -0
- inventory_project/tests/mocks.py +15 -0
- inventory_project/tests/playwright_utils.py +22 -0
- inventory_project/tests/test_admin.py +15 -0
- inventory_project/tests/test_admin_item.py +240 -0
- inventory_project/tests/test_admin_item_auto_group_items_1.snapshot.html +349 -0
- inventory_project/tests/test_admin_item_auto_group_items_2.snapshot.html +232 -0
- inventory_project/tests/test_admin_item_login_1.snapshot.html +40 -0
- inventory_project/tests/test_admin_item_normal_user_create_minimal_item_1.snapshot.html +637 -0
- inventory_project/tests/test_admin_item_normal_user_create_minimal_item_2.snapshot.html +930 -0
- inventory_project/tests/test_admin_memo.py +153 -0
- inventory_project/tests/test_admin_memo_normal_user_create_minimal_item_1.snapshot.html +365 -0
- inventory_project/tests/test_command_shell_help_django4.2.3.snapshot.txt +60 -0
- inventory_project/tests/test_inventory_commands.py +26 -0
- inventory_project/tests/test_migrations.py +22 -0
- inventory_project/tests/test_models_item.py +24 -0
- inventory_project/tests/test_playwright_admin.py +157 -0
- inventory_project/tests/test_project_setup.py +102 -0
- inventory_project/urls.py +21 -0
- inventory_project/wsgi.py +9 -0
inventory/admin/item.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import tagulous
|
|
4
|
+
from adminsortable2.admin import SortableAdminMixin, SortableInlineAdminMixin
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.contrib import admin
|
|
7
|
+
from django.template.loader import render_to_string
|
|
8
|
+
from django.urls import reverse
|
|
9
|
+
from django.utils.html import format_html
|
|
10
|
+
from django.utils.translation import gettext_lazy as _
|
|
11
|
+
from import_export.admin import ImportExportMixin
|
|
12
|
+
from import_export.resources import ModelResource
|
|
13
|
+
|
|
14
|
+
from inventory.admin.base import (
|
|
15
|
+
BaseFileModelInline,
|
|
16
|
+
BaseImageModelInline,
|
|
17
|
+
BaseUserAdmin,
|
|
18
|
+
LimitTreeDepthListFilter,
|
|
19
|
+
UserInlineMixin,
|
|
20
|
+
)
|
|
21
|
+
from inventory.admin.tagulous_fix import TagulousModelAdminFix
|
|
22
|
+
from inventory.models import ItemLinkModel, ItemModel
|
|
23
|
+
from inventory.models.item import ItemFileModel, ItemImageModel
|
|
24
|
+
from inventory.string_utils import ltruncatechars
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ItemLinkModelInline(UserInlineMixin, SortableInlineAdminMixin, admin.TabularInline):
|
|
31
|
+
model = ItemLinkModel
|
|
32
|
+
extra = 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ItemImageModelInline(BaseImageModelInline):
|
|
36
|
+
model = ItemImageModel
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ItemFileModelInline(BaseFileModelInline):
|
|
40
|
+
model = ItemFileModel
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ItemModelResource(ModelResource):
|
|
44
|
+
class Meta:
|
|
45
|
+
model = ItemModel
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@admin.register(ItemModel)
|
|
49
|
+
class ItemModelAdmin(TagulousModelAdminFix, ImportExportMixin, SortableAdminMixin, BaseUserAdmin):
|
|
50
|
+
@admin.display(description=_('Related items'))
|
|
51
|
+
def related_items(self, obj):
|
|
52
|
+
if obj.pk is None:
|
|
53
|
+
# Add a new item -> there are no related items ;)
|
|
54
|
+
return '-'
|
|
55
|
+
|
|
56
|
+
related_qs = ItemModel.tree_objects.related_objects(instance=obj)
|
|
57
|
+
context = {
|
|
58
|
+
'items': related_qs,
|
|
59
|
+
'opts': self.opts,
|
|
60
|
+
}
|
|
61
|
+
return render_to_string('admin/item/related_items.html', context)
|
|
62
|
+
|
|
63
|
+
@admin.display(ordering='path_str', description=_('ItemModel.verbose_name'))
|
|
64
|
+
def item(self, obj):
|
|
65
|
+
path = obj.path
|
|
66
|
+
if len(path) > 1:
|
|
67
|
+
prefixes = ' › '.join(path[:-1] + [''])
|
|
68
|
+
prefixes = ltruncatechars(prefixes, max_length=settings.TREE_PATH_STR_MAX_LENGTH)
|
|
69
|
+
else:
|
|
70
|
+
prefixes = ''
|
|
71
|
+
item = path[-1]
|
|
72
|
+
url = reverse('admin:inventory_itemmodel_change', args=[obj.pk])
|
|
73
|
+
return format_html(
|
|
74
|
+
'<a href="{}">{}<strong>{}</strong></a>',
|
|
75
|
+
url,
|
|
76
|
+
prefixes,
|
|
77
|
+
item,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def get_queryset(self, request):
|
|
81
|
+
qs = super().get_queryset(request)
|
|
82
|
+
qs = qs.prefetch_related(
|
|
83
|
+
'location',
|
|
84
|
+
'kind',
|
|
85
|
+
'producer',
|
|
86
|
+
)
|
|
87
|
+
return qs
|
|
88
|
+
|
|
89
|
+
def get_max_order(self, request, obj=None):
|
|
90
|
+
# Work-a-round for: https://github.com/jrief/django-admin-sortable2/issues/341
|
|
91
|
+
return 0
|
|
92
|
+
|
|
93
|
+
date_hierarchy = 'create_dt'
|
|
94
|
+
list_display = ('producer', 'item', 'kind', 'location', 'received_date', 'update_dt')
|
|
95
|
+
ordering = ('path_str',)
|
|
96
|
+
list_display_links = ()
|
|
97
|
+
list_filter = (LimitTreeDepthListFilter, 'kind', 'location', 'producer', 'tags')
|
|
98
|
+
search_fields = ('name', 'description', 'kind__name', 'tags__name')
|
|
99
|
+
fieldsets = (
|
|
100
|
+
(
|
|
101
|
+
_('Internals'),
|
|
102
|
+
{
|
|
103
|
+
'classes': ('collapse',),
|
|
104
|
+
'fields': (
|
|
105
|
+
('id', 'version'),
|
|
106
|
+
'user',
|
|
107
|
+
),
|
|
108
|
+
},
|
|
109
|
+
),
|
|
110
|
+
(_('Meta'), {'classes': ('collapse',), 'fields': ('create_dt', 'update_dt')}),
|
|
111
|
+
(
|
|
112
|
+
_('Basic'),
|
|
113
|
+
{
|
|
114
|
+
'fields': (
|
|
115
|
+
'kind',
|
|
116
|
+
('producer', 'name'),
|
|
117
|
+
'description',
|
|
118
|
+
'tags',
|
|
119
|
+
'fcc_id',
|
|
120
|
+
'parent',
|
|
121
|
+
'location',
|
|
122
|
+
)
|
|
123
|
+
},
|
|
124
|
+
),
|
|
125
|
+
(_('Related items'), {'classes': ('collapse',), 'fields': ('related_items',)}),
|
|
126
|
+
(
|
|
127
|
+
_('Lent'),
|
|
128
|
+
{
|
|
129
|
+
'classes': ('collapse',),
|
|
130
|
+
'fields': (
|
|
131
|
+
'lent_to',
|
|
132
|
+
(
|
|
133
|
+
'lent_from_date',
|
|
134
|
+
'lent_until_date',
|
|
135
|
+
),
|
|
136
|
+
),
|
|
137
|
+
},
|
|
138
|
+
),
|
|
139
|
+
(
|
|
140
|
+
_('Received'),
|
|
141
|
+
{
|
|
142
|
+
'classes': ('collapse',),
|
|
143
|
+
'fields': (('received_from', 'received_date', 'received_price'),),
|
|
144
|
+
},
|
|
145
|
+
),
|
|
146
|
+
(
|
|
147
|
+
_('Handed over'),
|
|
148
|
+
{
|
|
149
|
+
'classes': ('collapse',),
|
|
150
|
+
'fields': (('handed_over_to', 'handed_over_date', 'handed_over_price'),),
|
|
151
|
+
},
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
autocomplete_fields = ('parent', 'location')
|
|
155
|
+
readonly_fields = ('id', 'create_dt', 'update_dt', 'user', 'related_items')
|
|
156
|
+
inlines = (ItemImageModelInline, ItemFileModelInline, ItemLinkModelInline)
|
|
157
|
+
|
|
158
|
+
def get_list_display(self, request):
|
|
159
|
+
list_display = list(super().get_list_display(request))
|
|
160
|
+
|
|
161
|
+
# FIXME: SortableAdminMixin.get_list_display() adds this, we didn't need here:
|
|
162
|
+
# See: https://github.com/jrief/django-admin-sortable2/issues/363
|
|
163
|
+
if '_reorder_' in list_display:
|
|
164
|
+
list_display.remove('_reorder_')
|
|
165
|
+
|
|
166
|
+
return list_display
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
tagulous.admin.enhance(ItemModel, ItemModelAdmin)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.contrib import admin
|
|
3
|
+
from django.db.models import Count
|
|
4
|
+
from django.db.models.options import Options
|
|
5
|
+
from django.template.loader import render_to_string
|
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
|
7
|
+
from import_export.admin import ImportExportMixin
|
|
8
|
+
from import_export.resources import ModelResource
|
|
9
|
+
|
|
10
|
+
from inventory.admin.base import BaseUserAdmin, LimitTreeDepthListFilter
|
|
11
|
+
from inventory.models import ItemModel, LocationModel
|
|
12
|
+
from inventory.string_utils import ltruncatechars
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class LocationModelResource(ModelResource):
|
|
16
|
+
class Meta:
|
|
17
|
+
model = LocationModel
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@admin.register(LocationModel)
|
|
21
|
+
class LocationModelAdmin(ImportExportMixin, BaseUserAdmin):
|
|
22
|
+
@admin.display(ordering='item_count', description=_('ItemModel.verbose_name_plural'))
|
|
23
|
+
def item_count(self, obj):
|
|
24
|
+
return obj.item_count
|
|
25
|
+
|
|
26
|
+
@admin.display(description=_('ItemModel.verbose_name_plural'))
|
|
27
|
+
def items(self, obj):
|
|
28
|
+
item_qs = ItemModel.objects.filter(location=obj)
|
|
29
|
+
opts: Options = ItemModel._meta
|
|
30
|
+
context = {
|
|
31
|
+
'items': item_qs,
|
|
32
|
+
'opts': opts,
|
|
33
|
+
}
|
|
34
|
+
return render_to_string('admin/location/items.html', context)
|
|
35
|
+
|
|
36
|
+
@admin.display(ordering='path_str', description=_('LocationModel.verbose_name'))
|
|
37
|
+
def location(self, obj):
|
|
38
|
+
text = ' › '.join(obj.path)
|
|
39
|
+
text = ltruncatechars(text, max_length=settings.TREE_PATH_STR_MAX_LENGTH)
|
|
40
|
+
return text
|
|
41
|
+
|
|
42
|
+
def get_queryset(self, request):
|
|
43
|
+
qs = super().get_queryset(request)
|
|
44
|
+
qs = qs.annotate(item_count=Count('items'))
|
|
45
|
+
return qs
|
|
46
|
+
|
|
47
|
+
list_display = ('location', 'create_dt', 'update_dt', 'item_count')
|
|
48
|
+
fieldsets = (
|
|
49
|
+
(
|
|
50
|
+
_('Internals'),
|
|
51
|
+
{
|
|
52
|
+
'classes': ('collapse',),
|
|
53
|
+
'fields': (
|
|
54
|
+
('id', 'version'),
|
|
55
|
+
'user',
|
|
56
|
+
),
|
|
57
|
+
},
|
|
58
|
+
),
|
|
59
|
+
(_('Meta'), {'classes': ('collapse',), 'fields': ('create_dt', 'update_dt')}),
|
|
60
|
+
(
|
|
61
|
+
_('Basic'),
|
|
62
|
+
{
|
|
63
|
+
'fields': (
|
|
64
|
+
'name',
|
|
65
|
+
'description',
|
|
66
|
+
'tags',
|
|
67
|
+
'parent',
|
|
68
|
+
)
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
(_('Items in this Location'), {'fields': ('items',)}),
|
|
72
|
+
)
|
|
73
|
+
autocomplete_fields = ('parent',)
|
|
74
|
+
readonly_fields = ('id', 'create_dt', 'update_dt', 'user', 'item_count', 'items')
|
|
75
|
+
list_display_links = ('location',)
|
|
76
|
+
list_filter = (LimitTreeDepthListFilter,)
|
|
77
|
+
search_fields = ('name', 'description', 'tags__name')
|
|
78
|
+
ordering = ('path_str',)
|
inventory/admin/memo.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import tagulous
|
|
4
|
+
from adminsortable2.admin import SortableAdminMixin, SortableInlineAdminMixin
|
|
5
|
+
from django.contrib import admin
|
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
|
7
|
+
from import_export.admin import ImportExportMixin
|
|
8
|
+
from import_export.resources import ModelResource
|
|
9
|
+
|
|
10
|
+
from inventory.admin.base import BaseFileModelInline, BaseImageModelInline, BaseUserAdmin, UserInlineMixin
|
|
11
|
+
from inventory.admin.tagulous_fix import TagulousModelAdminFix
|
|
12
|
+
from inventory.models import MemoLinkModel, MemoModel
|
|
13
|
+
from inventory.models.memo import MemoFileModel, MemoImageModel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MemoLinkModelInline(UserInlineMixin, SortableInlineAdminMixin, admin.TabularInline):
|
|
20
|
+
model = MemoLinkModel
|
|
21
|
+
extra = 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class MemoImageModelInline(BaseImageModelInline):
|
|
25
|
+
model = MemoImageModel
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MemoFileModelInline(BaseFileModelInline):
|
|
29
|
+
model = MemoFileModel
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MemoModelResource(ModelResource):
|
|
33
|
+
class Meta:
|
|
34
|
+
model = MemoModel
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@admin.register(MemoModel)
|
|
38
|
+
class MemoModelAdmin(TagulousModelAdminFix, ImportExportMixin, SortableAdminMixin, BaseUserAdmin):
|
|
39
|
+
def get_max_order(self, request, obj=None):
|
|
40
|
+
# Work-a-round for: https://github.com/jrief/django-admin-sortable2/issues/341
|
|
41
|
+
return 0
|
|
42
|
+
|
|
43
|
+
date_hierarchy = 'create_dt'
|
|
44
|
+
list_display = ('name', 'update_dt')
|
|
45
|
+
ordering = ('-update_dt',)
|
|
46
|
+
list_display_links = ('name',)
|
|
47
|
+
list_filter = ('tags',)
|
|
48
|
+
search_fields = ('name', 'memo', 'tags__name')
|
|
49
|
+
fieldsets = (
|
|
50
|
+
(
|
|
51
|
+
_('Internals'),
|
|
52
|
+
{
|
|
53
|
+
'classes': ('collapse',),
|
|
54
|
+
'fields': (
|
|
55
|
+
('id', 'version'),
|
|
56
|
+
'user',
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
),
|
|
60
|
+
(_('Meta'), {'classes': ('collapse',), 'fields': ('create_dt', 'update_dt')}),
|
|
61
|
+
(
|
|
62
|
+
_('Basic'),
|
|
63
|
+
{
|
|
64
|
+
'fields': (
|
|
65
|
+
'name',
|
|
66
|
+
'memo',
|
|
67
|
+
'tags',
|
|
68
|
+
)
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
readonly_fields = ('id', 'create_dt', 'update_dt', 'user')
|
|
73
|
+
inlines = (MemoImageModelInline, MemoFileModelInline, MemoLinkModelInline)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
tagulous.admin.enhance(MemoModel, MemoModelAdmin)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Work-a-round for:
|
|
3
|
+
https://github.com/radiac/django-tagulous/issues/164
|
|
4
|
+
"""
|
|
5
|
+
from django import forms
|
|
6
|
+
from django.contrib.admin.widgets import AutocompleteMixin
|
|
7
|
+
from tagulous import settings as tagulous_settings
|
|
8
|
+
from tagulous.forms import AdminTagWidget, BaseTagField
|
|
9
|
+
from tagulous.models import SingleTagField, TagField
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AdminTagWidget2(AdminTagWidget):
|
|
13
|
+
@property
|
|
14
|
+
def media(self):
|
|
15
|
+
# Get the media from the AutocompleteMixin - this will give us Django's
|
|
16
|
+
# vendor jQuery and select2
|
|
17
|
+
class GetMedia(AutocompleteMixin, forms.Select):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
dependency_media = GetMedia(None, None).media
|
|
21
|
+
tagulous_media = forms.Media(
|
|
22
|
+
js=tagulous_settings.ADMIN_AUTOCOMPLETE_JS,
|
|
23
|
+
css=tagulous_settings.ADMIN_AUTOCOMPLETE_CSS,
|
|
24
|
+
)
|
|
25
|
+
all_media = dependency_media + tagulous_media
|
|
26
|
+
|
|
27
|
+
return all_media
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseTagField2(BaseTagField):
|
|
31
|
+
widget = AdminTagWidget2
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TagulousModelAdminFix:
|
|
35
|
+
def __init__(self, *args, **kwargs):
|
|
36
|
+
super().__init__(*args, **kwargs)
|
|
37
|
+
|
|
38
|
+
self.formfield_overrides[SingleTagField] = {
|
|
39
|
+
'form_class': BaseTagField2,
|
|
40
|
+
'widget': AdminTagWidget2,
|
|
41
|
+
}
|
|
42
|
+
self.formfield_overrides[TagField] = {
|
|
43
|
+
'form_class': BaseTagField2,
|
|
44
|
+
'widget': AdminTagWidget2,
|
|
45
|
+
}
|
inventory/apps.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
https://docs.djangoproject.com/en/2.0/ref/applications/#configuring-applications-ref
|
|
3
|
+
|
|
4
|
+
created 14.20.2020 by Jens Diemer <opensource@jensdiemer.de>
|
|
5
|
+
:copyleft: 2020 by the PyInventory team, see AUTHORS for more details.
|
|
6
|
+
:license: GNU GPL v3 or above, see LICENSE for more details.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from django.apps import AppConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InventoryConfig(AppConfig):
|
|
14
|
+
name = "inventory"
|
|
15
|
+
verbose_name = "Inventory"
|
|
16
|
+
|
|
17
|
+
def ready(self):
|
|
18
|
+
import inventory.signals # noqa
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from bx_django_utils.filename import clean_filename
|
|
4
|
+
from django.utils.crypto import get_random_string
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_filename(filename, request):
|
|
11
|
+
random_string = get_random_string()
|
|
12
|
+
filename = clean_filename(filename)
|
|
13
|
+
filename = f'{random_string}/{filename}'
|
|
14
|
+
logger.info(f'Upload filename: {filename!r}')
|
|
15
|
+
return filename
|
inventory/forms.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from django import forms
|
|
2
|
+
from django.core.exceptions import FieldDoesNotExist
|
|
3
|
+
|
|
4
|
+
from inventory.request_dict import get_request_dict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OnlyUserRelationsModelForm(forms.ModelForm):
|
|
8
|
+
def __init__(self, *args, **kwargs):
|
|
9
|
+
super().__init__(*args, **kwargs)
|
|
10
|
+
|
|
11
|
+
# Filter all related fields that has a "user" attribute for the current user
|
|
12
|
+
# e.g.:
|
|
13
|
+
# The user should only select his own "location" and "items"
|
|
14
|
+
|
|
15
|
+
user = get_request_dict()['user'] # get current user via threading.local()
|
|
16
|
+
for formfield in self.fields.values():
|
|
17
|
+
if not hasattr(formfield, 'queryset'):
|
|
18
|
+
continue
|
|
19
|
+
|
|
20
|
+
queryset = formfield.queryset
|
|
21
|
+
opts = queryset.model._meta
|
|
22
|
+
try:
|
|
23
|
+
opts.get_field('user')
|
|
24
|
+
except FieldDoesNotExist:
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
formfield.queryset = queryset.filter(user=user)
|
|
28
|
+
|
|
29
|
+
def save(self, commit=True):
|
|
30
|
+
instance = super().save(commit=False)
|
|
31
|
+
if instance.user_id is None:
|
|
32
|
+
user = get_request_dict()['user'] # get current user via threading.local()
|
|
33
|
+
instance.user_id = user.pk
|
|
34
|
+
|
|
35
|
+
instance.save()
|
|
36
|
+
return instance
|
|
Binary file
|