django-school-framework 0.2.2__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.
- apps/__init__.py +1 -0
- apps/admissions/__init__.py +0 -0
- apps/admissions/admin.py +28 -0
- apps/admissions/apps.py +6 -0
- apps/admissions/migrations/0001_initial.py +80 -0
- apps/admissions/migrations/__init__.py +0 -0
- apps/admissions/models.py +105 -0
- apps/admissions/serializers.py +28 -0
- apps/admissions/tests.py +29 -0
- apps/admissions/urls.py +16 -0
- apps/admissions/views.py +30 -0
- apps/core/__init__.py +0 -0
- apps/core/admin.py +17 -0
- apps/core/apps.py +6 -0
- apps/core/cli.py +1341 -0
- apps/core/compliance.py +236 -0
- apps/core/management/__init__.py +0 -0
- apps/core/management/commands/__init__.py +0 -0
- apps/core/management/commands/seed_school_data.py +281 -0
- apps/core/management/commands/wait_for_db.py +26 -0
- apps/core/migrations/0001_initial.py +55 -0
- apps/core/migrations/0002_alter_schoolprofile_options.py +17 -0
- apps/core/migrations/__init__.py +0 -0
- apps/core/models.py +134 -0
- apps/core/serializers.py +18 -0
- apps/core/templatetags/__init__.py +1 -0
- apps/core/templatetags/compliance_tags.py +13 -0
- apps/core/tests.py +123 -0
- apps/core/urls.py +14 -0
- apps/core/views.py +130 -0
- apps/gallery/__init__.py +0 -0
- apps/gallery/admin.py +18 -0
- apps/gallery/apps.py +6 -0
- apps/gallery/migrations/0001_initial.py +46 -0
- apps/gallery/migrations/__init__.py +0 -0
- apps/gallery/models.py +48 -0
- apps/gallery/serializers.py +14 -0
- apps/gallery/tests.py +31 -0
- apps/gallery/urls.py +11 -0
- apps/gallery/views.py +15 -0
- apps/legal/__init__.py +0 -0
- apps/legal/admin.py +31 -0
- apps/legal/apps.py +6 -0
- apps/legal/migrations/0001_initial.py +105 -0
- apps/legal/migrations/__init__.py +0 -0
- apps/legal/models.py +172 -0
- apps/legal/serializers.py +43 -0
- apps/legal/tests.py +42 -0
- apps/legal/urls.py +17 -0
- apps/legal/views.py +33 -0
- apps/news/__init__.py +0 -0
- apps/news/admin.py +13 -0
- apps/news/apps.py +6 -0
- apps/news/migrations/0001_initial.py +46 -0
- apps/news/migrations/__init__.py +0 -0
- apps/news/models.py +55 -0
- apps/news/serializers.py +14 -0
- apps/news/tests.py +37 -0
- apps/news/urls.py +11 -0
- apps/news/views.py +15 -0
- apps/nutrition/__init__.py +0 -0
- apps/nutrition/admin.py +12 -0
- apps/nutrition/apps.py +6 -0
- apps/nutrition/migrations/0001_initial.py +47 -0
- apps/nutrition/migrations/0002_alter_cateringprovider_options.py +17 -0
- apps/nutrition/migrations/__init__.py +0 -0
- apps/nutrition/models.py +53 -0
- apps/nutrition/serializers.py +17 -0
- apps/nutrition/tests.py +41 -0
- apps/nutrition/urls.py +13 -0
- apps/nutrition/views.py +13 -0
- apps/safety/__init__.py +0 -0
- apps/safety/admin.py +21 -0
- apps/safety/apps.py +6 -0
- apps/safety/migrations/0001_initial.py +48 -0
- apps/safety/migrations/__init__.py +0 -0
- apps/safety/models.py +70 -0
- apps/safety/serializers.py +17 -0
- apps/safety/tests.py +20 -0
- apps/safety/urls.py +11 -0
- apps/safety/views.py +17 -0
- apps/schedule/__init__.py +0 -0
- apps/schedule/admin.py +12 -0
- apps/schedule/apps.py +6 -0
- apps/schedule/migrations/0001_initial.py +49 -0
- apps/schedule/migrations/__init__.py +0 -0
- apps/schedule/models.py +60 -0
- apps/schedule/serializers.py +15 -0
- apps/schedule/tests.py +51 -0
- apps/schedule/urls.py +11 -0
- apps/schedule/views.py +15 -0
- django_school_framework-0.2.2.dist-info/METADATA +53 -0
- django_school_framework-0.2.2.dist-info/RECORD +96 -0
- django_school_framework-0.2.2.dist-info/WHEEL +5 -0
- django_school_framework-0.2.2.dist-info/entry_points.txt +2 -0
- django_school_framework-0.2.2.dist-info/top_level.txt +1 -0
apps/core/compliance.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
from django.apps import apps
|
|
2
|
+
from django.utils.translation import gettext_lazy as _
|
|
3
|
+
from datetime import date, timedelta
|
|
4
|
+
|
|
5
|
+
class ComplianceAuditor:
|
|
6
|
+
"""
|
|
7
|
+
Система аудиту інформаційної відкритості закладу освіти:
|
|
8
|
+
Перевіряє повноту оприлюднення обов'язкових документів згідно зі ст. 30 ЗУ «Про освіту»,
|
|
9
|
+
ст. 39 ЗУ «Про повну загальну середню освіту» та дотримання 10-денного строку оприлюднення кошторисів.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def get_audit_report(cls):
|
|
14
|
+
checks = []
|
|
15
|
+
warnings = []
|
|
16
|
+
|
|
17
|
+
# 1. Профіль закладу (core)
|
|
18
|
+
has_core = apps.is_installed('apps.core')
|
|
19
|
+
school = None
|
|
20
|
+
if has_core:
|
|
21
|
+
SchoolProfile = apps.get_model('core', 'SchoolProfile')
|
|
22
|
+
school = SchoolProfile.objects.first()
|
|
23
|
+
|
|
24
|
+
profile_ok = bool(school and school.name and school.edrpou and school.founder)
|
|
25
|
+
checks.append({
|
|
26
|
+
'code': 'profile',
|
|
27
|
+
'title': 'Реквізити, ЄДРПОУ та засновник закладу',
|
|
28
|
+
'law_ref': 'ст. 30 ч. 2 ЗУ «Про освіту»',
|
|
29
|
+
'status': 'ok' if profile_ok else 'danger',
|
|
30
|
+
'message': 'Заповнено' if profile_ok else 'Не заповнено профіль закладу або ЄДРПОУ',
|
|
31
|
+
'admin_url': '/admin/core/schoolprofile/'
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
language_ok = bool(school and school.education_language)
|
|
35
|
+
checks.append({
|
|
36
|
+
'code': 'language',
|
|
37
|
+
'title': 'Мова (мови) освітнього процесу',
|
|
38
|
+
'law_ref': 'ст. 30 ч. 2 ЗУ «Про освіту»',
|
|
39
|
+
'status': 'ok' if language_ok else 'warning',
|
|
40
|
+
'message': school.education_language if language_ok else 'Не вказано мову освітнього процесу',
|
|
41
|
+
'admin_url': '/admin/core/schoolprofile/'
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
capacity_ok = bool(school and school.licensed_capacity > 0)
|
|
45
|
+
checks.append({
|
|
46
|
+
'code': 'capacity_stats',
|
|
47
|
+
'title': 'Ліцензований обсяг та фактична кількість осіб',
|
|
48
|
+
'law_ref': 'ст. 30 ч. 2 ЗУ «Про освіту»',
|
|
49
|
+
'status': 'ok' if capacity_ok else 'warning',
|
|
50
|
+
'message': f"Ліцензія: {school.licensed_capacity if school else 0}, факт: {school.actual_students if school else 0}" if capacity_ok else 'Не зазначено ліцензований обсяг',
|
|
51
|
+
'admin_url': '/admin/core/schoolprofile/'
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
# 2. Обов'язкові нормативні документи (legal)
|
|
55
|
+
if apps.is_installed('apps.legal'):
|
|
56
|
+
LegalDocument = apps.get_model('legal', 'LegalDocument')
|
|
57
|
+
StaffMember = apps.get_model('legal', 'StaffMember')
|
|
58
|
+
FinancialReport = apps.get_model('legal', 'FinancialReport')
|
|
59
|
+
Vacancy = apps.get_model('legal', 'Vacancy')
|
|
60
|
+
|
|
61
|
+
docs = LegalDocument.objects.filter(is_active=True)
|
|
62
|
+
doc_titles = ' '.join(docs.values_list('title', flat=True)).lower()
|
|
63
|
+
|
|
64
|
+
has_charter = 'статут' in doc_titles
|
|
65
|
+
checks.append({
|
|
66
|
+
'code': 'charter',
|
|
67
|
+
'title': 'Статут закладу освіти',
|
|
68
|
+
'law_ref': 'ст. 30 ч. 2 п. 1 ЗУ «Про освіту»',
|
|
69
|
+
'status': 'ok' if has_charter else 'danger',
|
|
70
|
+
'message': 'Оприлюднено' if has_charter else 'Статут закладу відсутній у реєстрі документів',
|
|
71
|
+
'admin_url': '/admin/legal/legaldocument/'
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
has_license = 'ліценз' in doc_titles
|
|
75
|
+
checks.append({
|
|
76
|
+
'code': 'license',
|
|
77
|
+
'title': 'Ліцензії на провадження освітньої діяльності',
|
|
78
|
+
'law_ref': 'ст. 30 ч. 2 п. 2 ЗУ «Про освіту»',
|
|
79
|
+
'status': 'ok' if has_license else 'danger',
|
|
80
|
+
'message': 'Оприлюднено' if has_license else 'Ліцензію не знайдено',
|
|
81
|
+
'admin_url': '/admin/legal/legaldocument/'
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
has_accreditation = 'акредитац' in doc_titles or 'сертифікат' in doc_titles
|
|
85
|
+
checks.append({
|
|
86
|
+
'code': 'accreditation',
|
|
87
|
+
'title': 'Сертифікати про акредитацію освітніх програм',
|
|
88
|
+
'law_ref': 'ст. 30 ч. 2 п. 3 ЗУ «Про освіту»',
|
|
89
|
+
'status': 'ok' if has_accreditation else 'warning',
|
|
90
|
+
'message': 'Оприлюднено' if has_accreditation else 'Сертифікати акредитації не завантажено',
|
|
91
|
+
'admin_url': '/admin/legal/legaldocument/'
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
has_structure = 'структур' in doc_titles or 'орган' in doc_titles
|
|
95
|
+
checks.append({
|
|
96
|
+
'code': 'structure',
|
|
97
|
+
'title': 'Структура та органи управління закладу',
|
|
98
|
+
'law_ref': 'ст. 30 ч. 2 п. 4 ЗУ «Про освіту»',
|
|
99
|
+
'status': 'ok' if has_structure else 'warning',
|
|
100
|
+
'message': 'Оприлюднено' if has_structure else 'Документ про структуру відсутній',
|
|
101
|
+
'admin_url': '/admin/legal/legaldocument/'
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
# Кадровий склад
|
|
105
|
+
staff_count = StaffMember.objects.count()
|
|
106
|
+
checks.append({
|
|
107
|
+
'code': 'staff',
|
|
108
|
+
'title': 'Кадровий склад згідно з ліцензійними умовами',
|
|
109
|
+
'law_ref': 'ст. 30 ч. 2 п. 5 ЗУ «Про освіту»',
|
|
110
|
+
'status': 'ok' if staff_count > 0 else 'danger',
|
|
111
|
+
'message': f"Внесено {staff_count} працівників" if staff_count > 0 else 'Кадровий склад порожній',
|
|
112
|
+
'admin_url': '/admin/legal/staffmember/'
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
# Річний звіт керівника
|
|
116
|
+
has_annual_report = 'звіт' in doc_titles and ('керівник' in doc_titles or 'директор' in doc_titles or 'річн' in doc_titles)
|
|
117
|
+
checks.append({
|
|
118
|
+
'code': 'annual_report',
|
|
119
|
+
'title': 'Річний звіт про діяльність закладу освіти',
|
|
120
|
+
'law_ref': 'ст. 30 ч. 2 п. 12 ЗУ «Про освіту»',
|
|
121
|
+
'status': 'ok' if has_annual_report else 'warning',
|
|
122
|
+
'message': 'Оприлюднено' if has_annual_report else 'Публічний річний звіт керівника не знайдено',
|
|
123
|
+
'admin_url': '/admin/legal/legaldocument/'
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
# Фінансова звітність та контроль 10 днів
|
|
127
|
+
finance_reports = FinancialReport.objects.all()
|
|
128
|
+
finance_count = finance_reports.count()
|
|
129
|
+
overdue_reports = [r for r in finance_reports if not r.is_within_10_days]
|
|
130
|
+
|
|
131
|
+
if overdue_reports:
|
|
132
|
+
warnings.append(
|
|
133
|
+
f"Увага: {len(overdue_reports)} фінансових звітів оприлюднено з порушенням 10-денного строку з дня затвердження (ч. 3 ст. 30)!"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
finance_status = 'ok'
|
|
137
|
+
if finance_count == 0:
|
|
138
|
+
finance_status = 'danger'
|
|
139
|
+
elif overdue_reports:
|
|
140
|
+
finance_status = 'warning'
|
|
141
|
+
|
|
142
|
+
checks.append({
|
|
143
|
+
'code': 'finance_reports',
|
|
144
|
+
'title': 'Кошторис та фінансові звіти (строк 10 робочих днів)',
|
|
145
|
+
'law_ref': 'ст. 30 ч. 3 ЗУ «Про освіту»',
|
|
146
|
+
'status': finance_status,
|
|
147
|
+
'message': f"Оприлюднено {finance_count} звітів (порушень строку: {len(overdue_reports)})" if finance_count > 0 else 'Жодного кошторису чи фінзвіту не оприлюднено',
|
|
148
|
+
'admin_url': '/admin/legal/financialreport/'
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
# Конкурс на посаду керівника (ст. 39)
|
|
152
|
+
director_vacancies = Vacancy.objects.filter(is_director_vacancy=True, is_open=True)
|
|
153
|
+
if director_vacancies.exists():
|
|
154
|
+
warnings.append(
|
|
155
|
+
"Триває конкурс на посаду керівника закладу (ст. 39 ЗУ «Про повну загальну середню освіту»). Перевірте строки подання документів!"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# 3. Зарахування та вільні місця (admissions)
|
|
159
|
+
if apps.is_installed('apps.admissions'):
|
|
160
|
+
ServiceTerritory = apps.get_model('admissions', 'ServiceTerritory')
|
|
161
|
+
ClassCapacity = apps.get_model('admissions', 'ClassCapacity')
|
|
162
|
+
|
|
163
|
+
territory_count = ServiceTerritory.objects.count()
|
|
164
|
+
checks.append({
|
|
165
|
+
'code': 'territory',
|
|
166
|
+
'title': 'Закріплена територія обслуговування',
|
|
167
|
+
'law_ref': 'ст. 30 ч. 2 п. 7 ЗУ «Про освіту»',
|
|
168
|
+
'status': 'ok' if territory_count > 0 else 'warning',
|
|
169
|
+
'message': f"Закріплено {territory_count} адрес/вулиць" if territory_count > 0 else 'Територію обслуговування не внесено',
|
|
170
|
+
'admin_url': '/admin/admissions/serviceterritory/'
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
capacity_count = ClassCapacity.objects.count()
|
|
174
|
+
checks.append({
|
|
175
|
+
'code': 'class_capacity',
|
|
176
|
+
'title': 'Наявність вільних місць у кожному класі',
|
|
177
|
+
'law_ref': 'п. 7 Порядку зарахування учнів',
|
|
178
|
+
'status': 'ok' if capacity_count > 0 else 'danger',
|
|
179
|
+
'message': f"Оприлюднено для {capacity_count} класів" if capacity_count > 0 else 'Інформація про вільні місця відсутня',
|
|
180
|
+
'admin_url': '/admin/admissions/classcapacity/'
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
# 4. Протидія булінгу (safety)
|
|
184
|
+
if apps.is_installed('apps.safety'):
|
|
185
|
+
AntiBullyingPolicy = apps.get_model('safety', 'AntiBullyingPolicy')
|
|
186
|
+
ConfidentialReport = apps.get_model('safety', 'ConfidentialReport')
|
|
187
|
+
|
|
188
|
+
has_policy = AntiBullyingPolicy.objects.filter(is_active=True).exists()
|
|
189
|
+
unreviewed_reports = ConfidentialReport.objects.filter(is_reviewed=False).count()
|
|
190
|
+
|
|
191
|
+
if unreviewed_reports > 0:
|
|
192
|
+
warnings.append(
|
|
193
|
+
f"Нові конфіденційні звернення про булінг: {unreviewed_reports} неопрацьованих заяв потребують розгляду комісії!"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
checks.append({
|
|
197
|
+
'code': 'bullying_policy',
|
|
198
|
+
'title': 'План заходів щодо запобігання та протидії булінгу',
|
|
199
|
+
'law_ref': 'ст. 30 ч. 2 п. 14 ЗУ «Про освіту»',
|
|
200
|
+
'status': 'ok' if has_policy else 'danger',
|
|
201
|
+
'message': 'План та правила оприлюднено' if has_policy else 'План заходів протидії булінгу відсутній',
|
|
202
|
+
'admin_url': '/admin/safety/antibullyingpolicy/'
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
# 5. Щоденне меню харчування (nutrition)
|
|
206
|
+
if apps.is_installed('apps.nutrition'):
|
|
207
|
+
DailyMenu = apps.get_model('nutrition', 'DailyMenu')
|
|
208
|
+
recent_menu = DailyMenu.objects.filter(menu_date__gte=date.today() - timedelta(days=7)).exists()
|
|
209
|
+
|
|
210
|
+
checks.append({
|
|
211
|
+
'code': 'daily_menu',
|
|
212
|
+
'title': 'Щоденне меню харчування та безпечність',
|
|
213
|
+
'law_ref': 'Постанова КМУ № 305 / Санітарний регламент',
|
|
214
|
+
'status': 'ok' if recent_menu else 'warning',
|
|
215
|
+
'message': 'Оновлено на поточному тижні' if recent_menu else 'Меню на цей тиждень не завантажено',
|
|
216
|
+
'admin_url': '/admin/nutrition/dailymenu/'
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
# Підрахунок показників
|
|
220
|
+
total_count = len(checks)
|
|
221
|
+
passed_count = sum(1 for c in checks if c['status'] == 'ok')
|
|
222
|
+
warning_count = sum(1 for c in checks if c['status'] == 'warning')
|
|
223
|
+
danger_count = sum(1 for c in checks if c['status'] == 'danger')
|
|
224
|
+
|
|
225
|
+
score = round((passed_count / total_count) * 100) if total_count > 0 else 0
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
'score': score,
|
|
229
|
+
'total_count': total_count,
|
|
230
|
+
'passed_count': passed_count,
|
|
231
|
+
'warning_count': warning_count,
|
|
232
|
+
'danger_count': danger_count,
|
|
233
|
+
'warnings': warnings,
|
|
234
|
+
'checks': checks,
|
|
235
|
+
'is_fully_compliant': score >= 90 and danger_count == 0,
|
|
236
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand
|
|
2
|
+
from datetime import date, time
|
|
3
|
+
from apps.core.models import SchoolProfile, ThemeSettings
|
|
4
|
+
from apps.legal.models import LegalDocumentCategory, LegalDocument, FinancialReport, StaffMember, Vacancy
|
|
5
|
+
from apps.admissions.models import ServiceTerritory, ClassCapacity, EnrolledStudent, AdmissionLottery
|
|
6
|
+
from apps.nutrition.models import CateringProvider, DailyMenu
|
|
7
|
+
from apps.safety.models import AntiBullyingPolicy
|
|
8
|
+
from apps.news.models import NewsCategory, NewsArticle
|
|
9
|
+
from apps.schedule.models import BellSchedule, LessonSchedule
|
|
10
|
+
from apps.gallery.models import PhotoAlbum, GalleryPhoto
|
|
11
|
+
|
|
12
|
+
class Command(BaseCommand):
|
|
13
|
+
help = 'Наповнення бази даних зразковими даними типового закладу загальної середньої освіти'
|
|
14
|
+
|
|
15
|
+
def handle(self, *args, **options):
|
|
16
|
+
self.stdout.write('Початок генерації зразкових даних школи...')
|
|
17
|
+
|
|
18
|
+
# 1. School Profile
|
|
19
|
+
school, created = SchoolProfile.objects.get_or_create(
|
|
20
|
+
edrpou='12345678',
|
|
21
|
+
defaults={
|
|
22
|
+
'name': 'Ліцей «Інтелект та Інновації» Київської міської ради',
|
|
23
|
+
'short_name': 'Ліцей «Інтелект та Інновації»',
|
|
24
|
+
'founder': 'Київська міська рада (орган місцевого самоврядування від імені територіальної громади)',
|
|
25
|
+
'address': '01001, м. Київ, вул. Освітня, 15',
|
|
26
|
+
'phone': '+38 (044) 123-45-67',
|
|
27
|
+
'email': 'school@osvita.kyiv.ua',
|
|
28
|
+
'website': 'https://intellect-school.kyiv.ua',
|
|
29
|
+
'licensed_capacity': 900,
|
|
30
|
+
'actual_students': 824,
|
|
31
|
+
'education_language': 'Українська',
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# 2. Theme
|
|
36
|
+
ThemeSettings.objects.get_or_create(
|
|
37
|
+
school=school,
|
|
38
|
+
defaults={
|
|
39
|
+
'primary_color': '#1e3a8a',
|
|
40
|
+
'primary_hover': '#172554',
|
|
41
|
+
'secondary_color': '#059669',
|
|
42
|
+
'accent_color': '#d97706',
|
|
43
|
+
'font_family': 'Inter, system-ui, sans-serif'
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# 3. Legal Categories & Documents
|
|
48
|
+
cat_constituent, _ = LegalDocumentCategory.objects.get_or_create(name='Установчі документи', defaults={'order': 1})
|
|
49
|
+
cat_license, _ = LegalDocumentCategory.objects.get_or_create(name='Ліцензування та акредитація', defaults={'order': 2})
|
|
50
|
+
cat_structure, _ = LegalDocumentCategory.objects.get_or_create(name='Управління та структура', defaults={'order': 3})
|
|
51
|
+
cat_edu, _ = LegalDocumentCategory.objects.get_or_create(name='Освітні програми', defaults={'order': 4})
|
|
52
|
+
cat_reports, _ = LegalDocumentCategory.objects.get_or_create(name='Якість освіти та звіти', defaults={'order': 5})
|
|
53
|
+
|
|
54
|
+
LegalDocument.objects.get_or_create(
|
|
55
|
+
title='Статут закладу загальної середньої освіти',
|
|
56
|
+
defaults={
|
|
57
|
+
'category': cat_constituent,
|
|
58
|
+
'law_reference': 'ст. 30 ч. 2 ЗУ «Про освіту»',
|
|
59
|
+
'description': 'Затверджений рішенням сесії Київської міської ради. Визначає мету, завдання, структуру та статус закладу.',
|
|
60
|
+
'approval_date': date(2026, 1, 12),
|
|
61
|
+
'is_active': True,
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
LegalDocument.objects.get_or_create(
|
|
66
|
+
title='Ліцензія на провадження освітньої діяльності',
|
|
67
|
+
defaults={
|
|
68
|
+
'category': cat_license,
|
|
69
|
+
'law_reference': 'ст. 30 ч. 2 ЗУ «Про освіту»',
|
|
70
|
+
'description': 'Рівні: початкова, базова та профільна середня освіта. Видана Департаментом освіти і науки. Безстрокова.',
|
|
71
|
+
'approval_date': date(2023, 9, 5),
|
|
72
|
+
'is_active': True,
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
LegalDocument.objects.get_or_create(
|
|
77
|
+
title='Освітня програма та перелік навчальних компонентів',
|
|
78
|
+
defaults={
|
|
79
|
+
'category': cat_edu,
|
|
80
|
+
'law_reference': 'ст. 30 ч. 2 ЗУ «Про освіту»',
|
|
81
|
+
'description': 'Навчальний план, опис модельних навчальних програм НУШ, форма оцінювання результатів навчання здобувачів.',
|
|
82
|
+
'approval_date': date(2026, 8, 28),
|
|
83
|
+
'is_active': True,
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# 4. Financial Reports (п. 3 ст. 30)
|
|
88
|
+
FinancialReport.objects.get_or_create(
|
|
89
|
+
title='Кошторис закладу освіти на 2026 рік (зі змінами)',
|
|
90
|
+
defaults={
|
|
91
|
+
'report_type': 'estimate',
|
|
92
|
+
'period': '2026 рік',
|
|
93
|
+
'total_amount': 14250000.00,
|
|
94
|
+
'approval_date': date(2026, 9, 15),
|
|
95
|
+
'publish_date': date(2026, 9, 22),
|
|
96
|
+
'file': 'legal/finance/estimate_2026.pdf',
|
|
97
|
+
'notes': 'Затверджено розпорядником публічних коштів. Оприлюднено в межах 10 робочих днів.'
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
FinancialReport.objects.get_or_create(
|
|
102
|
+
title='Звіт про надходження та використання коштів загального фонду (Форма №2)',
|
|
103
|
+
defaults={
|
|
104
|
+
'report_type': 'quarterly_report',
|
|
105
|
+
'period': 'III квартал 2026',
|
|
106
|
+
'total_amount': 10890400.00,
|
|
107
|
+
'approval_date': date(2026, 10, 5),
|
|
108
|
+
'publish_date': date(2026, 10, 12),
|
|
109
|
+
'file': 'legal/finance/report_q3_2026.pdf',
|
|
110
|
+
'notes': 'Фактичне фінансування захищених статей: заробітна плата, комунальні послуги, харчування.'
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# 5. Staff
|
|
115
|
+
StaffMember.objects.get_or_create(
|
|
116
|
+
full_name='Коваленко Олена Василівна',
|
|
117
|
+
defaults={
|
|
118
|
+
'position': 'Директор ліцею, вчитель математики',
|
|
119
|
+
'is_administration': True,
|
|
120
|
+
'category': 'Вища категорія',
|
|
121
|
+
'pedagogical_title': 'Вчитель-методист',
|
|
122
|
+
'education': 'НПУ ім. М.П. Драгоманова, математичний факультет',
|
|
123
|
+
'last_qualification_update': 2025,
|
|
124
|
+
'order': 1,
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
StaffMember.objects.get_or_create(
|
|
129
|
+
full_name='Мельник Андрій Сергійович',
|
|
130
|
+
defaults={
|
|
131
|
+
'position': 'Заступник директора з навчально-виховної роботи',
|
|
132
|
+
'is_administration': True,
|
|
133
|
+
'category': 'Вища категорія',
|
|
134
|
+
'pedagogical_title': 'Старший вчитель',
|
|
135
|
+
'education': 'КНУ ім. Тараса Шевченка, фізичний факультет',
|
|
136
|
+
'last_qualification_update': 2026,
|
|
137
|
+
'order': 2,
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# 6. Territory & Classes
|
|
142
|
+
ServiceTerritory.objects.get_or_create(
|
|
143
|
+
street_name='вул. Освітня',
|
|
144
|
+
defaults={
|
|
145
|
+
'building_numbers': 'парні 2-24, непарні 1-31',
|
|
146
|
+
'district': 'Шевченківський',
|
|
147
|
+
'decision_doc': 'Розпорядження РДА № 142 від 15.02.2026'
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
ServiceTerritory.objects.get_or_create(
|
|
152
|
+
street_name='просп. Науки',
|
|
153
|
+
defaults={
|
|
154
|
+
'building_numbers': 'буд. 42, 44, 46, 48/1',
|
|
155
|
+
'district': 'Шевченківський',
|
|
156
|
+
'decision_doc': 'Розпорядження РДА № 142 від 15.02.2026'
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
cls_1a, _ = ClassCapacity.objects.get_or_create(
|
|
161
|
+
grade_name='1-А',
|
|
162
|
+
defaults={'total_capacity': 30, 'enrolled_count': 30}
|
|
163
|
+
)
|
|
164
|
+
cls_1b, _ = ClassCapacity.objects.get_or_create(
|
|
165
|
+
grade_name='1-Б',
|
|
166
|
+
defaults={'total_capacity': 30, 'enrolled_count': 28}
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Enrolled students (LAST NAMES ONLY!)
|
|
170
|
+
EnrolledStudent.objects.get_or_create(
|
|
171
|
+
last_name='Бойко',
|
|
172
|
+
grade=cls_1b,
|
|
173
|
+
defaults={'enrollment_date': date(2026, 6, 1), 'order_number': '№ 45-У'}
|
|
174
|
+
)
|
|
175
|
+
EnrolledStudent.objects.get_or_create(
|
|
176
|
+
last_name='Гриценко',
|
|
177
|
+
grade=cls_1b,
|
|
178
|
+
defaults={'enrollment_date': date(2026, 6, 1), 'order_number': '№ 45-У'}
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# 7. Catering
|
|
182
|
+
catering, _ = CateringProvider.objects.get_or_create(
|
|
183
|
+
company_name='ТОВ «Якісне Шкільне Харчування»',
|
|
184
|
+
defaults={
|
|
185
|
+
'edrpou': '87654321',
|
|
186
|
+
'contract_number': 'Договір № 12/26 від 10.01.2026',
|
|
187
|
+
'contact_person': 'Іванова Марія Петрівна, головний технолог',
|
|
188
|
+
'phone': '+38 (044) 987-65-43',
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# 8. Anti-Bullying Policy
|
|
193
|
+
AntiBullyingPolicy.objects.get_or_create(
|
|
194
|
+
title='Політика запобігання та протидії булінгу в закладі освіти',
|
|
195
|
+
academic_year='2026/2027',
|
|
196
|
+
defaults={
|
|
197
|
+
'behavior_rules_doc': 'safety/rules/rules_2026.pdf',
|
|
198
|
+
'application_procedure_doc': 'safety/procedures/application_order.pdf',
|
|
199
|
+
'reaction_procedure_doc': 'safety/procedures/reaction_order.pdf',
|
|
200
|
+
'action_plan_doc': 'safety/plans/action_plan_2026.pdf',
|
|
201
|
+
'is_active': True,
|
|
202
|
+
}
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# 9. News
|
|
206
|
+
cat_events, _ = NewsCategory.objects.get_or_create(name='Події та свята', defaults={'slug': 'events'})
|
|
207
|
+
cat_announcements, _ = NewsCategory.objects.get_or_create(name='Оголошення', defaults={'slug': 'announcements'})
|
|
208
|
+
|
|
209
|
+
NewsArticle.objects.get_or_create(
|
|
210
|
+
title='Початок прийому заяв до 1-х класів на 2026/2027 навчальний рік',
|
|
211
|
+
defaults={
|
|
212
|
+
'category': cat_announcements,
|
|
213
|
+
'summary': 'З 1 квітня розпочинається прийом заяв для дітей, які проживають на закріпленій території обслуговування.',
|
|
214
|
+
'content': 'Подання документів можливе особисто, поштою або через електронний сервіс АІКОМ.',
|
|
215
|
+
'is_urgent': True,
|
|
216
|
+
'is_published': True,
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
NewsArticle.objects.get_or_create(
|
|
221
|
+
title='Перемога учнів ліцею на Всеукраїнській олімпіаді з інформатики',
|
|
222
|
+
defaults={
|
|
223
|
+
'category': cat_events,
|
|
224
|
+
'summary': 'Команда учнів 10-х класів здобула дипломи І та ІІ ступенів.',
|
|
225
|
+
'content': 'Вітаємо переможців та їхніх наставників! Пишаємося інтелектуальними здобутками ліцеїстів.',
|
|
226
|
+
'is_urgent': False,
|
|
227
|
+
'is_published': True,
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# 10. Bell Schedule
|
|
232
|
+
bells = [
|
|
233
|
+
(1, time(8, 30), time(9, 15), 15),
|
|
234
|
+
(2, time(9, 30), time(10, 15), 20),
|
|
235
|
+
(3, time(10, 35), time(11, 20), 20),
|
|
236
|
+
(4, time(11, 40), time(12, 25), 15),
|
|
237
|
+
(5, time(12, 40), time(13, 25), 10),
|
|
238
|
+
(6, time(13, 35), time(14, 20), 10),
|
|
239
|
+
(7, time(14, 30), time(15, 15), 0),
|
|
240
|
+
]
|
|
241
|
+
for num, start, end, break_len in bells:
|
|
242
|
+
BellSchedule.objects.get_or_create(
|
|
243
|
+
lesson_number=num,
|
|
244
|
+
defaults={'start_time': start, 'end_time': end, 'break_duration': break_len}
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
# 11. Lesson Schedule (Sample for 1-Б)
|
|
248
|
+
monday_lessons = [
|
|
249
|
+
(1, 'Математика', 'каб. 102', 'Коваленко О.В.'),
|
|
250
|
+
(2, 'Українська мова', 'каб. 102', 'Ткаченко І.М.'),
|
|
251
|
+
(3, 'Я досліджую світ', 'каб. 102', 'Ткаченко І.М.'),
|
|
252
|
+
(4, 'Фізична культура', 'спортзал №1', 'Сидоренко П.В.'),
|
|
253
|
+
]
|
|
254
|
+
for lesson_num, subj, room, teacher in monday_lessons:
|
|
255
|
+
LessonSchedule.objects.get_or_create(
|
|
256
|
+
grade=cls_1b,
|
|
257
|
+
day_of_week=1,
|
|
258
|
+
lesson_number=lesson_num,
|
|
259
|
+
defaults={'subject': subj, 'classroom': room, 'teacher_name': teacher}
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# 12. Gallery
|
|
263
|
+
album, _ = PhotoAlbum.objects.get_or_create(
|
|
264
|
+
title='Свято Першого дзвоника та посвята першокласників у НУШ',
|
|
265
|
+
defaults={
|
|
266
|
+
'description': 'Урочиста лінійка, інтерактивний квест та перший урок єдності для першокласників.',
|
|
267
|
+
'event_date': date(2026, 9, 1),
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
GalleryPhoto.objects.get_or_create(
|
|
271
|
+
album=album,
|
|
272
|
+
order=1,
|
|
273
|
+
defaults={'caption': 'Урочисте відкриття навчального року'}
|
|
274
|
+
)
|
|
275
|
+
GalleryPhoto.objects.get_or_create(
|
|
276
|
+
album=album,
|
|
277
|
+
order=2,
|
|
278
|
+
defaults={'caption': 'Першокласники у просторі Нової Української Школи'}
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
self.stdout.write(self.style.SUCCESS('Базу даних успішно наповнено типовими зразковими даними!'))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from django.core.management.base import BaseCommand
|
|
3
|
+
from django.db import connections
|
|
4
|
+
from django.db.utils import OperationalError
|
|
5
|
+
|
|
6
|
+
class Command(BaseCommand):
|
|
7
|
+
"""Django command that pauses execution until the database is available"""
|
|
8
|
+
|
|
9
|
+
def handle(self, *args, **options):
|
|
10
|
+
self.stdout.write('Очікування з’єднання з базою даних...')
|
|
11
|
+
db_conn = None
|
|
12
|
+
retries = 30
|
|
13
|
+
while not db_conn and retries > 0:
|
|
14
|
+
try:
|
|
15
|
+
db_conn = connections['default']
|
|
16
|
+
db_conn.cursor()
|
|
17
|
+
self.stdout.write(self.style.SUCCESS('База даних доступна!'))
|
|
18
|
+
return
|
|
19
|
+
except OperationalError:
|
|
20
|
+
retries -= 1
|
|
21
|
+
self.stdout.write(f'База даних поки що недоступна, спроба через 1с... (залишилось {retries})')
|
|
22
|
+
time.sleep(1)
|
|
23
|
+
|
|
24
|
+
if not db_conn:
|
|
25
|
+
self.stdout.write(self.style.ERROR('Не вдалося з’єднатися з базою даних.'))
|
|
26
|
+
exit(1)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Generated by Django 5.1.15 on 2026-10-15 06:10
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
|
|
9
|
+
initial = True
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
migrations.CreateModel(
|
|
16
|
+
name='SchoolProfile',
|
|
17
|
+
fields=[
|
|
18
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
19
|
+
('name', models.CharField(help_text='Наприклад: Ліцей "Інтелект та Інновації" Київської міської ради', max_length=255, verbose_name='Повна назва закладу освіти')),
|
|
20
|
+
('short_name', models.CharField(blank=True, max_length=100, verbose_name='Скорочена назва')),
|
|
21
|
+
('edrpou', models.CharField(max_length=10, unique=True, verbose_name='Код ЄДРПОУ')),
|
|
22
|
+
('founder', models.CharField(help_text='Орган державної влади, відповідна рада територіальної громади або уповноважена особа', max_length=255, verbose_name='Засновник закладу освіти')),
|
|
23
|
+
('address', models.CharField(max_length=255, verbose_name='Місцезнаходження (юридична адреса)')),
|
|
24
|
+
('phone', models.CharField(max_length=50, verbose_name='Контактний телефон')),
|
|
25
|
+
('email', models.EmailField(max_length=254, verbose_name='Офіційна електронна адреса')),
|
|
26
|
+
('website', models.URLField(blank=True, verbose_name='Адреса веб-сайту')),
|
|
27
|
+
('licensed_capacity', models.PositiveIntegerField(default=0, verbose_name='Ліцензований обсяг (кількість учнів)')),
|
|
28
|
+
('actual_students', models.PositiveIntegerField(default=0, verbose_name='Фактична кількість здобувачів освіти')),
|
|
29
|
+
('education_language', models.CharField(default='Українська', max_length=100, verbose_name='Мова (мови) освітнього процесу')),
|
|
30
|
+
('logo', models.ImageField(blank=True, null=True, upload_to='school/logo/', verbose_name='Логотип / герб закладу')),
|
|
31
|
+
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
32
|
+
('updated_at', models.DateTimeField(auto_now=True)),
|
|
33
|
+
],
|
|
34
|
+
options={
|
|
35
|
+
'verbose_name': 'Профіль закладу освіти',
|
|
36
|
+
'verbose_name_plural': 'Профіль закладу освіти',
|
|
37
|
+
},
|
|
38
|
+
),
|
|
39
|
+
migrations.CreateModel(
|
|
40
|
+
name='ThemeSettings',
|
|
41
|
+
fields=[
|
|
42
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
43
|
+
('primary_color', models.CharField(default='#1e3a8a', max_length=20, verbose_name='Основний колір (Primary Hex)')),
|
|
44
|
+
('primary_hover', models.CharField(default='#172554', max_length=20, verbose_name='Колір при наведенні (Primary Hover Hex)')),
|
|
45
|
+
('secondary_color', models.CharField(default='#059669', max_length=20, verbose_name='Додатковий колір (Secondary Hex)')),
|
|
46
|
+
('accent_color', models.CharField(default='#d97706', max_length=20, verbose_name='Акцентний колір (Accent Hex)')),
|
|
47
|
+
('font_family', models.CharField(default='Inter, system-ui, sans-serif', max_length=100, verbose_name='Основний шрифт')),
|
|
48
|
+
('school', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='theme', to='core.schoolprofile', verbose_name='Заклад освіти')),
|
|
49
|
+
],
|
|
50
|
+
options={
|
|
51
|
+
'verbose_name': 'Налаштування теми та брендингу',
|
|
52
|
+
'verbose_name_plural': 'Налаштування тем та брендингу',
|
|
53
|
+
},
|
|
54
|
+
),
|
|
55
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generated by Django 5.1.15 on 2026-10-15 06:17
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
|
|
8
|
+
dependencies = [
|
|
9
|
+
('core', '0001_initial'),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterModelOptions(
|
|
14
|
+
name='schoolprofile',
|
|
15
|
+
options={'ordering': ['id'], 'verbose_name': 'Профіль закладу освіти', 'verbose_name_plural': 'Профіль закладу освіти'},
|
|
16
|
+
),
|
|
17
|
+
]
|
|
File without changes
|