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,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>&nbsp;</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)