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
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Django settings
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path as __Path
|
|
7
|
+
|
|
8
|
+
from ckeditor.configs import DEFAULT_CONFIG
|
|
9
|
+
from django.utils.translation import gettext_lazy as _
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
###############################################################################
|
|
13
|
+
|
|
14
|
+
# Build paths relative to the project root:
|
|
15
|
+
BASE_PATH = __Path(__file__).parent.parent.parent
|
|
16
|
+
print(f'BASE_PATH:{BASE_PATH}')
|
|
17
|
+
assert __Path(BASE_PATH, 'inventory_project').is_dir()
|
|
18
|
+
|
|
19
|
+
###############################################################################
|
|
20
|
+
# PyInventory:
|
|
21
|
+
|
|
22
|
+
# Max length of Item/Location "path name" in change list:
|
|
23
|
+
TREE_PATH_STR_MAX_LENGTH = 70
|
|
24
|
+
|
|
25
|
+
###############################################################################
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# SECURITY WARNING: don't run with debug turned on in production!
|
|
29
|
+
DEBUG = False
|
|
30
|
+
|
|
31
|
+
# Serve static/media files by Django?
|
|
32
|
+
# In production the Webserver should serve this!
|
|
33
|
+
SERVE_FILES = False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# SECURITY WARNING: keep the secret key used in production secret!
|
|
37
|
+
__SECRET_FILE = __Path(BASE_PATH, 'secret.txt').resolve()
|
|
38
|
+
if not __SECRET_FILE.is_file():
|
|
39
|
+
print(f'Generate {__SECRET_FILE}')
|
|
40
|
+
from secrets import token_urlsafe as __token_urlsafe
|
|
41
|
+
|
|
42
|
+
__SECRET_FILE.open('w').write(__token_urlsafe(128))
|
|
43
|
+
|
|
44
|
+
SECRET_KEY = __SECRET_FILE.open('r').read().strip()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Application definition
|
|
48
|
+
|
|
49
|
+
INSTALLED_APPS = [
|
|
50
|
+
'django.contrib.admin',
|
|
51
|
+
'django.contrib.auth',
|
|
52
|
+
'django.contrib.contenttypes',
|
|
53
|
+
'django.contrib.sessions',
|
|
54
|
+
'django.contrib.messages',
|
|
55
|
+
'django.contrib.staticfiles',
|
|
56
|
+
'bx_django_utils', # https://github.com/boxine/bx_django_utils
|
|
57
|
+
'import_export', # https://github.com/django-import-export/django-import-export
|
|
58
|
+
'dbbackup', # https://github.com/django-dbbackup/django-dbbackup
|
|
59
|
+
'ckeditor', # https://github.com/django-ckeditor/django-ckeditor
|
|
60
|
+
'ckeditor_uploader', # https://github.com/django-ckeditor/django-ckeditor
|
|
61
|
+
'reversion', # https://github.com/etianen/django-reversion
|
|
62
|
+
'reversion_compare', # https://github.com/jedie/django-reversion-compare
|
|
63
|
+
'tagulous', # https://github.com/radiac/django-tagulous
|
|
64
|
+
'adminsortable2', # https://github.com/jrief/django-admin-sortable2
|
|
65
|
+
# https://github.com/jedie/django-tools/tree/master/django_tools/serve_media_app
|
|
66
|
+
'django_tools.serve_media_app.apps.UserMediaFilesConfig',
|
|
67
|
+
# https://github.com/jedie/django-tools/tree/master/django_tools/model_version_protect
|
|
68
|
+
'django_tools.model_version_protect.apps.ModelVersionProtectConfig',
|
|
69
|
+
'inventory.apps.InventoryConfig',
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
ROOT_URLCONF = 'inventory_project.urls'
|
|
73
|
+
WSGI_APPLICATION = 'inventory_project.wsgi.application'
|
|
74
|
+
|
|
75
|
+
AUTHENTICATION_BACKENDS = [
|
|
76
|
+
'django.contrib.auth.backends.ModelBackend',
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
MIDDLEWARE = [
|
|
80
|
+
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
81
|
+
'django.middleware.locale.LocaleMiddleware',
|
|
82
|
+
'django.middleware.common.CommonMiddleware',
|
|
83
|
+
'django.middleware.csrf.CsrfViewMiddleware',
|
|
84
|
+
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
85
|
+
'inventory.middlewares.RequestDictMiddleware',
|
|
86
|
+
'django.contrib.messages.middleware.MessageMiddleware',
|
|
87
|
+
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
88
|
+
'django.middleware.security.SecurityMiddleware',
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
__TEMPLATE_DIR = __Path(BASE_PATH, 'inventory_project', 'templates')
|
|
92
|
+
assert __TEMPLATE_DIR.is_dir(), f'Directory not exists: {__TEMPLATE_DIR}'
|
|
93
|
+
TEMPLATES = [
|
|
94
|
+
{
|
|
95
|
+
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
96
|
+
"DIRS": [str(__TEMPLATE_DIR)],
|
|
97
|
+
'APP_DIRS': True,
|
|
98
|
+
'OPTIONS': {
|
|
99
|
+
'context_processors': [
|
|
100
|
+
'django.contrib.auth.context_processors.auth',
|
|
101
|
+
'django.contrib.messages.context_processors.messages',
|
|
102
|
+
'django.template.context_processors.i18n',
|
|
103
|
+
'django.template.context_processors.debug',
|
|
104
|
+
'django.template.context_processors.request',
|
|
105
|
+
'django.template.context_processors.media',
|
|
106
|
+
'django.template.context_processors.csrf',
|
|
107
|
+
'django.template.context_processors.tz',
|
|
108
|
+
'django.template.context_processors.static',
|
|
109
|
+
'inventory.context_processors.inventory_version_string',
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
# _____________________________________________________________________________
|
|
116
|
+
|
|
117
|
+
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
|
|
118
|
+
|
|
119
|
+
# _____________________________________________________________________________
|
|
120
|
+
|
|
121
|
+
# Mark CSRF cookie as "secure" -> browsers sent cookie only with an HTTPS connection:
|
|
122
|
+
CSRF_COOKIE_SECURE = True
|
|
123
|
+
|
|
124
|
+
# Mark session cookie as "secure" -> browsers sent cookie only with an HTTPS connection:
|
|
125
|
+
SESSION_COOKIE_SECURE = True
|
|
126
|
+
|
|
127
|
+
# HTTP header/value combination that signifies a request is secure
|
|
128
|
+
# Your nginx.conf must set "X-Forwarded-Protocol" proxy header!
|
|
129
|
+
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
|
|
130
|
+
|
|
131
|
+
# SecurityMiddleware should redirects all non-HTTPS requests to HTTPS:
|
|
132
|
+
SECURE_SSL_REDIRECT = True
|
|
133
|
+
|
|
134
|
+
# SecurityMiddleware should preload directive to the HTTP Strict Transport Security header:
|
|
135
|
+
SECURE_HSTS_PRELOAD = True
|
|
136
|
+
|
|
137
|
+
# Instruct modern browsers to refuse to connect to your domain name via an insecure connection:
|
|
138
|
+
SECURE_HSTS_SECONDS = 3600
|
|
139
|
+
|
|
140
|
+
# SecurityMiddleware should add the "includeSubDomains" directive to the Strict-Transport-Security
|
|
141
|
+
# header: All subdomains of your domain should be served exclusively via SSL!
|
|
142
|
+
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
|
143
|
+
|
|
144
|
+
# _____________________________________________________________________________
|
|
145
|
+
# Internationalization
|
|
146
|
+
|
|
147
|
+
LANGUAGE_CODE = 'en'
|
|
148
|
+
|
|
149
|
+
LANGUAGES = [('ca', _('Catalan')), ('de', _('German')), ('en', _('English')), ('es', _('Spanish'))]
|
|
150
|
+
USE_I18N = True
|
|
151
|
+
USE_L10N = True
|
|
152
|
+
TIME_ZONE = 'Europe/Paris'
|
|
153
|
+
USE_TZ = True
|
|
154
|
+
|
|
155
|
+
# _____________________________________________________________________________
|
|
156
|
+
# Static files (CSS, JavaScript, Images)
|
|
157
|
+
|
|
158
|
+
STATIC_URL = '/static/'
|
|
159
|
+
STATIC_ROOT = str(__Path(BASE_PATH, 'static'))
|
|
160
|
+
|
|
161
|
+
MEDIA_URL = '/media/'
|
|
162
|
+
MEDIA_ROOT = str(__Path(BASE_PATH, 'media'))
|
|
163
|
+
|
|
164
|
+
# _____________________________________________________________________________
|
|
165
|
+
# Cache Backend
|
|
166
|
+
|
|
167
|
+
CACHES = {
|
|
168
|
+
'default': {
|
|
169
|
+
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
|
170
|
+
'LOCATION': 'unique-snowflake',
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
# _____________________________________________________________________________
|
|
175
|
+
# Django-dbbackup
|
|
176
|
+
|
|
177
|
+
DBBACKUP_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
|
178
|
+
DBBACKUP_STORAGE_OPTIONS = {'location': str(__Path(BASE_PATH, 'backups'))}
|
|
179
|
+
|
|
180
|
+
# _____________________________________________________________________________
|
|
181
|
+
# django-ckeditor
|
|
182
|
+
|
|
183
|
+
CKEDITOR_BASEPATH = STATIC_URL + 'ckeditor/ckeditor/'
|
|
184
|
+
CKEDITOR_FILENAME_GENERATOR = 'inventory.ckeditor_upload.get_filename'
|
|
185
|
+
CKEDITOR_DEFAULT_CONFIG = DEFAULT_CONFIG
|
|
186
|
+
CKEDITOR_DEFAULT_CONFIG.update(
|
|
187
|
+
{
|
|
188
|
+
'removeButtons': 'Language,Cut,Copy,Paste,Undo,Redo,Anchor',
|
|
189
|
+
# plugins are here: .../site-packages/ckeditor/static/ckeditor/ckeditor/plugins
|
|
190
|
+
# and here: https://github.com/ckeditor/ckeditor4/tree/major/plugins
|
|
191
|
+
# See also: .../site-packages/ckeditor/static/ckeditor/ckeditor/build-config.js
|
|
192
|
+
'removePlugins': (
|
|
193
|
+
# Generated with devshell command:
|
|
194
|
+
# (inventory) ckeditor_info
|
|
195
|
+
'a11yhelp',
|
|
196
|
+
# 'about',
|
|
197
|
+
'adobeair',
|
|
198
|
+
'ajax',
|
|
199
|
+
'autoembed',
|
|
200
|
+
# 'autogrow',
|
|
201
|
+
'autolink',
|
|
202
|
+
# 'basicstyles',
|
|
203
|
+
'bbcode',
|
|
204
|
+
'bidi',
|
|
205
|
+
# 'blockquote',
|
|
206
|
+
'clipboard',
|
|
207
|
+
'codesnippet',
|
|
208
|
+
'codesnippetgeshi',
|
|
209
|
+
# 'colorbutton',
|
|
210
|
+
# 'colordialog',
|
|
211
|
+
'contextmenu',
|
|
212
|
+
'copyformatting',
|
|
213
|
+
'devtools',
|
|
214
|
+
'dialog',
|
|
215
|
+
'dialogadvtab',
|
|
216
|
+
'div',
|
|
217
|
+
'divarea',
|
|
218
|
+
'docprops',
|
|
219
|
+
# 'elementspath',
|
|
220
|
+
'embed',
|
|
221
|
+
'embedbase',
|
|
222
|
+
'embedsemantic',
|
|
223
|
+
'enterkey',
|
|
224
|
+
# 'entities',
|
|
225
|
+
'exportpdf',
|
|
226
|
+
# 'filebrowser',
|
|
227
|
+
# 'filetools',
|
|
228
|
+
'find',
|
|
229
|
+
'flash',
|
|
230
|
+
# 'floatingspace',
|
|
231
|
+
# 'font',
|
|
232
|
+
# 'format',
|
|
233
|
+
'forms',
|
|
234
|
+
# 'horizontalrule',
|
|
235
|
+
'htmlwriter',
|
|
236
|
+
'iframe',
|
|
237
|
+
'iframedialog',
|
|
238
|
+
# 'image',
|
|
239
|
+
# 'image2',
|
|
240
|
+
# 'indentblock',
|
|
241
|
+
# 'indentlist',
|
|
242
|
+
# 'justify',
|
|
243
|
+
'language',
|
|
244
|
+
# 'lineutils',
|
|
245
|
+
# 'link',
|
|
246
|
+
# 'list',
|
|
247
|
+
# 'liststyle',
|
|
248
|
+
'magicline',
|
|
249
|
+
'mathjax',
|
|
250
|
+
# 'maximize',
|
|
251
|
+
# 'menubutton',
|
|
252
|
+
'newpage',
|
|
253
|
+
'notification',
|
|
254
|
+
'notificationaggregator',
|
|
255
|
+
'pagebreak',
|
|
256
|
+
'pastefromgdocs',
|
|
257
|
+
'pastefromword',
|
|
258
|
+
'pastetext',
|
|
259
|
+
'pastetools',
|
|
260
|
+
'placeholder',
|
|
261
|
+
'preview',
|
|
262
|
+
'print',
|
|
263
|
+
# 'removeformat',
|
|
264
|
+
# 'resize',
|
|
265
|
+
'save',
|
|
266
|
+
'scayt',
|
|
267
|
+
'selectall',
|
|
268
|
+
'sharedspace',
|
|
269
|
+
# 'showblocks',
|
|
270
|
+
# 'showborders',
|
|
271
|
+
'smiley',
|
|
272
|
+
# 'sourcearea',
|
|
273
|
+
'sourcedialog',
|
|
274
|
+
'specialchar',
|
|
275
|
+
'stylescombo',
|
|
276
|
+
'stylesheetparser',
|
|
277
|
+
'tab',
|
|
278
|
+
# 'table',
|
|
279
|
+
# 'tableresize',
|
|
280
|
+
# 'tableselection',
|
|
281
|
+
# 'tabletools',
|
|
282
|
+
'templates',
|
|
283
|
+
# 'toolbar',
|
|
284
|
+
'uicolor',
|
|
285
|
+
# 'undo',
|
|
286
|
+
# 'uploadimage',
|
|
287
|
+
# 'uploadwidget',
|
|
288
|
+
'widget',
|
|
289
|
+
'wsc',
|
|
290
|
+
# 'wysiwygarea',
|
|
291
|
+
'xml',
|
|
292
|
+
),
|
|
293
|
+
'toolbar_PyInventoryToolbarConfig': [
|
|
294
|
+
{'name': 'basicstyles', 'items': ['Bold', 'Italic', 'Underline', 'Strike', '-', 'RemoveFormat']},
|
|
295
|
+
{
|
|
296
|
+
'name': 'paragraph',
|
|
297
|
+
'items': [
|
|
298
|
+
'NumberedList',
|
|
299
|
+
'BulletedList',
|
|
300
|
+
'-',
|
|
301
|
+
'Outdent',
|
|
302
|
+
'Indent',
|
|
303
|
+
'-',
|
|
304
|
+
'Blockquote',
|
|
305
|
+
'-',
|
|
306
|
+
'JustifyLeft',
|
|
307
|
+
'JustifyCenter',
|
|
308
|
+
'JustifyRight',
|
|
309
|
+
'JustifyBlock',
|
|
310
|
+
],
|
|
311
|
+
},
|
|
312
|
+
{'name': 'links', 'items': ['Link', 'Unlink', 'Anchor']},
|
|
313
|
+
{'name': 'insert', 'items': ['Image', 'Table', 'HorizontalRule']},
|
|
314
|
+
'/',
|
|
315
|
+
{'name': 'styles', 'items': ['Styles', 'Format', 'Font', 'FontSize']},
|
|
316
|
+
{'name': 'colors', 'items': ['TextColor', 'BGColor']},
|
|
317
|
+
{'name': 'tools', 'items': ['Maximize', 'ShowBlocks', 'Source']},
|
|
318
|
+
{'name': 'about', 'items': ['About']},
|
|
319
|
+
],
|
|
320
|
+
'toolbar': 'PyInventoryToolbarConfig',
|
|
321
|
+
'height': '25em',
|
|
322
|
+
'width': '100%',
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
CKEDITOR_CONFIGS = {
|
|
326
|
+
'ItemModel.description': CKEDITOR_DEFAULT_CONFIG,
|
|
327
|
+
'LocationModel.description': CKEDITOR_DEFAULT_CONFIG,
|
|
328
|
+
'MemoModel.description': CKEDITOR_DEFAULT_CONFIG,
|
|
329
|
+
}
|
|
330
|
+
CKEDITOR_RESTRICT_BY_USER = True
|
|
331
|
+
CKEDITOR_RESTRICT_BY_DATE = True
|
|
332
|
+
CKEDITOR_UPLOAD_PATH = 'uploads/'
|
|
333
|
+
CKEDITOR_IMAGE_BACKEND = 'pillow'
|
|
334
|
+
CKEDITOR_THUMBNAIL_SIZE = (300, 300)
|
|
335
|
+
CKEDITOR_IMAGE_QUALITY = 40
|
|
336
|
+
CKEDITOR_BROWSE_SHOW_DIRS = True
|
|
337
|
+
CKEDITOR_ALLOW_NONIMAGE_FILES = True
|
|
338
|
+
|
|
339
|
+
# _____________________________________________________________________________
|
|
340
|
+
# http://radiac.net/projects/django-tagulous/documentation/installation/#settings
|
|
341
|
+
|
|
342
|
+
TAGULOUS_NAME_MAX_LENGTH = 255
|
|
343
|
+
TAGULOUS_SLUG_MAX_LENGTH = 50
|
|
344
|
+
TAGULOUS_LABEL_MAX_LENGTH = TAGULOUS_NAME_MAX_LENGTH
|
|
345
|
+
TAGULOUS_SLUG_TRUNCATE_UNIQUE = 5
|
|
346
|
+
TAGULOUS_SLUG_ALLOW_UNICODE = False
|
|
347
|
+
|
|
348
|
+
SERIALIZATION_MODULES = {
|
|
349
|
+
'xml': 'tagulous.serializers.xml_serializer',
|
|
350
|
+
'json': 'tagulous.serializers.json',
|
|
351
|
+
'python': 'tagulous.serializers.python',
|
|
352
|
+
'yaml': 'tagulous.serializers.pyyaml',
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
# _____________________________________________________________________________
|
|
356
|
+
# cut 'pathname' in log output
|
|
357
|
+
|
|
358
|
+
old_factory = logging.getLogRecordFactory()
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def cut_path(pathname, max_length):
|
|
362
|
+
if len(pathname) <= max_length:
|
|
363
|
+
return pathname
|
|
364
|
+
return f'...{pathname[-(max_length - 3):]}'
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def record_factory(*args, **kwargs):
|
|
368
|
+
record = old_factory(*args, **kwargs)
|
|
369
|
+
record.cut_path = cut_path(record.pathname, 30)
|
|
370
|
+
return record
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
logging.setLogRecordFactory(record_factory)
|
|
374
|
+
|
|
375
|
+
# -----------------------------------------------------------------------------
|
|
376
|
+
|
|
377
|
+
LOGGING = {
|
|
378
|
+
'version': 1,
|
|
379
|
+
'disable_existing_loggers': True,
|
|
380
|
+
'formatters': {
|
|
381
|
+
'colored': { # https://github.com/borntyping/python-colorlog
|
|
382
|
+
'()': 'colorlog.ColoredFormatter',
|
|
383
|
+
'format': '%(log_color)s%(asctime)s %(levelname)8s %(cut_path)s:%(lineno)-3s %(message)s',
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
'handlers': {'console': {'class': 'colorlog.StreamHandler', 'formatter': 'colored'}},
|
|
387
|
+
'loggers': {
|
|
388
|
+
'': {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False},
|
|
389
|
+
'django': {'handlers': ['console'], 'level': 'INFO', 'propagate': False},
|
|
390
|
+
'django_tools': {'handlers': ['console'], 'level': 'INFO', 'propagate': False},
|
|
391
|
+
'inventory': {'handlers': ['console'], 'level': 'DEBUG', 'propagate': False},
|
|
392
|
+
},
|
|
393
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# flake8: noqa: E405
|
|
2
|
+
"""
|
|
3
|
+
Settings used to run tests
|
|
4
|
+
"""
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
from inventory_project.settings.prod import * # noqa
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# _____________________________________________________________________________
|
|
11
|
+
# Manage Django Project
|
|
12
|
+
|
|
13
|
+
INSTALLED_APPS.append('manage_django_project')
|
|
14
|
+
|
|
15
|
+
# _____________________________________________________________________________
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DATABASES = {
|
|
19
|
+
'default': {
|
|
20
|
+
'ENGINE': 'django.db.backends.sqlite3',
|
|
21
|
+
'NAME': ':memory:',
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
DEBUG = True
|
|
26
|
+
|
|
27
|
+
# Speedup tests by change the Password hasher:
|
|
28
|
+
PASSWORD_HASHERS = ('django.contrib.auth.hashers.MD5PasswordHasher',)
|
|
29
|
+
|
|
30
|
+
# _____________________________________________________________________________
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# All tests should use django-override-storage!
|
|
34
|
+
# Set root to not existing path, so that wrong tests will fail:
|
|
35
|
+
STATIC_ROOT = '/not/exists/static/'
|
|
36
|
+
MEDIA_ROOT = '/not/exists/media/'
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# _____________________________________________________________________________
|
|
40
|
+
# Playwright
|
|
41
|
+
# Avoid django.core.exceptions.SynchronousOnlyOperation. Playwright uses an event loop,
|
|
42
|
+
# even when using he sync API. Django only checks whether _any_ event loop is running,
|
|
43
|
+
# but not if _itself_ is running in an even loop.
|
|
44
|
+
# see https://github.com/microsoft/playwright-python/issues/439#issuecomment-763339612.
|
|
45
|
+
os.environ.setdefault('DJANGO_ALLOW_ASYNC_UNSAFE', '1')
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{% extends "admin/base.html" %}
|
|
2
|
+
{% load i18n static %}
|
|
3
|
+
|
|
4
|
+
{% block extrahead %}{{ block.super }}
|
|
5
|
+
<meta name="google" content="notranslate">
|
|
6
|
+
<meta name="robots" content="noindex,nofollow" />
|
|
7
|
+
<link rel="stylesheet" type="text/css" href="{% static 'inventory.css' %}">
|
|
8
|
+
{% endblock %}
|
|
9
|
+
|
|
10
|
+
{% block title %}{{ title }} | PyInventory {{ inventory_version_string }}{% endblock %}
|
|
11
|
+
|
|
12
|
+
{% block branding %}
|
|
13
|
+
<h1 id="site-name">
|
|
14
|
+
<a href="{% url 'admin:index' %}">PyInventory {{ inventory_version_string }}</a>
|
|
15
|
+
</h1>
|
|
16
|
+
{% endblock %}
|
|
17
|
+
|
|
18
|
+
{% block nav-global %}{% endblock %}
|
|
19
|
+
|
|
20
|
+
{% block footer %}<div id="footer">
|
|
21
|
+
<a href="https://github.com/jedie/PyInventory">https://github.com/jedie/PyInventory</a>
|
|
22
|
+
</div>{% endblock %}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{% extends "admin/login.html" %}
|
|
2
|
+
{% load i18n static %}
|
|
3
|
+
|
|
4
|
+
{% block branding %}{# remove branding #}{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block extrastyle %}{{ block.super }}
|
|
7
|
+
<style>.form-row {display: none;}</style>
|
|
8
|
+
{% endblock %}
|
|
9
|
+
|
|
10
|
+
{% block content %}
|
|
11
|
+
<script>document.write('<fo'+'rm act'+'ion="{{ app_path }}" met'+'hod="po'+'st" id="lo'+'gin-fo'+'rm">');</script>
|
|
12
|
+
{% csrf_token %}
|
|
13
|
+
<div class="form-row">
|
|
14
|
+
{{ form.as_p }}
|
|
15
|
+
</div>
|
|
16
|
+
<div class="submit-row">
|
|
17
|
+
<noscript>Please enable JavaScript ;)</noscript>
|
|
18
|
+
<label> </label>
|
|
19
|
+
<script>document.write('<in'+'put type="sub'+'mit" val'+'ue="{% trans 'Log in' %}">');</script>
|
|
20
|
+
</div>
|
|
21
|
+
<script>
|
|
22
|
+
'use strict';
|
|
23
|
+
document.write('</fo'+'rm>');
|
|
24
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
25
|
+
for (const object of document.querySelectorAll('.form-row')) {
|
|
26
|
+
object.style.display = "block";
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
</script>
|
|
30
|
+
{% endblock %}
|
|
31
|
+
|
|
32
|
+
{% block footer %}{# remove footer #}{% endblock %}
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
|
|
3
|
+
from django.contrib.auth.models import User
|
|
4
|
+
from model_bakery import baker
|
|
5
|
+
from PIL import Image
|
|
6
|
+
|
|
7
|
+
from inventory.permissions import get_or_create_normal_user_group
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_normal_user():
|
|
11
|
+
user = baker.make(
|
|
12
|
+
User,
|
|
13
|
+
id=1,
|
|
14
|
+
username='NormalUser',
|
|
15
|
+
is_staff=True,
|
|
16
|
+
is_active=True,
|
|
17
|
+
is_superuser=False,
|
|
18
|
+
)
|
|
19
|
+
assert user.user_permissions.count() == 0
|
|
20
|
+
group = get_or_create_normal_user_group()[0]
|
|
21
|
+
user.groups.set([group])
|
|
22
|
+
user.full_clean()
|
|
23
|
+
return user
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TempImageFile:
|
|
27
|
+
def __init__(self, prefix='test_image', format='png', size=(1, 1)):
|
|
28
|
+
self.format = format
|
|
29
|
+
self.image_size = size
|
|
30
|
+
self.temp = tempfile.NamedTemporaryFile(prefix=prefix, suffix=f'.{format}')
|
|
31
|
+
|
|
32
|
+
def __enter__(self):
|
|
33
|
+
self.temp_file = self.temp.__enter__()
|
|
34
|
+
pil_image = Image.new('RGB', self.image_size)
|
|
35
|
+
pil_image.save(self.temp_file, format=self.format)
|
|
36
|
+
self.temp_file.seek(0)
|
|
37
|
+
return self.temp_file
|
|
38
|
+
|
|
39
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
40
|
+
self.temp_file.__exit__(exc_type, exc_val, exc_tb)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from unittest import mock
|
|
2
|
+
|
|
3
|
+
from bx_py_utils.test_utils.context_managers import MassContextManager
|
|
4
|
+
|
|
5
|
+
from inventory import context_processors
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MockInventoryVersionString(MassContextManager):
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.mocks = [
|
|
11
|
+
mock.patch.object(context_processors, '__version__', self),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
def __repr__(self):
|
|
15
|
+
return 'MockedVersionString'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.contrib.auth.models import User
|
|
3
|
+
from django.test.client import Client
|
|
4
|
+
from playwright.sync_api import Page
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def login(page: Page, client: Client, url: str, user: User) -> None:
|
|
8
|
+
"""
|
|
9
|
+
Helper to fast login, without using the login page.
|
|
10
|
+
"""
|
|
11
|
+
# Create a session by using Django's test login:
|
|
12
|
+
client.force_login(user=user)
|
|
13
|
+
session_cookie = client.cookies[settings.SESSION_COOKIE_NAME]
|
|
14
|
+
assert session_cookie
|
|
15
|
+
|
|
16
|
+
# Inject the session Cookie to playwright browser:
|
|
17
|
+
cookie_object = {
|
|
18
|
+
'name': session_cookie.key,
|
|
19
|
+
'value': session_cookie.value,
|
|
20
|
+
'url': url,
|
|
21
|
+
}
|
|
22
|
+
page.context.add_cookies([cookie_object])
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from django.test import TestCase
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AdminAnonymousTests(TestCase):
|
|
5
|
+
"""
|
|
6
|
+
Anonymous will be redirected to the login page.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
def test_login_en(self):
|
|
10
|
+
response = self.client.get('/admin/', secure=True, HTTP_ACCEPT_LANGUAGE='en')
|
|
11
|
+
self.assertRedirects(response, expected_url='/admin/login/?next=/admin/', fetch_redirect_response=False)
|
|
12
|
+
|
|
13
|
+
def test_login_de(self):
|
|
14
|
+
response = self.client.get('/admin/', secure=True, HTTP_ACCEPT_LANGUAGE='de')
|
|
15
|
+
self.assertRedirects(response, expected_url='/admin/login/?next=/admin/', fetch_redirect_response=False)
|