django-school-framework 0.2.2__tar.gz → 0.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/PKG-INFO +1 -1
- django_school_framework-0.2.4/apps/core/apps.py +20 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/cli.py +211 -51
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/serializers.py +2 -1
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/safety/models.py +1 -0
- django_school_framework-0.2.4/apps/safety/tests.py +41 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/safety/views.py +1 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/django_school_framework.egg-info/PKG-INFO +1 -1
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/pyproject.toml +1 -1
- django_school_framework-0.2.2/apps/core/apps.py +0 -6
- django_school_framework-0.2.2/apps/safety/tests.py +0 -20
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/README.md +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/admin.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/apps.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/migrations/0001_initial.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/migrations/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/models.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/serializers.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/tests.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/urls.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/views.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/admin.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/compliance.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/management/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/management/commands/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/management/commands/seed_school_data.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/management/commands/wait_for_db.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/migrations/0001_initial.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/migrations/0002_alter_schoolprofile_options.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/migrations/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/models.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/serializers.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/templatetags/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/templatetags/compliance_tags.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/tests.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/urls.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/views.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/admin.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/apps.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/migrations/0001_initial.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/migrations/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/models.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/serializers.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/tests.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/urls.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/views.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/admin.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/apps.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/migrations/0001_initial.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/migrations/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/models.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/tests.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/urls.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/views.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/admin.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/apps.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/migrations/0001_initial.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/migrations/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/models.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/serializers.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/tests.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/urls.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/views.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/admin.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/apps.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/migrations/0001_initial.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/migrations/0002_alter_cateringprovider_options.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/migrations/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/models.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/serializers.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/tests.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/urls.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/views.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/safety/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/safety/admin.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/safety/apps.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/safety/migrations/0001_initial.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/safety/migrations/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/safety/serializers.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/safety/urls.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/admin.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/apps.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/migrations/0001_initial.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/migrations/__init__.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/models.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/serializers.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/tests.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/urls.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/views.py +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/django_school_framework.egg-info/SOURCES.txt +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/django_school_framework.egg-info/dependency_links.txt +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/django_school_framework.egg-info/entry_points.txt +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/django_school_framework.egg-info/requires.txt +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/django_school_framework.egg-info/top_level.txt +0 -0
- {django_school_framework-0.2.2 → django_school_framework-0.2.4}/setup.cfg +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from django.apps import AppConfig
|
|
2
|
+
|
|
3
|
+
class CoreConfig(AppConfig):
|
|
4
|
+
default_auto_field = 'django.db.models.BigAutoField'
|
|
5
|
+
name = 'apps.core'
|
|
6
|
+
verbose_name = 'Загальні відомості та налаштування школи'
|
|
7
|
+
|
|
8
|
+
def ready(self):
|
|
9
|
+
import sys
|
|
10
|
+
if sys.platform == 'win32':
|
|
11
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
12
|
+
try:
|
|
13
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
14
|
+
except Exception:
|
|
15
|
+
pass
|
|
16
|
+
if hasattr(sys.stderr, 'reconfigure'):
|
|
17
|
+
try:
|
|
18
|
+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
19
|
+
except Exception:
|
|
20
|
+
pass
|
|
@@ -9,6 +9,33 @@ import os
|
|
|
9
9
|
import argparse
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
+
# Windows UTF-8 console & ANSI color configuration
|
|
13
|
+
def configure_windows_encoding():
|
|
14
|
+
"""Налаштування UTF-8 кодування для Windows консолі та системного виводу."""
|
|
15
|
+
if sys.platform == "win32":
|
|
16
|
+
try:
|
|
17
|
+
import os
|
|
18
|
+
os.system("") # Вмикає підтримку ANSI escape-послідовностей у Windows 10/11
|
|
19
|
+
except Exception:
|
|
20
|
+
pass
|
|
21
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
22
|
+
try:
|
|
23
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
if hasattr(sys.stderr, "reconfigure"):
|
|
27
|
+
try:
|
|
28
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
29
|
+
except Exception:
|
|
30
|
+
pass
|
|
31
|
+
if hasattr(sys.stdin, "reconfigure"):
|
|
32
|
+
try:
|
|
33
|
+
sys.stdin.reconfigure(encoding="utf-8", errors="replace")
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
configure_windows_encoding()
|
|
38
|
+
|
|
12
39
|
# ANSI colors
|
|
13
40
|
GREEN = "\033[92m"
|
|
14
41
|
YELLOW = "\033[93m"
|
|
@@ -64,6 +91,18 @@ import os
|
|
|
64
91
|
import sys
|
|
65
92
|
|
|
66
93
|
def main():
|
|
94
|
+
if sys.platform == 'win32':
|
|
95
|
+
if hasattr(sys.stdout, 'reconfigure'):
|
|
96
|
+
try:
|
|
97
|
+
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
98
|
+
except Exception:
|
|
99
|
+
pass
|
|
100
|
+
if hasattr(sys.stderr, 'reconfigure'):
|
|
101
|
+
try:
|
|
102
|
+
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
|
103
|
+
except Exception:
|
|
104
|
+
pass
|
|
105
|
+
|
|
67
106
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
|
68
107
|
try:
|
|
69
108
|
from django.core.management import execute_from_command_line
|
|
@@ -76,8 +115,11 @@ def main():
|
|
|
76
115
|
if __name__ == '__main__':
|
|
77
116
|
main()
|
|
78
117
|
"""
|
|
79
|
-
(output_dir / "manage.py").write_text(manage_py_content)
|
|
80
|
-
|
|
118
|
+
(output_dir / "manage.py").write_text(manage_py_content, encoding="utf-8")
|
|
119
|
+
try:
|
|
120
|
+
(output_dir / "manage.py").chmod(0o755)
|
|
121
|
+
except OSError:
|
|
122
|
+
pass
|
|
81
123
|
|
|
82
124
|
# 2. config/settings.py
|
|
83
125
|
settings_content = f"""import os
|
|
@@ -148,7 +190,7 @@ WSGI_APPLICATION = 'config.wsgi.application'
|
|
|
148
190
|
DATABASES = {{
|
|
149
191
|
'default': {{
|
|
150
192
|
'ENGINE': os.environ.get('DB_ENGINE', 'django.db.backends.sqlite3'),
|
|
151
|
-
'NAME': BASE_DIR / 'db.sqlite3' if os.environ.get('DB_ENGINE', '')
|
|
193
|
+
'NAME': BASE_DIR / 'db.sqlite3' if 'sqlite' in os.environ.get('DB_ENGINE', 'sqlite') else os.environ.get('DB_NAME', '{project_name}_db'),
|
|
152
194
|
}}
|
|
153
195
|
}}
|
|
154
196
|
|
|
@@ -176,18 +218,23 @@ if DEBUG:
|
|
|
176
218
|
CSRF_TRUSTED_ORIGINS.append(f'http://localhost:{{port}}')
|
|
177
219
|
CSRF_TRUSTED_ORIGINS.append(f'http://127.0.0.1:{{port}}')
|
|
178
220
|
"""
|
|
179
|
-
(output_dir / "config" / "settings.py").write_text(settings_content)
|
|
221
|
+
(output_dir / "config" / "settings.py").write_text(settings_content, encoding="utf-8")
|
|
180
222
|
|
|
181
223
|
# 3. config/urls.py
|
|
182
224
|
urls_content = """from django.contrib import admin
|
|
183
225
|
from django.urls import path, include
|
|
184
226
|
from django.conf import settings
|
|
227
|
+
from django.conf.urls.static import static
|
|
185
228
|
from django.views.generic import RedirectView
|
|
229
|
+
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView, SpectacularRedocView
|
|
186
230
|
from apps.core.views import PublicSchoolHomeView
|
|
187
231
|
|
|
188
232
|
urlpatterns = [
|
|
189
233
|
path('', PublicSchoolHomeView.as_view(), name='home'),
|
|
190
234
|
path('admin/', admin.site.urls),
|
|
235
|
+
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
|
236
|
+
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
|
237
|
+
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
|
|
191
238
|
path('api/v1/core/', include('apps.core.urls')),
|
|
192
239
|
path('api/v1/legal/', include('apps.legal.urls')),
|
|
193
240
|
path('api/v1/admissions/', include('apps.admissions.urls')),
|
|
@@ -200,8 +247,9 @@ urlpatterns = [
|
|
|
200
247
|
|
|
201
248
|
if settings.DEBUG:
|
|
202
249
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
250
|
+
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
|
203
251
|
"""
|
|
204
|
-
(output_dir / "config" / "urls.py").write_text(urls_content)
|
|
252
|
+
(output_dir / "config" / "urls.py").write_text(urls_content, encoding="utf-8")
|
|
205
253
|
|
|
206
254
|
# 4. config/__init__.py, wsgi.py, asgi.py
|
|
207
255
|
init_content = """# Django School Framework Project
|
|
@@ -224,25 +272,25 @@ try:
|
|
|
224
272
|
except Exception:
|
|
225
273
|
pass
|
|
226
274
|
"""
|
|
227
|
-
(output_dir / "config" / "__init__.py").write_text(init_content)
|
|
275
|
+
(output_dir / "config" / "__init__.py").write_text(init_content, encoding="utf-8")
|
|
228
276
|
|
|
229
277
|
wsgi_content = """import os
|
|
230
278
|
from django.core.wsgi import get_wsgi_application
|
|
231
279
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
|
232
280
|
application = get_wsgi_application()
|
|
233
281
|
"""
|
|
234
|
-
(output_dir / "config" / "wsgi.py").write_text(wsgi_content)
|
|
282
|
+
(output_dir / "config" / "wsgi.py").write_text(wsgi_content, encoding="utf-8")
|
|
235
283
|
|
|
236
284
|
# 5. .env.example
|
|
237
285
|
env_example = f"""DEBUG=True
|
|
238
286
|
DJANGO_SECRET_KEY=change-me-in-production
|
|
239
287
|
DB_ENGINE=django.db.backends.sqlite3
|
|
240
|
-
DB_NAME=
|
|
288
|
+
DB_NAME=db.sqlite3
|
|
241
289
|
DB_HOST_PORT=3307
|
|
242
290
|
BACKEND_HOST_PORT=8000
|
|
243
291
|
FRONTEND_HOST_PORT=5173
|
|
244
292
|
"""
|
|
245
|
-
(output_dir / ".env.example").write_text(env_example)
|
|
293
|
+
(output_dir / ".env.example").write_text(env_example, encoding="utf-8")
|
|
246
294
|
|
|
247
295
|
# 6. README.md
|
|
248
296
|
readme_content = f"""# {project_name} — Офіційний вебсайт закладу освіти
|
|
@@ -269,7 +317,7 @@ school-admin check-compliance
|
|
|
269
317
|
python manage.py runserver
|
|
270
318
|
```
|
|
271
319
|
"""
|
|
272
|
-
(output_dir / "README.md").write_text(readme_content)
|
|
320
|
+
(output_dir / "README.md").write_text(readme_content, encoding="utf-8")
|
|
273
321
|
|
|
274
322
|
print(f"\n{GREEN}{BOLD}✓ Проєкт закладу освіти успішно створено у {output_dir}!{RESET}\n")
|
|
275
323
|
print(f"Наступні кроки:")
|
|
@@ -439,7 +487,7 @@ def cmd_create_frontend(args):
|
|
|
439
487
|
}
|
|
440
488
|
}
|
|
441
489
|
"""
|
|
442
|
-
(target_dir / "package.json").write_text(pkg_json)
|
|
490
|
+
(target_dir / "package.json").write_text(pkg_json, encoding="utf-8")
|
|
443
491
|
|
|
444
492
|
# 2. vite.config.ts
|
|
445
493
|
backend_url = args.api_url or "http://127.0.0.1:8000"
|
|
@@ -474,7 +522,7 @@ export default defineConfig({{
|
|
|
474
522
|
}},
|
|
475
523
|
}});
|
|
476
524
|
"""
|
|
477
|
-
(target_dir / "vite.config.ts").write_text(vite_conf)
|
|
525
|
+
(target_dir / "vite.config.ts").write_text(vite_conf, encoding="utf-8")
|
|
478
526
|
|
|
479
527
|
# 3. index.html
|
|
480
528
|
index_html = """<!doctype html>
|
|
@@ -490,7 +538,7 @@ export default defineConfig({{
|
|
|
490
538
|
</body>
|
|
491
539
|
</html>
|
|
492
540
|
"""
|
|
493
|
-
(target_dir / "index.html").write_text(index_html)
|
|
541
|
+
(target_dir / "index.html").write_text(index_html, encoding="utf-8")
|
|
494
542
|
|
|
495
543
|
# 4. tsconfig.json
|
|
496
544
|
tsconfig = """{
|
|
@@ -511,7 +559,7 @@ export default defineConfig({{
|
|
|
511
559
|
"include": ["src"]
|
|
512
560
|
}
|
|
513
561
|
"""
|
|
514
|
-
(target_dir / "tsconfig.json").write_text(tsconfig)
|
|
562
|
+
(target_dir / "tsconfig.json").write_text(tsconfig, encoding="utf-8")
|
|
515
563
|
|
|
516
564
|
# 5. tailwind & postcss
|
|
517
565
|
tailwind_conf = """/** @type {import('tailwindcss').Config} */
|
|
@@ -523,7 +571,7 @@ export default {
|
|
|
523
571
|
plugins: [],
|
|
524
572
|
};
|
|
525
573
|
"""
|
|
526
|
-
(target_dir / "tailwind.config.js").write_text(tailwind_conf)
|
|
574
|
+
(target_dir / "tailwind.config.js").write_text(tailwind_conf, encoding="utf-8")
|
|
527
575
|
|
|
528
576
|
postcss_conf = """export default {
|
|
529
577
|
plugins: {
|
|
@@ -532,7 +580,7 @@ export default {
|
|
|
532
580
|
},
|
|
533
581
|
};
|
|
534
582
|
"""
|
|
535
|
-
(target_dir / "postcss.config.js").write_text(postcss_conf)
|
|
583
|
+
(target_dir / "postcss.config.js").write_text(postcss_conf, encoding="utf-8")
|
|
536
584
|
|
|
537
585
|
# 6. src/index.css
|
|
538
586
|
index_css = """@tailwind base;
|
|
@@ -551,7 +599,7 @@ body.contrast-bw {
|
|
|
551
599
|
color: #000000 !important;
|
|
552
600
|
}
|
|
553
601
|
"""
|
|
554
|
-
(src_dir / "index.css").write_text(index_css)
|
|
602
|
+
(src_dir / "index.css").write_text(index_css, encoding="utf-8")
|
|
555
603
|
|
|
556
604
|
# 7. src/main.tsx
|
|
557
605
|
main_tsx = """import React from 'react';
|
|
@@ -565,7 +613,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
|
565
613
|
</React.StrictMode>
|
|
566
614
|
);
|
|
567
615
|
"""
|
|
568
|
-
(src_dir / "main.tsx").write_text(main_tsx)
|
|
616
|
+
(src_dir / "main.tsx").write_text(main_tsx, encoding="utf-8")
|
|
569
617
|
|
|
570
618
|
# 8. src/App.tsx
|
|
571
619
|
app_tsx = """import React, { useState, useEffect } from 'react';
|
|
@@ -590,10 +638,12 @@ export default function App() {
|
|
|
590
638
|
const [newsList, setNewsList] = useState<any[]>([]);
|
|
591
639
|
const [selectedNews, setSelectedNews] = useState<any | null>(null);
|
|
592
640
|
const [bells, setBells] = useState<any[]>([]);
|
|
641
|
+
const [cateringProvider, setCateringProvider] = useState<any | null>(null);
|
|
593
642
|
|
|
594
643
|
// Bullying report form
|
|
595
644
|
const [incidentText, setIncidentText] = useState('');
|
|
596
645
|
const [reporterRole, setReporterRole] = useState('student');
|
|
646
|
+
const [reportStatus, setReportStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
|
597
647
|
const [reportFeedback, setReportFeedback] = useState<string | null>(null);
|
|
598
648
|
|
|
599
649
|
useEffect(() => {
|
|
@@ -633,6 +683,14 @@ export default function App() {
|
|
|
633
683
|
.then(d => setMenus(d.results || d))
|
|
634
684
|
.catch(() => {});
|
|
635
685
|
|
|
686
|
+
fetch('/api/v1/nutrition/providers/')
|
|
687
|
+
.then(r => r.json())
|
|
688
|
+
.then(d => {
|
|
689
|
+
const items = d.results || d;
|
|
690
|
+
if (items.length > 0) setCateringProvider(items[0]);
|
|
691
|
+
})
|
|
692
|
+
.catch(() => {});
|
|
693
|
+
|
|
636
694
|
fetch('/api/v1/safety/policy/')
|
|
637
695
|
.then(r => r.json())
|
|
638
696
|
.then(d => {
|
|
@@ -666,9 +724,17 @@ export default function App() {
|
|
|
666
724
|
const handleReportSubmit = (e: React.FormEvent) => {
|
|
667
725
|
e.preventDefault();
|
|
668
726
|
if (!incidentText.trim()) return;
|
|
727
|
+
setReportStatus('loading');
|
|
728
|
+
setReportFeedback(null);
|
|
729
|
+
|
|
730
|
+
const csrfMatch = document.cookie.match(/csrftoken=([^;]+)/);
|
|
731
|
+
const csrfToken = csrfMatch ? csrfMatch[1] : '';
|
|
732
|
+
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
733
|
+
if (csrfToken) headers['X-CSRFToken'] = csrfToken;
|
|
734
|
+
|
|
669
735
|
fetch('/api/v1/safety/reports/', {
|
|
670
736
|
method: 'POST',
|
|
671
|
-
headers
|
|
737
|
+
headers,
|
|
672
738
|
body: JSON.stringify({
|
|
673
739
|
reporter_role: reporterRole,
|
|
674
740
|
incident_description: incidentText,
|
|
@@ -676,15 +742,17 @@ export default function App() {
|
|
|
676
742
|
})
|
|
677
743
|
.then(res => {
|
|
678
744
|
if (res.ok) {
|
|
745
|
+
setReportStatus('success');
|
|
679
746
|
setReportFeedback('Дякуємо! Ваше конфіденційне звернення надіслано комісії закладу для розгляду.');
|
|
680
747
|
setIncidentText('');
|
|
681
748
|
} else {
|
|
749
|
+
setReportStatus('error');
|
|
682
750
|
setReportFeedback('Помилка при відправці. Будь ласка, зверніться до чергового адміністратора.');
|
|
683
751
|
}
|
|
684
752
|
})
|
|
685
753
|
.catch(() => {
|
|
686
|
-
|
|
687
|
-
|
|
754
|
+
setReportStatus('error');
|
|
755
|
+
setReportFeedback('Помилка з\'єднання. Будь ласка, зверніться до чергового адміністратора.');
|
|
688
756
|
});
|
|
689
757
|
};
|
|
690
758
|
|
|
@@ -804,18 +872,19 @@ export default function App() {
|
|
|
804
872
|
<div>
|
|
805
873
|
<div className="font-semibold text-slate-900">{doc.title}</div>
|
|
806
874
|
<div className="text-xs text-slate-500">
|
|
807
|
-
|
|
875
|
+
{doc.category_name && <span>Категорія: {doc.category_name} • </span>}
|
|
876
|
+
Оприлюднено: {doc.publish_date || doc.published_date || doc.approval_date || ''}
|
|
808
877
|
</div>
|
|
809
878
|
</div>
|
|
810
879
|
</div>
|
|
811
|
-
{doc.file && (
|
|
880
|
+
{(doc.file || doc.external_url) && (
|
|
812
881
|
<a
|
|
813
|
-
href={doc.file}
|
|
882
|
+
href={doc.file || doc.external_url}
|
|
814
883
|
target="_blank"
|
|
815
884
|
rel="noreferrer"
|
|
816
885
|
className="px-3 py-1.5 text-xs font-semibold text-blue-700 bg-blue-50 hover:bg-blue-100 rounded-lg flex items-center gap-1"
|
|
817
886
|
>
|
|
818
|
-
<Download className="w-3.5 h-3.5" /> Завантажити
|
|
887
|
+
<Download className="w-3.5 h-3.5" /> {doc.file ? 'Завантажити' : 'Відкрити'}
|
|
819
888
|
</a>
|
|
820
889
|
)}
|
|
821
890
|
</div>
|
|
@@ -848,8 +917,7 @@ export default function App() {
|
|
|
848
917
|
<tr className="border-b border-slate-200 text-slate-500 text-xs uppercase">
|
|
849
918
|
<th className="py-3 px-2">Звітний період</th>
|
|
850
919
|
<th className="py-3 px-2">Тип звіту</th>
|
|
851
|
-
<th className="py-3 px-2"
|
|
852
|
-
<th className="py-3 px-2">Видатки (грн)</th>
|
|
920
|
+
<th className="py-3 px-2">Сума коштів (грн)</th>
|
|
853
921
|
<th className="py-3 px-2">Контроль 10 днів</th>
|
|
854
922
|
<th className="py-3 px-2 text-right">Файл</th>
|
|
855
923
|
</tr>
|
|
@@ -857,10 +925,9 @@ export default function App() {
|
|
|
857
925
|
<tbody className="divide-y divide-slate-100">
|
|
858
926
|
{finances.map((f: any) => (
|
|
859
927
|
<tr key={f.id} className="hover:bg-slate-50">
|
|
860
|
-
<td className="py-3 px-2 font-bold text-slate-900">{f.
|
|
861
|
-
<td className="py-3 px-2">{f.report_type}</td>
|
|
862
|
-
<td className="py-3 px-2 font-mono text-emerald-700">{Number(f.
|
|
863
|
-
<td className="py-3 px-2 font-mono text-slate-800">{Number(f.total_expenses || 0).toLocaleString()} ₴</td>
|
|
928
|
+
<td className="py-3 px-2 font-bold text-slate-900">{f.period || f.title}</td>
|
|
929
|
+
<td className="py-3 px-2">{f.report_type_display || f.report_type}</td>
|
|
930
|
+
<td className="py-3 px-2 font-mono text-emerald-700">{Number(f.total_amount || 0).toLocaleString()} ₴</td>
|
|
864
931
|
<td className="py-3 px-2">
|
|
865
932
|
{f.is_within_10_days ? (
|
|
866
933
|
<span className="inline-flex items-center gap-1 text-xs text-emerald-700 bg-emerald-50 px-2 py-0.5 rounded font-medium">
|
|
@@ -873,8 +940,8 @@ export default function App() {
|
|
|
873
940
|
)}
|
|
874
941
|
</td>
|
|
875
942
|
<td className="py-3 px-2 text-right">
|
|
876
|
-
{f.document_file && (
|
|
877
|
-
<a href={f.document_file} target="_blank" rel="noreferrer" className="text-blue-600 hover:underline text-xs font-semibold">
|
|
943
|
+
{(f.file || f.document_file) && (
|
|
944
|
+
<a href={f.file || f.document_file} target="_blank" rel="noreferrer" className="text-blue-600 hover:underline text-xs font-semibold">
|
|
878
945
|
PDF
|
|
879
946
|
</a>
|
|
880
947
|
)}
|
|
@@ -904,10 +971,10 @@ export default function App() {
|
|
|
904
971
|
) : (
|
|
905
972
|
capacities.map((c: any) => (
|
|
906
973
|
<div key={c.id} className="p-3 flex justify-between items-center text-sm">
|
|
907
|
-
<span className="font-semibold text-slate-800">{c.grade_level}-й
|
|
908
|
-
<span className="text-xs text-slate-500">Фактично: {c.actual_students}</span>
|
|
974
|
+
<span className="font-semibold text-slate-800">{c.grade_name || (c.grade_level ? `${c.grade_level}-й клас` : 'Клас')}</span>
|
|
975
|
+
<span className="text-xs text-slate-500">Фактично: {c.enrolled_count ?? c.actual_students ?? 0}</span>
|
|
909
976
|
<span className="font-bold text-emerald-600 bg-emerald-50 px-2 py-0.5 rounded text-xs">
|
|
910
|
-
Вільних: {c.
|
|
977
|
+
Вільних: {c.available_slots ?? (c.total_capacity && c.enrolled_count !== undefined ? c.total_capacity - c.enrolled_count : 0)}
|
|
911
978
|
</span>
|
|
912
979
|
</div>
|
|
913
980
|
))
|
|
@@ -938,23 +1005,77 @@ export default function App() {
|
|
|
938
1005
|
|
|
939
1006
|
{activeTab === 'nutrition' && (
|
|
940
1007
|
<div>
|
|
941
|
-
<
|
|
942
|
-
|
|
1008
|
+
<div className="flex flex-col md:flex-row md:items-center justify-between gap-2 mb-6">
|
|
1009
|
+
<div>
|
|
1010
|
+
<h2 className="text-xl font-bold text-slate-900 mb-1">Харчування здобувачів освіти</h2>
|
|
1011
|
+
<p className="text-slate-500 text-sm">Щоденне меню відповідно до Постанови КМУ № 305 та стандартів НАССР</p>
|
|
1012
|
+
</div>
|
|
1013
|
+
{cateringProvider && (
|
|
1014
|
+
<div className="text-xs bg-amber-50 text-amber-900 border border-amber-200 rounded-lg p-2.5">
|
|
1015
|
+
<span className="font-semibold">Постачальник:</span> {cateringProvider.company_name}
|
|
1016
|
+
{cateringProvider.phone && <span className="ml-2 font-mono">тел. {cateringProvider.phone}</span>}
|
|
1017
|
+
</div>
|
|
1018
|
+
)}
|
|
1019
|
+
</div>
|
|
943
1020
|
|
|
944
1021
|
{menus.length === 0 ? (
|
|
945
|
-
<div className="p-8 text-center text-slate-500 bg-slate-50 rounded-xl">
|
|
1022
|
+
<div className="p-8 text-center text-slate-500 bg-slate-50 rounded-xl border border-slate-200">
|
|
946
1023
|
Меню на поточний тиждень завантажується черговою медсестрою/оператором харчування.
|
|
947
1024
|
</div>
|
|
948
1025
|
) : (
|
|
949
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-
|
|
1026
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
|
950
1027
|
{menus.map((m: any) => (
|
|
951
|
-
<div key={m.id} className="p-
|
|
952
|
-
<div
|
|
953
|
-
<
|
|
954
|
-
|
|
1028
|
+
<div key={m.id} className="p-5 rounded-2xl border border-slate-200 bg-white shadow-sm flex flex-col justify-between">
|
|
1029
|
+
<div>
|
|
1030
|
+
<div className="flex justify-between items-center pb-3 mb-3 border-b border-slate-100">
|
|
1031
|
+
<span className="font-bold text-base text-slate-900">{m.menu_date}</span>
|
|
1032
|
+
<span className="text-xs px-2.5 py-1 bg-blue-50 text-blue-700 rounded-full font-semibold border border-blue-100">
|
|
1033
|
+
{m.age_group_display || m.age_group}
|
|
1034
|
+
</span>
|
|
1035
|
+
</div>
|
|
1036
|
+
|
|
1037
|
+
<div className="space-y-3">
|
|
1038
|
+
{m.breakfast && (
|
|
1039
|
+
<div>
|
|
1040
|
+
<div className="text-xs font-bold text-amber-700 uppercase tracking-wide mb-1">🍳 Сніданок (страви, вихід у грамах):</div>
|
|
1041
|
+
<div className="text-xs text-slate-800 bg-slate-50 p-2.5 rounded-lg border border-slate-100 whitespace-pre-line leading-relaxed">
|
|
1042
|
+
{m.breakfast}
|
|
1043
|
+
</div>
|
|
1044
|
+
</div>
|
|
1045
|
+
)}
|
|
1046
|
+
|
|
1047
|
+
{m.lunch && (
|
|
1048
|
+
<div>
|
|
1049
|
+
<div className="text-xs font-bold text-emerald-700 uppercase tracking-wide mb-1">🍲 Обід (страви, вихід у грамах):</div>
|
|
1050
|
+
<div className="text-xs text-slate-800 bg-slate-50 p-2.5 rounded-lg border border-slate-100 whitespace-pre-line leading-relaxed">
|
|
1051
|
+
{m.lunch}
|
|
1052
|
+
</div>
|
|
1053
|
+
</div>
|
|
1054
|
+
)}
|
|
1055
|
+
|
|
1056
|
+
{m.snack && (
|
|
1057
|
+
<div>
|
|
1058
|
+
<div className="text-xs font-bold text-violet-700 uppercase tracking-wide mb-1">🍎 Полуденок / підвечірок:</div>
|
|
1059
|
+
<div className="text-xs text-slate-800 bg-slate-50 p-2.5 rounded-lg border border-slate-100 whitespace-pre-line leading-relaxed">
|
|
1060
|
+
{m.snack}
|
|
1061
|
+
</div>
|
|
1062
|
+
</div>
|
|
1063
|
+
)}
|
|
1064
|
+
</div>
|
|
955
1065
|
</div>
|
|
956
|
-
|
|
957
|
-
|
|
1066
|
+
|
|
1067
|
+
{m.photo && (
|
|
1068
|
+
<div className="mt-4 pt-3 border-t border-slate-100">
|
|
1069
|
+
<a
|
|
1070
|
+
href={m.photo}
|
|
1071
|
+
target="_blank"
|
|
1072
|
+
rel="noreferrer"
|
|
1073
|
+
className="inline-flex items-center gap-1.5 text-xs font-semibold text-blue-600 hover:text-blue-800 hover:underline"
|
|
1074
|
+
>
|
|
1075
|
+
📷 Переглянути фото затвердженого меню з печаткою →
|
|
1076
|
+
</a>
|
|
1077
|
+
</div>
|
|
1078
|
+
)}
|
|
958
1079
|
</div>
|
|
959
1080
|
))}
|
|
960
1081
|
</div>
|
|
@@ -975,8 +1096,38 @@ export default function App() {
|
|
|
975
1096
|
<div className="p-4 bg-slate-50 border border-slate-200 rounded-xl text-sm text-slate-700">
|
|
976
1097
|
<p className="font-semibold text-slate-900 mb-2">{policy?.title || 'План заходів щодо запобігання та протидії булінгу'}</p>
|
|
977
1098
|
<p className="text-xs text-slate-600 leading-relaxed mb-4">
|
|
978
|
-
{policy?.
|
|
1099
|
+
{policy?.academic_year ? `Навчальний рік: ${policy.academic_year}. ` : ''}
|
|
1100
|
+
Заклад освіти гарантує захист прав кожного здобувача освіти, конфіденційне реагування на звернення та негайне скликання комісії з розгляду випадків булінгу.
|
|
979
1101
|
</p>
|
|
1102
|
+
|
|
1103
|
+
{policy && (
|
|
1104
|
+
<div className="space-y-2 pt-2 border-t border-slate-200">
|
|
1105
|
+
{policy.action_plan_doc && (
|
|
1106
|
+
<a href={policy.action_plan_doc} target="_blank" rel="noreferrer" className="flex items-center justify-between p-2.5 bg-white rounded-lg border border-slate-200 text-xs font-semibold text-blue-700 hover:bg-blue-50">
|
|
1107
|
+
<span>📄 План заходів з протидії булінгу</span>
|
|
1108
|
+
<Download className="w-3.5 h-3.5" />
|
|
1109
|
+
</a>
|
|
1110
|
+
)}
|
|
1111
|
+
{policy.behavior_rules_doc && (
|
|
1112
|
+
<a href={policy.behavior_rules_doc} target="_blank" rel="noreferrer" className="flex items-center justify-between p-2.5 bg-white rounded-lg border border-slate-200 text-xs font-semibold text-blue-700 hover:bg-blue-50">
|
|
1113
|
+
<span>📄 Правила поведінки здобувача освіти</span>
|
|
1114
|
+
<Download className="w-3.5 h-3.5" />
|
|
1115
|
+
</a>
|
|
1116
|
+
)}
|
|
1117
|
+
{policy.application_procedure_doc && (
|
|
1118
|
+
<a href={policy.application_procedure_doc} target="_blank" rel="noreferrer" className="flex items-center justify-between p-2.5 bg-white rounded-lg border border-slate-200 text-xs font-semibold text-blue-700 hover:bg-blue-50">
|
|
1119
|
+
<span>📄 Порядок подання та розгляду заяв про булінг</span>
|
|
1120
|
+
<Download className="w-3.5 h-3.5" />
|
|
1121
|
+
</a>
|
|
1122
|
+
)}
|
|
1123
|
+
{policy.reaction_procedure_doc && (
|
|
1124
|
+
<a href={policy.reaction_procedure_doc} target="_blank" rel="noreferrer" className="flex items-center justify-between p-2.5 bg-white rounded-lg border border-slate-200 text-xs font-semibold text-blue-700 hover:bg-blue-50">
|
|
1125
|
+
<span>📄 Порядок реагування на доведені випадки булінгу</span>
|
|
1126
|
+
<Download className="w-3.5 h-3.5" />
|
|
1127
|
+
</a>
|
|
1128
|
+
)}
|
|
1129
|
+
</div>
|
|
1130
|
+
)}
|
|
980
1131
|
</div>
|
|
981
1132
|
</div>
|
|
982
1133
|
|
|
@@ -994,6 +1145,7 @@ export default function App() {
|
|
|
994
1145
|
<option value="parent">Батько / Мати / Опікун</option>
|
|
995
1146
|
<option value="teacher">Вчитель / Працівник</option>
|
|
996
1147
|
<option value="witness">Свідок події</option>
|
|
1148
|
+
<option value="anonymous">Анонімно</option>
|
|
997
1149
|
</select>
|
|
998
1150
|
</div>
|
|
999
1151
|
<div>
|
|
@@ -1009,18 +1161,26 @@ export default function App() {
|
|
|
1009
1161
|
</div>
|
|
1010
1162
|
<button
|
|
1011
1163
|
type="submit"
|
|
1012
|
-
|
|
1164
|
+
disabled={reportStatus === 'loading'}
|
|
1165
|
+
className="w-full py-2 bg-emerald-700 text-white rounded-lg text-xs font-semibold hover:bg-emerald-800 flex items-center justify-center gap-1.5 disabled:opacity-50"
|
|
1013
1166
|
>
|
|
1014
|
-
<Send className="w-3.5 h-3.5" /> Надіслати конфіденційно
|
|
1167
|
+
<Send className="w-3.5 h-3.5" /> {reportStatus === 'loading' ? 'Надсилання...' : 'Надіслати конфіденційно'}
|
|
1015
1168
|
</button>
|
|
1016
1169
|
{reportFeedback && (
|
|
1017
|
-
<div className=
|
|
1170
|
+
<div className={`p-3 rounded-lg text-xs border leading-relaxed ${
|
|
1171
|
+
reportStatus === 'success'
|
|
1172
|
+
? 'bg-emerald-50 text-emerald-800 border-emerald-200'
|
|
1173
|
+
: 'bg-rose-50 text-rose-800 border-rose-200'
|
|
1174
|
+
}`}>
|
|
1018
1175
|
{reportFeedback}
|
|
1019
1176
|
</div>
|
|
1020
1177
|
)}
|
|
1021
1178
|
</form>
|
|
1022
1179
|
</div>
|
|
1023
1180
|
</div>
|
|
1181
|
+
</div>
|
|
1182
|
+
)}
|
|
1183
|
+
|
|
1024
1184
|
{activeTab === 'gallery' && (
|
|
1025
1185
|
<div>
|
|
1026
1186
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 mb-6">
|
|
@@ -1269,7 +1429,7 @@ export default function App() {
|
|
|
1269
1429
|
);
|
|
1270
1430
|
}
|
|
1271
1431
|
"""
|
|
1272
|
-
(src_dir / "App.tsx").write_text(app_tsx)
|
|
1432
|
+
(src_dir / "App.tsx").write_text(app_tsx, encoding="utf-8")
|
|
1273
1433
|
|
|
1274
1434
|
print(f"\n{GREEN}{BOLD}✓ Готовий React-фронтенд успішно створено у {target_dir}!{RESET}\n")
|
|
1275
1435
|
print(f"Наступні кроки для запуску сайту:")
|
|
@@ -18,11 +18,12 @@ class LegalDocumentSerializer(serializers.ModelSerializer):
|
|
|
18
18
|
|
|
19
19
|
class FinancialReportSerializer(serializers.ModelSerializer):
|
|
20
20
|
is_within_10_days = serializers.BooleanField(read_only=True)
|
|
21
|
+
report_type_display = serializers.CharField(source='get_report_type_display', read_only=True)
|
|
21
22
|
|
|
22
23
|
class Meta:
|
|
23
24
|
model = FinancialReport
|
|
24
25
|
fields = [
|
|
25
|
-
'id', 'title', 'report_type', 'period', 'total_amount',
|
|
26
|
+
'id', 'title', 'report_type', 'report_type_display', 'period', 'total_amount',
|
|
26
27
|
'approval_date', 'publish_date', 'file', 'notes', 'is_within_10_days'
|
|
27
28
|
]
|
|
28
29
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from django.test import TestCase
|
|
2
|
+
from rest_framework.test import APIClient
|
|
3
|
+
from .models import ConfidentialReport
|
|
4
|
+
|
|
5
|
+
class SafetyTests(TestCase):
|
|
6
|
+
def setUp(self):
|
|
7
|
+
self.client = APIClient()
|
|
8
|
+
|
|
9
|
+
def test_confidential_report_submission(self):
|
|
10
|
+
"""Перевірка можливості анонімного надсилання заяви про булінг через API"""
|
|
11
|
+
payload = {
|
|
12
|
+
'reporter_role': 'anonymous',
|
|
13
|
+
'incident_description': 'Тестове повідомлення про факт цькування в коридорі.',
|
|
14
|
+
'contact_feedback': 'test@example.com'
|
|
15
|
+
}
|
|
16
|
+
response = self.client.post('/api/v1/safety/reports/', payload, format='json')
|
|
17
|
+
self.assertEqual(response.status_code, 201)
|
|
18
|
+
self.assertEqual(ConfidentialReport.objects.count(), 1)
|
|
19
|
+
def test_confidential_report_by_witness(self):
|
|
20
|
+
"""Перевірка ролі 'свідок події' при надсиланні заяви"""
|
|
21
|
+
payload = {
|
|
22
|
+
'reporter_role': 'witness',
|
|
23
|
+
'incident_description': 'Помічено булінг на шкільному подвір\'ї.',
|
|
24
|
+
}
|
|
25
|
+
response = self.client.post('/api/v1/safety/reports/', payload, format='json')
|
|
26
|
+
self.assertEqual(response.status_code, 201)
|
|
27
|
+
report = ConfidentialReport.objects.get(reporter_role='witness')
|
|
28
|
+
self.assertEqual(report.reporter_role, 'witness')
|
|
29
|
+
|
|
30
|
+
def test_confidential_report_with_logged_in_session(self):
|
|
31
|
+
"""Перевірка, що користувач із активною сесією (наприклад адмін) може відправити форму без CSRF помилки"""
|
|
32
|
+
from django.contrib.auth.models import User
|
|
33
|
+
user = User.objects.create_user(username='staff', password='pwd')
|
|
34
|
+
client = APIClient(enforce_csrf_checks=True)
|
|
35
|
+
client.force_login(user)
|
|
36
|
+
payload = {
|
|
37
|
+
'reporter_role': 'student',
|
|
38
|
+
'incident_description': 'Звернення від учня під час сесії.',
|
|
39
|
+
}
|
|
40
|
+
response = client.post('/api/v1/safety/reports/', payload, format='json')
|
|
41
|
+
self.assertEqual(response.status_code, 201)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-school-framework"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "Відкритий веб-фреймворк для офіційних сайтів закладів загальної середньої освіти України (ст. 30 ЗУ «Про освіту»)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
from django.test import TestCase
|
|
2
|
-
from rest_framework.test import APIClient
|
|
3
|
-
from .models import ConfidentialReport
|
|
4
|
-
|
|
5
|
-
class SafetyTests(TestCase):
|
|
6
|
-
def setUp(self):
|
|
7
|
-
self.client = APIClient()
|
|
8
|
-
|
|
9
|
-
def test_confidential_report_submission(self):
|
|
10
|
-
"""Перевірка можливості анонімного надсилання заяви про булінг через API"""
|
|
11
|
-
payload = {
|
|
12
|
-
'reporter_role': 'anonymous',
|
|
13
|
-
'incident_description': 'Тестове повідомлення про факт цькування в коридорі.',
|
|
14
|
-
'contact_feedback': 'test@example.com'
|
|
15
|
-
}
|
|
16
|
-
response = self.client.post('/api/v1/safety/reports/', payload, format='json')
|
|
17
|
-
self.assertEqual(response.status_code, 201)
|
|
18
|
-
self.assertEqual(ConfidentialReport.objects.count(), 1)
|
|
19
|
-
report = ConfidentialReport.objects.first()
|
|
20
|
-
self.assertFalse(report.is_reviewed)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/admissions/serializers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/management/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/migrations/0001_initial.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/migrations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/core/templatetags/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/gallery/migrations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/legal/migrations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/migrations/0001_initial.py
RENAMED
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/news/migrations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/nutrition/serializers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/safety/migrations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/migrations/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_school_framework-0.2.2 → django_school_framework-0.2.4}/apps/schedule/serializers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|