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.
Files changed (101) hide show
  1. PyInventory-0.19.0.dist-info/AUTHORS +14 -0
  2. PyInventory-0.19.0.dist-info/LICENSE +674 -0
  3. PyInventory-0.19.0.dist-info/METADATA +347 -0
  4. PyInventory-0.19.0.dist-info/RECORD +101 -0
  5. PyInventory-0.19.0.dist-info/WHEEL +5 -0
  6. PyInventory-0.19.0.dist-info/entry_points.txt +2 -0
  7. PyInventory-0.19.0.dist-info/top_level.txt +2 -0
  8. inventory/__init__.py +7 -0
  9. inventory/admin/__init__.py +3 -0
  10. inventory/admin/base.py +104 -0
  11. inventory/admin/item.py +169 -0
  12. inventory/admin/location.py +78 -0
  13. inventory/admin/memo.py +76 -0
  14. inventory/admin/tagulous_fix.py +45 -0
  15. inventory/apps.py +18 -0
  16. inventory/ckeditor_upload.py +15 -0
  17. inventory/context_processors.py +5 -0
  18. inventory/forms.py +36 -0
  19. inventory/locale/ca/LC_MESSAGES/django.mo +0 -0
  20. inventory/locale/ca/LC_MESSAGES/django.po +297 -0
  21. inventory/locale/de/LC_MESSAGES/django.mo +0 -0
  22. inventory/locale/de/LC_MESSAGES/django.po +294 -0
  23. inventory/locale/en/LC_MESSAGES/django.mo +0 -0
  24. inventory/locale/en/LC_MESSAGES/django.po +294 -0
  25. inventory/locale/es/LC_MESSAGES/django.mo +0 -0
  26. inventory/locale/es/LC_MESSAGES/django.po +297 -0
  27. inventory/management/__init__.py +0 -0
  28. inventory/management/commands/__init__.py +0 -0
  29. inventory/management/commands/seed_data.py +135 -0
  30. inventory/management/commands/tree.py +62 -0
  31. inventory/middlewares.py +21 -0
  32. inventory/migrations/0001_initial.py +596 -0
  33. inventory/migrations/0002_auto_20201017_2211.py +87 -0
  34. inventory/migrations/0003_auto_20201024_1830.py +23 -0
  35. inventory/migrations/0004_item_user_images.py +129 -0
  36. inventory/migrations/0005_serve_uploads_by_django_tools.py +77 -0
  37. inventory/migrations/0006_refactor_image_model.py +46 -0
  38. inventory/migrations/0007_add_file_attachment.py +128 -0
  39. inventory/migrations/0008_last_check_datetime.py +23 -0
  40. inventory/migrations/0009_add_memo.py +517 -0
  41. inventory/migrations/0010_version_protect_models.py +37 -0
  42. inventory/migrations/0011_parent_tree1.py +97 -0
  43. inventory/migrations/0012_parent_tree2.py +20 -0
  44. inventory/migrations/0013_alter_itemmodel_location.py +26 -0
  45. inventory/migrations/__init__.py +0 -0
  46. inventory/models/__init__.py +3 -0
  47. inventory/models/base.py +239 -0
  48. inventory/models/item.py +228 -0
  49. inventory/models/links.py +104 -0
  50. inventory/models/location.py +24 -0
  51. inventory/models/memo.py +109 -0
  52. inventory/parent_tree.py +71 -0
  53. inventory/permissions.py +60 -0
  54. inventory/request_dict.py +16 -0
  55. inventory/signals.py +15 -0
  56. inventory/string_utils.py +15 -0
  57. inventory/templates/admin/item/related_items.html +18 -0
  58. inventory/templates/admin/location/items.html +18 -0
  59. inventory/tests/__init__.py +0 -0
  60. inventory/tests/fixtures/__init__.py +0 -0
  61. inventory/tests/fixtures/users.py +11 -0
  62. inventory/tests/test_admin_location.py +34 -0
  63. inventory/tests/test_admin_location_empty_change_list_1.snapshot.html +84 -0
  64. inventory/tests/test_item_images.py +76 -0
  65. inventory/tests/test_link_model.py +72 -0
  66. inventory/tests/test_management_command_seed_data.py +49 -0
  67. inventory/tests/test_management_command_tree.py +27 -0
  68. inventory/tests/test_parent_tree.py +40 -0
  69. inventory/tests/test_parent_tree_model.py +139 -0
  70. inventory_project/__init__.py +12 -0
  71. inventory_project/__main__.py +17 -0
  72. inventory_project/manage.py +41 -0
  73. inventory_project/middlewares.py +23 -0
  74. inventory_project/publish.py +21 -0
  75. inventory_project/settings/__init__.py +0 -0
  76. inventory_project/settings/local.py +74 -0
  77. inventory_project/settings/prod.py +393 -0
  78. inventory_project/settings/tests.py +45 -0
  79. inventory_project/templates/admin/base_site.html +22 -0
  80. inventory_project/templates/admin/login.html +32 -0
  81. inventory_project/tests/__init__.py +0 -0
  82. inventory_project/tests/fixtures.py +40 -0
  83. inventory_project/tests/mocks.py +15 -0
  84. inventory_project/tests/playwright_utils.py +22 -0
  85. inventory_project/tests/test_admin.py +15 -0
  86. inventory_project/tests/test_admin_item.py +240 -0
  87. inventory_project/tests/test_admin_item_auto_group_items_1.snapshot.html +349 -0
  88. inventory_project/tests/test_admin_item_auto_group_items_2.snapshot.html +232 -0
  89. inventory_project/tests/test_admin_item_login_1.snapshot.html +40 -0
  90. inventory_project/tests/test_admin_item_normal_user_create_minimal_item_1.snapshot.html +637 -0
  91. inventory_project/tests/test_admin_item_normal_user_create_minimal_item_2.snapshot.html +930 -0
  92. inventory_project/tests/test_admin_memo.py +153 -0
  93. inventory_project/tests/test_admin_memo_normal_user_create_minimal_item_1.snapshot.html +365 -0
  94. inventory_project/tests/test_command_shell_help_django4.2.3.snapshot.txt +60 -0
  95. inventory_project/tests/test_inventory_commands.py +26 -0
  96. inventory_project/tests/test_migrations.py +22 -0
  97. inventory_project/tests/test_models_item.py +24 -0
  98. inventory_project/tests/test_playwright_admin.py +157 -0
  99. inventory_project/tests/test_project_setup.py +102 -0
  100. inventory_project/urls.py +21 -0
  101. inventory_project/wsgi.py +9 -0
@@ -0,0 +1,153 @@
1
+ from unittest import mock
2
+
3
+ from bx_django_utils.test_utils.html_assertion import HtmlAssertionMixin, assert_html_response_snapshot
4
+ from django.contrib.auth.models import User
5
+ from django.template.defaulttags import CsrfTokenNode, NowNode
6
+ from django.test import TestCase, override_settings
7
+ from django_tools.unittest_utils.mockup import ImageDummy
8
+ from model_bakery import baker
9
+ from override_storage import locmem_stats_override_storage
10
+ from reversion.models import Revision
11
+
12
+ from inventory.models import MemoImageModel, MemoModel
13
+ from inventory.permissions import get_or_create_normal_user_group
14
+ from inventory_project.tests.mocks import MockInventoryVersionString
15
+
16
+
17
+ class AdminAnonymousTests(TestCase):
18
+ def test_login(self):
19
+ response = self.client.get('/admin/inventory/memomodel/add/', secure=True, HTTP_ACCEPT_LANGUAGE='en')
20
+ self.assertRedirects(
21
+ response,
22
+ expected_url='/admin/login/?next=/admin/inventory/memomodel/add/',
23
+ fetch_redirect_response=False,
24
+ )
25
+
26
+
27
+ @override_settings(SECURE_SSL_REDIRECT=False)
28
+ class AdminTestCase(HtmlAssertionMixin, TestCase):
29
+ @classmethod
30
+ def setUpTestData(cls):
31
+ cls.normaluser = baker.make(User, username='NormalUser', is_staff=True, is_active=True, is_superuser=False)
32
+ assert cls.normaluser.user_permissions.count() == 0
33
+ group = get_or_create_normal_user_group()[0]
34
+ cls.normaluser.groups.set([group])
35
+
36
+ def test_normal_user_create_minimal_item(self):
37
+ self.client.force_login(self.normaluser)
38
+
39
+ with mock.patch.object(NowNode, 'render', return_value='MockedNowNode'), mock.patch.object(
40
+ CsrfTokenNode, 'render', return_value='MockedCsrfTokenNode'
41
+ ), MockInventoryVersionString():
42
+ response = self.client.get('/admin/inventory/memomodel/add/')
43
+ assert response.status_code == 200
44
+ self.assert_html_parts(response, parts=('<title>Add Memo | PyInventory vMockedVersionString</title>',))
45
+ assert_html_response_snapshot(response=response, validate=False)
46
+
47
+ assert MemoModel.objects.count() == 0
48
+
49
+ response = self.client.post(
50
+ path='/admin/inventory/memomodel/add/',
51
+ data={
52
+ 'version': 0, # VersionProtectBaseModel field
53
+ 'name': 'The Memo Name',
54
+ 'memo': 'This is a test Memo',
55
+ 'memoimagemodel_set-TOTAL_FORMS': '0',
56
+ 'memoimagemodel_set-INITIAL_FORMS': '0',
57
+ 'memoimagemodel_set-MIN_NUM_FORMS': '0',
58
+ 'memoimagemodel_set-MAX_NUM_FORMS': '1000',
59
+ 'memoimagemodel_set-__prefix__-position': '0',
60
+ 'memofilemodel_set-TOTAL_FORMS': '0',
61
+ 'memofilemodel_set-INITIAL_FORMS': '0',
62
+ 'memofilemodel_set-MIN_NUM_FORMS': '0',
63
+ 'memofilemodel_set-MAX_NUM_FORMS': '1000',
64
+ 'memofilemodel_set-__prefix__-position': '0',
65
+ 'memolinkmodel_set-TOTAL_FORMS': '0',
66
+ 'memolinkmodel_set-INITIAL_FORMS': '0',
67
+ 'memolinkmodel_set-MIN_NUM_FORMS': '0',
68
+ 'memolinkmodel_set-MAX_NUM_FORMS': '1000',
69
+ 'memolinkmodel_set-__prefix__-position': '0',
70
+ '_save': 'Save',
71
+ },
72
+ )
73
+ assert response.status_code == 302, response.content.decode('utf-8') # Form error?
74
+ self.assertRedirects(response, expected_url='/admin/inventory/memomodel/')
75
+
76
+ data = list(MemoModel.objects.values_list('name', 'memo'))
77
+ assert data == [('The Memo Name', 'This is a test Memo')]
78
+
79
+ item = MemoModel.objects.first()
80
+
81
+ self.assert_messages(
82
+ response,
83
+ expected_messages=[
84
+ f'The Memo “<a href="/admin/inventory/memomodel/{item.pk}/change/">The Memo Name</a>”'
85
+ ' was added successfully.'
86
+ ],
87
+ )
88
+
89
+ assert item.user_id == self.normaluser.pk
90
+
91
+ # django-revision integration:
92
+ comments = list(Revision.objects.order_by('date_created').values_list('comment', flat=True))
93
+ self.assertEqual(comments, ['Added.'])
94
+
95
+ def test_new_item_with_image(self):
96
+ """
97
+ https://github.com/jedie/PyInventory/issues/33
98
+ """
99
+ self.client.force_login(self.normaluser)
100
+
101
+ img = ImageDummy(width=1, height=1, format='png').in_memory_image_file(filename='test.png')
102
+
103
+ with locmem_stats_override_storage() as storage_stats:
104
+ response = self.client.post(
105
+ path='/admin/inventory/memomodel/add/',
106
+ data={
107
+ 'version': 0, # VersionProtectBaseModel field
108
+ 'name': 'The Memo Name',
109
+ 'memo': 'This is a test Memo',
110
+ 'memoimagemodel_set-TOTAL_FORMS': '1',
111
+ 'memoimagemodel_set-INITIAL_FORMS': '0',
112
+ 'memoimagemodel_set-MIN_NUM_FORMS': '0',
113
+ 'memoimagemodel_set-MAX_NUM_FORMS': '1000',
114
+ 'memoimagemodel_set-0-position': '0',
115
+ 'memoimagemodel_set-__prefix__-position': '0',
116
+ 'memoimagemodel_set-0-image': img,
117
+ 'memofilemodel_set-TOTAL_FORMS': '0',
118
+ 'memofilemodel_set-INITIAL_FORMS': '0',
119
+ 'memofilemodel_set-MIN_NUM_FORMS': '0',
120
+ 'memofilemodel_set-MAX_NUM_FORMS': '1000',
121
+ 'memofilemodel_set-__prefix__-position': '0',
122
+ 'memolinkmodel_set-TOTAL_FORMS': '0',
123
+ 'memolinkmodel_set-INITIAL_FORMS': '0',
124
+ 'memolinkmodel_set-MIN_NUM_FORMS': '0',
125
+ 'memolinkmodel_set-MAX_NUM_FORMS': '1000',
126
+ 'memolinkmodel_set-__prefix__-position': '0',
127
+ '_save': 'Save',
128
+ },
129
+ )
130
+ assert response.status_code == 302, response.content.decode('utf-8') # Form error?
131
+ memo = MemoModel.objects.first() or MemoModel()
132
+ self.assert_messages(
133
+ response,
134
+ expected_messages=[
135
+ f'The Memo “<a href="/admin/inventory/memomodel/{memo.pk}/change/">The Memo Name</a>”'
136
+ ' was added successfully.'
137
+ ],
138
+ )
139
+ self.assertRedirects(response, expected_url='/admin/inventory/memomodel/')
140
+
141
+ data = list(MemoModel.objects.values_list('name', 'memo'))
142
+ assert data == [('The Memo Name', 'This is a test Memo')]
143
+
144
+ assert memo.user_id == self.normaluser.pk
145
+
146
+ assert MemoImageModel.objects.count() == 1
147
+ image = MemoImageModel.objects.first()
148
+ assert image.name == 'test.png'
149
+ assert image.memo == memo
150
+ assert image.user_id == self.normaluser.pk
151
+
152
+ # Test image file should be stored:
153
+ self.assertEqual(storage_stats.save_cnt, 1)
@@ -0,0 +1,365 @@
1
+ <div class="colM" id="content">
2
+ <h1>
3
+ Add Memo
4
+ </h1>
5
+ <div id="content-main">
6
+ <form enctype="multipart/form-data" id="memomodel_form" method="post" novalidate="">
7
+ MockedCsrfTokenNode
8
+ <div>
9
+ <fieldset class="module aligned collapse">
10
+ <h2>
11
+ Internals
12
+ </h2>
13
+ <div class="form-row field-id field-version">
14
+ <div class="flex-container form-multiline">
15
+ <div>
16
+ <div class="flex-container fieldBox field-id">
17
+ <label>
18
+ ID:
19
+ </label>
20
+ <div class="readonly">
21
+ -
22
+ </div>
23
+ </div>
24
+ <div class="help">
25
+ <div>
26
+ ID
27
+ </div>
28
+ </div>
29
+ </div>
30
+ <div>
31
+ <div class="flex-container fieldBox field-version">
32
+ <label class="required inline" for="id_version">
33
+ Version:
34
+ </label>
35
+ <input id="id_version" name="version" required="" type="hidden" value="0"/>
36
+ 0
37
+ </div>
38
+ <div class="help" id="id_version_helptext">
39
+ <div>
40
+ Internal version number of this entry. Used to protect the overwriting of an older entry.
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ <div class="form-row field-user">
47
+ <div>
48
+ <div class="flex-container">
49
+ <label>
50
+ User:
51
+ </label>
52
+ <div class="readonly">
53
+ -
54
+ </div>
55
+ </div>
56
+ <div class="help">
57
+ <div>
58
+ The user who is the owner of this entry and can manage it (will be set automatically)
59
+ </div>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </fieldset>
64
+ <fieldset class="module aligned collapse">
65
+ <h2>
66
+ Meta
67
+ </h2>
68
+ <div class="form-row field-create_dt">
69
+ <div>
70
+ <div class="flex-container">
71
+ <label>
72
+ Create date:
73
+ </label>
74
+ <div class="readonly">
75
+ -
76
+ </div>
77
+ </div>
78
+ <div class="help">
79
+ <div>
80
+ (will be set automatically)
81
+ </div>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ <div class="form-row field-update_dt">
86
+ <div>
87
+ <div class="flex-container">
88
+ <label>
89
+ Last update:
90
+ </label>
91
+ <div class="readonly">
92
+ -
93
+ </div>
94
+ </div>
95
+ <div class="help">
96
+ <div>
97
+ (will be set automatically)
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </div>
102
+ </fieldset>
103
+ <fieldset class="module aligned">
104
+ <h2>
105
+ Basic
106
+ </h2>
107
+ <div class="form-row field-name">
108
+ <div>
109
+ <div class="flex-container">
110
+ <label class="required" for="id_name">
111
+ Name:
112
+ </label>
113
+ <input class="vTextField" id="id_name" maxlength="255" name="name" required="" type="text"/>
114
+ </div>
115
+ <div class="help" id="id_name_helptext">
116
+ <div>
117
+ Name
118
+ </div>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ <div class="form-row field-memo">
123
+ <div>
124
+ <div class="flex-container">
125
+ <label for="id_memo">
126
+ Description:
127
+ </label>
128
+ <div class="django-ckeditor-widget" data-field-id="id_memo" style="display: inline-block;">
129
+ <textarea cols="40" data-config='{"skin": "moono-lisa", "toolbar_Basic": [["Source", "-", "Bold", "Italic"]], "toolbar_Full": [["Styles", "Format", "Bold", "Italic", "Underline", "Strike", "SpellChecker", "Undo", "Redo"], ["Link", "Unlink", "Anchor"], ["Image", "Flash", "Table", "HorizontalRule"], ["TextColor", "BGColor"], ["Smiley", "SpecialChar"], ["Source"]], "toolbar": "PyInventoryToolbarConfig", "height": "25em", "width": "100%", "filebrowserWindowWidth": 940, "filebrowserWindowHeight": 725, "removeButtons": "Language,Cut,Copy,Paste,Undo,Redo,Anchor", "removePlugins": ["a11yhelp", "adobeair", "ajax", "autoembed", "autolink", "bbcode", "bidi", "clipboard", "codesnippet", "codesnippetgeshi", "contextmenu", "copyformatting", "devtools", "dialog", "dialogadvtab", "div", "divarea", "docprops", "embed", "embedbase", "embedsemantic", "enterkey", "exportpdf", "find", "flash", "forms", "htmlwriter", "iframe", "iframedialog", "language", "magicline", "mathjax", "newpage", "notification", "notificationaggregator", "pagebreak", "pastefromgdocs", "pastefromword", "pastetext", "pastetools", "placeholder", "preview", "print", "save", "scayt", "selectall", "sharedspace", "smiley", "sourcedialog", "specialchar", "stylescombo", "stylesheetparser", "tab", "templates", "uicolor", "widget", "wsc", "xml"], "toolbar_PyInventoryToolbarConfig": [{"name": "basicstyles", "items": ["Bold", "Italic", "Underline", "Strike", "-", "RemoveFormat"]}, {"name": "paragraph", "items": ["NumberedList", "BulletedList", "-", "Outdent", "Indent", "-", "Blockquote", "-", "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock"]}, {"name": "links", "items": ["Link", "Unlink", "Anchor"]}, {"name": "insert", "items": ["Image", "Table", "HorizontalRule"]}, "/", {"name": "styles", "items": ["Styles", "Format", "Font", "FontSize"]}, {"name": "colors", "items": ["TextColor", "BGColor"]}, {"name": "tools", "items": ["Maximize", "ShowBlocks", "Source"]}, {"name": "about", "items": ["About"]}], "filebrowserUploadUrl": "/ckeditor/upload/", "filebrowserBrowseUrl": "/ckeditor/browse/", "language": "en"}' data-external-plugin-resources="[]" data-id="id_memo" data-processed="0" data-type="ckeditortype" id="id_memo" name="memo" rows="10"></textarea>
130
+ </div>
131
+ </div>
132
+ <div class="help" id="id_memo_helptext">
133
+ <div>
134
+ </div>
135
+ </div>
136
+ </div>
137
+ </div>
138
+ <div class="form-row field-tags">
139
+ <div>
140
+ <div class="flex-container">
141
+ <label for="id_tags">
142
+ Tags:
143
+ </label>
144
+ <div class="related-widget-wrapper" data-model-ref="Memo Tags">
145
+ <input autocomplete="off" data-tag-list="[]" data-tag-options='{"case_sensitive": false, "force_lowercase": false, "max_count": 10, "space_delimiter": false, "required": false}' data-tagulous="true" data-theme="admin-autocomplete" id="id_tags" name="tags" type="text"/>
146
+ </div>
147
+ </div>
148
+ <div class="help" id="id_tags_helptext">
149
+ <div>
150
+ </div>
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </fieldset>
155
+ <div class="js-inline-admin-formset inline-group" data-inline-formset='{"name": "#memoimagemodel_set", "options": {"prefix": "memoimagemodel_set", "addText": "Add another Image", "deleteText": "Remove"}}' data-inline-type="tabular" id="memoimagemodel_set-group">
156
+ <div class="tabular inline-related">
157
+ <input id="id_memoimagemodel_set-TOTAL_FORMS" name="memoimagemodel_set-TOTAL_FORMS" type="hidden" value="0"/>
158
+ <input id="id_memoimagemodel_set-INITIAL_FORMS" name="memoimagemodel_set-INITIAL_FORMS" type="hidden" value="0"/>
159
+ <input id="id_memoimagemodel_set-MIN_NUM_FORMS" name="memoimagemodel_set-MIN_NUM_FORMS" type="hidden" value="0"/>
160
+ <input id="id_memoimagemodel_set-MAX_NUM_FORMS" name="memoimagemodel_set-MAX_NUM_FORMS" type="hidden" value="1000"/>
161
+ <fieldset class="module sortable">
162
+ <h2>
163
+ Images
164
+ </h2>
165
+ <table>
166
+ <thead>
167
+ <tr>
168
+ <th class="original">
169
+ </th>
170
+ <th class="column-position hidden">
171
+ Position
172
+ </th>
173
+ <th class="column-preview">
174
+ Preview
175
+ </th>
176
+ <th class="column-image required">
177
+ Image
178
+ <img alt="( )" class="help help-tooltip" height="10" src="/static/admin/img/icon-unknown.svg" title=" " width="10"/>
179
+ </th>
180
+ <th class="column-name">
181
+ Name
182
+ <img alt="(BaseItemAttachmentModel.name.help_text)" class="help help-tooltip" height="10" src="/static/admin/img/icon-unknown.svg" title="BaseItemAttachmentModel.name.help_text" width="10"/>
183
+ </th>
184
+ <th class="column-tags">
185
+ Tags
186
+ <img alt="( )" class="help help-tooltip" height="10" src="/static/admin/img/icon-unknown.svg" title=" " width="10"/>
187
+ </th>
188
+ <th>
189
+ Delete?
190
+ </th>
191
+ </tr>
192
+ </thead>
193
+ <tbody>
194
+ <tr class="form-row empty-form" id="memoimagemodel_set-empty">
195
+ <td class="original">
196
+ <input id="id_memoimagemodel_set-__prefix__-id" name="memoimagemodel_set-__prefix__-id" type="hidden"/>
197
+ <input id="id_memoimagemodel_set-__prefix__-memo" name="memoimagemodel_set-__prefix__-memo" type="hidden"/>
198
+ </td>
199
+ <td class="field-position hidden">
200
+ <input class="_reorder_" id="id_memoimagemodel_set-__prefix__-position" name="memoimagemodel_set-__prefix__-position" type="hidden" value="0"/>
201
+ </td>
202
+ <td class="field-preview">
203
+ <p>
204
+ -
205
+ </p>
206
+ </td>
207
+ <td class="field-image">
208
+ <input accept="image/*" id="id_memoimagemodel_set-__prefix__-image" name="memoimagemodel_set-__prefix__-image" type="file"/>
209
+ </td>
210
+ <td class="field-name">
211
+ <input class="vTextField" id="id_memoimagemodel_set-__prefix__-name" maxlength="255" name="memoimagemodel_set-__prefix__-name" type="text"/>
212
+ </td>
213
+ <td class="field-tags">
214
+ <div class="related-widget-wrapper" data-model-ref="Image Tags">
215
+ <input autocomplete="off" data-tag-list="[]" data-tag-options='{"case_sensitive": false, "force_lowercase": false, "max_count": 10, "space_delimiter": false, "required": false}' data-tagulous="true" data-theme="admin-autocomplete" id="id_memoimagemodel_set-__prefix__-tags" name="memoimagemodel_set-__prefix__-tags" type="text"/>
216
+ </div>
217
+ </td>
218
+ <td class="delete">
219
+ </td>
220
+ </tr>
221
+ </tbody>
222
+ </table>
223
+ </fieldset>
224
+ </div>
225
+ </div>
226
+ <div class="js-inline-admin-formset inline-group" data-inline-formset='{"name": "#memofilemodel_set", "options": {"prefix": "memofilemodel_set", "addText": "Add another File", "deleteText": "Remove"}}' data-inline-type="tabular" id="memofilemodel_set-group">
227
+ <div class="tabular inline-related">
228
+ <input id="id_memofilemodel_set-TOTAL_FORMS" name="memofilemodel_set-TOTAL_FORMS" type="hidden" value="0"/>
229
+ <input id="id_memofilemodel_set-INITIAL_FORMS" name="memofilemodel_set-INITIAL_FORMS" type="hidden" value="0"/>
230
+ <input id="id_memofilemodel_set-MIN_NUM_FORMS" name="memofilemodel_set-MIN_NUM_FORMS" type="hidden" value="0"/>
231
+ <input id="id_memofilemodel_set-MAX_NUM_FORMS" name="memofilemodel_set-MAX_NUM_FORMS" type="hidden" value="1000"/>
232
+ <fieldset class="module sortable">
233
+ <h2>
234
+ Files
235
+ </h2>
236
+ <table>
237
+ <thead>
238
+ <tr>
239
+ <th class="original">
240
+ </th>
241
+ <th class="column-position hidden">
242
+ Position
243
+ </th>
244
+ <th class="column-file required">
245
+ File
246
+ <img alt="( )" class="help help-tooltip" height="10" src="/static/admin/img/icon-unknown.svg" title=" " width="10"/>
247
+ </th>
248
+ <th class="column-name">
249
+ Name
250
+ <img alt="(BaseItemAttachmentModel.name.help_text)" class="help help-tooltip" height="10" src="/static/admin/img/icon-unknown.svg" title="BaseItemAttachmentModel.name.help_text" width="10"/>
251
+ </th>
252
+ <th class="column-tags">
253
+ Tags
254
+ <img alt="( )" class="help help-tooltip" height="10" src="/static/admin/img/icon-unknown.svg" title=" " width="10"/>
255
+ </th>
256
+ <th>
257
+ Delete?
258
+ </th>
259
+ </tr>
260
+ </thead>
261
+ <tbody>
262
+ <tr class="form-row empty-form" id="memofilemodel_set-empty">
263
+ <td class="original">
264
+ <input id="id_memofilemodel_set-__prefix__-id" name="memofilemodel_set-__prefix__-id" type="hidden"/>
265
+ <input id="id_memofilemodel_set-__prefix__-memo" name="memofilemodel_set-__prefix__-memo" type="hidden"/>
266
+ </td>
267
+ <td class="field-position hidden">
268
+ <input class="_reorder_" id="id_memofilemodel_set-__prefix__-position" name="memofilemodel_set-__prefix__-position" type="hidden" value="0"/>
269
+ </td>
270
+ <td class="field-file">
271
+ <input id="id_memofilemodel_set-__prefix__-file" name="memofilemodel_set-__prefix__-file" type="file"/>
272
+ </td>
273
+ <td class="field-name">
274
+ <input class="vTextField" id="id_memofilemodel_set-__prefix__-name" maxlength="255" name="memofilemodel_set-__prefix__-name" type="text"/>
275
+ </td>
276
+ <td class="field-tags">
277
+ <div class="related-widget-wrapper" data-model-ref="File Tags">
278
+ <input autocomplete="off" data-tag-list="[]" data-tag-options='{"case_sensitive": false, "force_lowercase": false, "max_count": 10, "space_delimiter": false, "required": false}' data-tagulous="true" data-theme="admin-autocomplete" id="id_memofilemodel_set-__prefix__-tags" name="memofilemodel_set-__prefix__-tags" type="text"/>
279
+ </div>
280
+ </td>
281
+ <td class="delete">
282
+ </td>
283
+ </tr>
284
+ </tbody>
285
+ </table>
286
+ </fieldset>
287
+ </div>
288
+ </div>
289
+ <div class="js-inline-admin-formset inline-group" data-inline-formset='{"name": "#memolinkmodel_set", "options": {"prefix": "memolinkmodel_set", "addText": "Add another Link", "deleteText": "Remove"}}' data-inline-type="tabular" id="memolinkmodel_set-group">
290
+ <div class="tabular inline-related last-related">
291
+ <input id="id_memolinkmodel_set-TOTAL_FORMS" name="memolinkmodel_set-TOTAL_FORMS" type="hidden" value="0"/>
292
+ <input id="id_memolinkmodel_set-INITIAL_FORMS" name="memolinkmodel_set-INITIAL_FORMS" type="hidden" value="0"/>
293
+ <input id="id_memolinkmodel_set-MIN_NUM_FORMS" name="memolinkmodel_set-MIN_NUM_FORMS" type="hidden" value="0"/>
294
+ <input id="id_memolinkmodel_set-MAX_NUM_FORMS" name="memolinkmodel_set-MAX_NUM_FORMS" type="hidden" value="1000"/>
295
+ <fieldset class="module sortable">
296
+ <h2>
297
+ Links
298
+ </h2>
299
+ <table>
300
+ <thead>
301
+ <tr>
302
+ <th class="original">
303
+ </th>
304
+ <th class="column-tags">
305
+ Tags
306
+ <img alt="( )" class="help help-tooltip" height="10" src="/static/admin/img/icon-unknown.svg" title=" " width="10"/>
307
+ </th>
308
+ <th class="column-name">
309
+ Name
310
+ <img alt="( )" class="help help-tooltip" height="10" src="/static/admin/img/icon-unknown.svg" title=" " width="10"/>
311
+ </th>
312
+ <th class="column-url required">
313
+ URL
314
+ <img alt="( )" class="help help-tooltip" height="10" src="/static/admin/img/icon-unknown.svg" title=" " width="10"/>
315
+ </th>
316
+ <th class="column-position hidden">
317
+ Position
318
+ </th>
319
+ <th>
320
+ Delete?
321
+ </th>
322
+ </tr>
323
+ </thead>
324
+ <tbody>
325
+ <tr class="form-row empty-form" id="memolinkmodel_set-empty">
326
+ <td class="original">
327
+ <input id="id_memolinkmodel_set-__prefix__-id" name="memolinkmodel_set-__prefix__-id" type="hidden"/>
328
+ <input id="id_memolinkmodel_set-__prefix__-memo" name="memolinkmodel_set-__prefix__-memo" type="hidden"/>
329
+ </td>
330
+ <td class="field-tags">
331
+ <div class="related-widget-wrapper" data-model-ref="Link Tags">
332
+ <input autocomplete="off" data-tag-list="[]" data-tag-options='{"case_sensitive": false, "force_lowercase": false, "max_count": 10, "space_delimiter": false, "required": false}' data-tagulous="true" data-theme="admin-autocomplete" id="id_memolinkmodel_set-__prefix__-tags" name="memolinkmodel_set-__prefix__-tags" type="text"/>
333
+ </div>
334
+ </td>
335
+ <td class="field-name">
336
+ <input class="vTextField" id="id_memolinkmodel_set-__prefix__-name" maxlength="255" name="memolinkmodel_set-__prefix__-name" type="text"/>
337
+ </td>
338
+ <td class="field-url">
339
+ <input class="vURLField" id="id_memolinkmodel_set-__prefix__-url" maxlength="200" name="memolinkmodel_set-__prefix__-url" type="url"/>
340
+ </td>
341
+ <td class="field-position hidden">
342
+ <input class="_reorder_" id="id_memolinkmodel_set-__prefix__-position" name="memolinkmodel_set-__prefix__-position" type="hidden" value="0"/>
343
+ </td>
344
+ <td class="delete">
345
+ </td>
346
+ </tr>
347
+ </tbody>
348
+ </table>
349
+ </fieldset>
350
+ </div>
351
+ </div>
352
+ <div class="submit-row">
353
+ <input class="default" name="_save" type="submit" value="Save"/>
354
+ <input name="_addanother" type="submit" value="Save and add another"/>
355
+ <input name="_continue" type="submit" value="Save and continue editing"/>
356
+ </div>
357
+ <script async="" data-model-name="memomodel" id="django-admin-form-add-constants" src="/static/admin/js/change_form.js">
358
+ </script>
359
+ <script data-prepopulated-fields="[]" id="django-admin-prepopulated-fields-constants" src="/static/admin/js/prepopulate_init.js">
360
+ </script>
361
+ </div>
362
+ </form>
363
+ </div>
364
+ <br class="clear"/>
365
+ </div>
@@ -0,0 +1,60 @@
1
+
2
+ Documented commands (use 'help -v' for verbose/'help <topic>' for details):
3
+
4
+ adminsortable2
5
+ ==============
6
+ reorder
7
+
8
+ ckeditor_uploader
9
+ =================
10
+ generateckeditorthumbnails
11
+
12
+ dbbackup
13
+ ========
14
+ dbbackup dbrestore listbackups mediabackup mediarestore
15
+
16
+ django.contrib.auth
17
+ ===================
18
+ changepassword createsuperuser
19
+
20
+ django.contrib.contenttypes
21
+ ===========================
22
+ remove_stale_contenttypes
23
+
24
+ django.contrib.sessions
25
+ =======================
26
+ clearsessions
27
+
28
+ django.contrib.staticfiles
29
+ ==========================
30
+ collectstatic findstatic runserver
31
+
32
+ django.core
33
+ ===========
34
+ check flush optimizemigration squashmigrations
35
+ compilemessages inspectdb sendtestemail startapp
36
+ createcachetable loaddata showmigrations startproject
37
+ dbshell makemessages sqlflush test
38
+ diffsettings makemigrations sqlmigrate testserver
39
+ dumpdata migrate sqlsequencereset
40
+
41
+ inventory
42
+ =========
43
+ seed_data tree
44
+
45
+ manage_django_project
46
+ =====================
47
+ code_style install publish safety update_req
48
+ coverage project_info run_dev_server tox
49
+
50
+ reversion
51
+ =========
52
+ createinitialrevisions deleterevisions
53
+
54
+ tagulous
55
+ ========
56
+ initial_tags
57
+
58
+ Uncategorized
59
+ =============
60
+ alias help history macro quit set shortcuts
@@ -0,0 +1,26 @@
1
+ import django
2
+ from bx_py_utils.test_utils.snapshot import assert_text_snapshot
3
+ from manage_django_project.tests.cmd2_test_utils import BaseShellTestCase
4
+
5
+
6
+ class PyInventoryDevShellTestCase(BaseShellTestCase):
7
+ def test_help(self):
8
+ stdout, stderr = self.execute(command='help')
9
+ self.assertEqual(stderr, '')
10
+ self.assertIn('Documented commands', stdout)
11
+
12
+ # Django commands:
13
+ self.assertIn('django.core', stdout)
14
+ self.assertIn('makemessages', stdout)
15
+ self.assertIn('makemigrations', stdout)
16
+
17
+ # manage_django_project:
18
+ self.assertIn('manage_django_project', stdout)
19
+ self.assertIn('run_dev_server', stdout)
20
+
21
+ # Own commands:
22
+ self.assertIn('inventory', stdout)
23
+ self.assertIn('seed_data', stdout)
24
+ self.assertIn('tree', stdout)
25
+
26
+ assert_text_snapshot(got=stdout, snapshot_name=f'test_command_shell_help_django{django.__version__}')
@@ -0,0 +1,22 @@
1
+ import io
2
+
3
+ from django.core import management
4
+ from django.core.management.commands import makemigrations
5
+ from django.test import TestCase, override_settings
6
+
7
+
8
+ class TestMigrations(TestCase):
9
+ databases = [
10
+ 'default',
11
+ ]
12
+
13
+ @override_settings(MIGRATION_MODULES={})
14
+ def test_missing_migrations(self):
15
+ output = io.StringIO()
16
+ try:
17
+ management.call_command(
18
+ makemigrations.Command(), dry_run=True, check_changes=True, verbosity=1, stdout=output
19
+ )
20
+ except SystemExit as err:
21
+ if err.code != 0:
22
+ raise AssertionError(output.getvalue())
@@ -0,0 +1,24 @@
1
+ from ckeditor.widgets import CKEditorWidget
2
+ from ckeditor_uploader.fields import RichTextUploadingField, RichTextUploadingFormField
3
+ from django.forms import modelform_factory
4
+ from django.test import TestCase
5
+
6
+ from inventory.models import ItemModel
7
+
8
+
9
+ class ItemModelTestCase(TestCase):
10
+ def test_item_description_ckeditor(self):
11
+ item = ItemModel()
12
+ opts = item._meta
13
+ model_description_field = opts.get_field('description')
14
+ self.assertIsInstance(model_description_field, RichTextUploadingField)
15
+
16
+ def test_item_description_form_ckeditor(self):
17
+ ItemForm = modelform_factory(ItemModel, fields=('description',))
18
+ form = ItemForm()
19
+ form_field = form.fields['description']
20
+ self.assertIsInstance(form_field, RichTextUploadingFormField)
21
+ widget = form_field.widget
22
+
23
+ # Note: django-ckeditor 6.3.1-6.3.x broke the widget loading:
24
+ self.assertIsInstance(widget, CKEditorWidget)