django-school-framework 0.2.2__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/PKG-INFO +53 -0
- django_school_framework-0.2.2/README.md +27 -0
- django_school_framework-0.2.2/apps/__init__.py +1 -0
- django_school_framework-0.2.2/apps/admissions/__init__.py +0 -0
- django_school_framework-0.2.2/apps/admissions/admin.py +28 -0
- django_school_framework-0.2.2/apps/admissions/apps.py +6 -0
- django_school_framework-0.2.2/apps/admissions/migrations/0001_initial.py +80 -0
- django_school_framework-0.2.2/apps/admissions/migrations/__init__.py +0 -0
- django_school_framework-0.2.2/apps/admissions/models.py +105 -0
- django_school_framework-0.2.2/apps/admissions/serializers.py +28 -0
- django_school_framework-0.2.2/apps/admissions/tests.py +29 -0
- django_school_framework-0.2.2/apps/admissions/urls.py +16 -0
- django_school_framework-0.2.2/apps/admissions/views.py +30 -0
- django_school_framework-0.2.2/apps/core/__init__.py +0 -0
- django_school_framework-0.2.2/apps/core/admin.py +17 -0
- django_school_framework-0.2.2/apps/core/apps.py +6 -0
- django_school_framework-0.2.2/apps/core/cli.py +1341 -0
- django_school_framework-0.2.2/apps/core/compliance.py +236 -0
- django_school_framework-0.2.2/apps/core/management/__init__.py +0 -0
- django_school_framework-0.2.2/apps/core/management/commands/__init__.py +0 -0
- django_school_framework-0.2.2/apps/core/management/commands/seed_school_data.py +281 -0
- django_school_framework-0.2.2/apps/core/management/commands/wait_for_db.py +26 -0
- django_school_framework-0.2.2/apps/core/migrations/0001_initial.py +55 -0
- django_school_framework-0.2.2/apps/core/migrations/0002_alter_schoolprofile_options.py +17 -0
- django_school_framework-0.2.2/apps/core/migrations/__init__.py +0 -0
- django_school_framework-0.2.2/apps/core/models.py +134 -0
- django_school_framework-0.2.2/apps/core/serializers.py +18 -0
- django_school_framework-0.2.2/apps/core/templatetags/__init__.py +1 -0
- django_school_framework-0.2.2/apps/core/templatetags/compliance_tags.py +13 -0
- django_school_framework-0.2.2/apps/core/tests.py +123 -0
- django_school_framework-0.2.2/apps/core/urls.py +14 -0
- django_school_framework-0.2.2/apps/core/views.py +130 -0
- django_school_framework-0.2.2/apps/gallery/__init__.py +0 -0
- django_school_framework-0.2.2/apps/gallery/admin.py +18 -0
- django_school_framework-0.2.2/apps/gallery/apps.py +6 -0
- django_school_framework-0.2.2/apps/gallery/migrations/0001_initial.py +46 -0
- django_school_framework-0.2.2/apps/gallery/migrations/__init__.py +0 -0
- django_school_framework-0.2.2/apps/gallery/models.py +48 -0
- django_school_framework-0.2.2/apps/gallery/serializers.py +14 -0
- django_school_framework-0.2.2/apps/gallery/tests.py +31 -0
- django_school_framework-0.2.2/apps/gallery/urls.py +11 -0
- django_school_framework-0.2.2/apps/gallery/views.py +15 -0
- django_school_framework-0.2.2/apps/legal/__init__.py +0 -0
- django_school_framework-0.2.2/apps/legal/admin.py +31 -0
- django_school_framework-0.2.2/apps/legal/apps.py +6 -0
- django_school_framework-0.2.2/apps/legal/migrations/0001_initial.py +105 -0
- django_school_framework-0.2.2/apps/legal/migrations/__init__.py +0 -0
- django_school_framework-0.2.2/apps/legal/models.py +172 -0
- django_school_framework-0.2.2/apps/legal/serializers.py +43 -0
- django_school_framework-0.2.2/apps/legal/tests.py +42 -0
- django_school_framework-0.2.2/apps/legal/urls.py +17 -0
- django_school_framework-0.2.2/apps/legal/views.py +33 -0
- django_school_framework-0.2.2/apps/news/__init__.py +0 -0
- django_school_framework-0.2.2/apps/news/admin.py +13 -0
- django_school_framework-0.2.2/apps/news/apps.py +6 -0
- django_school_framework-0.2.2/apps/news/migrations/0001_initial.py +46 -0
- django_school_framework-0.2.2/apps/news/migrations/__init__.py +0 -0
- django_school_framework-0.2.2/apps/news/models.py +55 -0
- django_school_framework-0.2.2/apps/news/serializers.py +14 -0
- django_school_framework-0.2.2/apps/news/tests.py +37 -0
- django_school_framework-0.2.2/apps/news/urls.py +11 -0
- django_school_framework-0.2.2/apps/news/views.py +15 -0
- django_school_framework-0.2.2/apps/nutrition/__init__.py +0 -0
- django_school_framework-0.2.2/apps/nutrition/admin.py +12 -0
- django_school_framework-0.2.2/apps/nutrition/apps.py +6 -0
- django_school_framework-0.2.2/apps/nutrition/migrations/0001_initial.py +47 -0
- django_school_framework-0.2.2/apps/nutrition/migrations/0002_alter_cateringprovider_options.py +17 -0
- django_school_framework-0.2.2/apps/nutrition/migrations/__init__.py +0 -0
- django_school_framework-0.2.2/apps/nutrition/models.py +53 -0
- django_school_framework-0.2.2/apps/nutrition/serializers.py +17 -0
- django_school_framework-0.2.2/apps/nutrition/tests.py +41 -0
- django_school_framework-0.2.2/apps/nutrition/urls.py +13 -0
- django_school_framework-0.2.2/apps/nutrition/views.py +13 -0
- django_school_framework-0.2.2/apps/safety/__init__.py +0 -0
- django_school_framework-0.2.2/apps/safety/admin.py +21 -0
- django_school_framework-0.2.2/apps/safety/apps.py +6 -0
- django_school_framework-0.2.2/apps/safety/migrations/0001_initial.py +48 -0
- django_school_framework-0.2.2/apps/safety/migrations/__init__.py +0 -0
- django_school_framework-0.2.2/apps/safety/models.py +70 -0
- django_school_framework-0.2.2/apps/safety/serializers.py +17 -0
- django_school_framework-0.2.2/apps/safety/tests.py +20 -0
- django_school_framework-0.2.2/apps/safety/urls.py +11 -0
- django_school_framework-0.2.2/apps/safety/views.py +17 -0
- django_school_framework-0.2.2/apps/schedule/__init__.py +0 -0
- django_school_framework-0.2.2/apps/schedule/admin.py +12 -0
- django_school_framework-0.2.2/apps/schedule/apps.py +6 -0
- django_school_framework-0.2.2/apps/schedule/migrations/0001_initial.py +49 -0
- django_school_framework-0.2.2/apps/schedule/migrations/__init__.py +0 -0
- django_school_framework-0.2.2/apps/schedule/models.py +60 -0
- django_school_framework-0.2.2/apps/schedule/serializers.py +15 -0
- django_school_framework-0.2.2/apps/schedule/tests.py +51 -0
- django_school_framework-0.2.2/apps/schedule/urls.py +11 -0
- django_school_framework-0.2.2/apps/schedule/views.py +15 -0
- django_school_framework-0.2.2/django_school_framework.egg-info/PKG-INFO +53 -0
- django_school_framework-0.2.2/django_school_framework.egg-info/SOURCES.txt +99 -0
- django_school_framework-0.2.2/django_school_framework.egg-info/dependency_links.txt +1 -0
- django_school_framework-0.2.2/django_school_framework.egg-info/entry_points.txt +2 -0
- django_school_framework-0.2.2/django_school_framework.egg-info/requires.txt +14 -0
- django_school_framework-0.2.2/django_school_framework.egg-info/top_level.txt +1 -0
- django_school_framework-0.2.2/pyproject.toml +44 -0
- django_school_framework-0.2.2/setup.cfg +4 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: django-school-framework
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Відкритий веб-фреймворк для офіційних сайтів закладів загальної середньої освіти України (ст. 30 ЗУ «Про освіту»)
|
|
5
|
+
License: MIT
|
|
6
|
+
Classifier: Framework :: Django
|
|
7
|
+
Classifier: Framework :: Django :: 5.1
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
10
|
+
Classifier: Intended Audience :: Education
|
|
11
|
+
Classifier: Topic :: Education
|
|
12
|
+
Requires-Python: >=3.11
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
Requires-Dist: Django<5.2,>=5.1
|
|
15
|
+
Requires-Dist: djangorestframework>=3.15.2
|
|
16
|
+
Requires-Dist: django-cors-headers>=4.4.0
|
|
17
|
+
Requires-Dist: drf-spectacular>=0.27.2
|
|
18
|
+
Requires-Dist: django-filter>=24.3
|
|
19
|
+
Requires-Dist: pillow>=10.4.0
|
|
20
|
+
Provides-Extra: mysql
|
|
21
|
+
Requires-Dist: PyMySQL>=1.1.1; extra == "mysql"
|
|
22
|
+
Requires-Dist: mysqlclient>=2.2.4; extra == "mysql"
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-django>=4.8.0; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# Django School Framework
|
|
28
|
+
|
|
29
|
+
Модульний веб-фреймворк для офіційних сайтів закладів загальної середньої освіти України відповідно до ст. 30 ЗУ «Про освіту».
|
|
30
|
+
|
|
31
|
+
## Встановлення
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install django-school-framework
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Швидкий старт
|
|
38
|
+
|
|
39
|
+
Додайте необхідні додатки у ваш `settings.py`:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
INSTALLED_APPS = [
|
|
43
|
+
# ...
|
|
44
|
+
'apps.core',
|
|
45
|
+
'apps.legal',
|
|
46
|
+
'apps.admissions',
|
|
47
|
+
'apps.nutrition',
|
|
48
|
+
'apps.safety',
|
|
49
|
+
'apps.news',
|
|
50
|
+
'apps.schedule',
|
|
51
|
+
'apps.gallery',
|
|
52
|
+
]
|
|
53
|
+
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Django School Framework
|
|
2
|
+
|
|
3
|
+
Модульний веб-фреймворк для офіційних сайтів закладів загальної середньої освіти України відповідно до ст. 30 ЗУ «Про освіту».
|
|
4
|
+
|
|
5
|
+
## Встановлення
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install django-school-framework
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Швидкий старт
|
|
12
|
+
|
|
13
|
+
Додайте необхідні додатки у ваш `settings.py`:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
INSTALLED_APPS = [
|
|
17
|
+
# ...
|
|
18
|
+
'apps.core',
|
|
19
|
+
'apps.legal',
|
|
20
|
+
'apps.admissions',
|
|
21
|
+
'apps.nutrition',
|
|
22
|
+
'apps.safety',
|
|
23
|
+
'apps.news',
|
|
24
|
+
'apps.schedule',
|
|
25
|
+
'apps.gallery',
|
|
26
|
+
]
|
|
27
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Apps package
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
from .models import ServiceTerritory, ClassCapacity, EnrolledStudent, AdmissionLottery
|
|
3
|
+
|
|
4
|
+
class EnrolledStudentInline(admin.TabularInline):
|
|
5
|
+
model = EnrolledStudent
|
|
6
|
+
extra = 1
|
|
7
|
+
fields = ('last_name', 'enrollment_date', 'order_number', 'is_lottery')
|
|
8
|
+
|
|
9
|
+
@admin.register(ServiceTerritory)
|
|
10
|
+
class ServiceTerritoryAdmin(admin.ModelAdmin):
|
|
11
|
+
list_display = ('street_name', 'building_numbers', 'district')
|
|
12
|
+
search_fields = ('street_name', 'building_numbers')
|
|
13
|
+
|
|
14
|
+
@admin.register(ClassCapacity)
|
|
15
|
+
class ClassCapacityAdmin(admin.ModelAdmin):
|
|
16
|
+
list_display = ('grade_name', 'total_capacity', 'enrolled_count', 'available_slots', 'updated_at')
|
|
17
|
+
inlines = [EnrolledStudentInline]
|
|
18
|
+
|
|
19
|
+
@admin.register(EnrolledStudent)
|
|
20
|
+
class EnrolledStudentAdmin(admin.ModelAdmin):
|
|
21
|
+
list_display = ('last_name', 'grade', 'enrollment_date', 'order_number', 'is_lottery')
|
|
22
|
+
list_filter = ('grade', 'is_lottery')
|
|
23
|
+
search_fields = ('last_name',)
|
|
24
|
+
|
|
25
|
+
@admin.register(AdmissionLottery)
|
|
26
|
+
class AdmissionLotteryAdmin(admin.ModelAdmin):
|
|
27
|
+
list_display = ('title', 'target_grade', 'lottery_datetime', 'available_places', 'candidates_count', 'is_completed')
|
|
28
|
+
list_filter = ('is_completed',)
|
|
@@ -0,0 +1,80 @@
|
|
|
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='AdmissionLottery',
|
|
17
|
+
fields=[
|
|
18
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
19
|
+
('title', models.CharField(max_length=255, verbose_name='Тема оголошення жеребкування')),
|
|
20
|
+
('target_grade', models.CharField(max_length=50, verbose_name='Клас для жеребкування')),
|
|
21
|
+
('lottery_datetime', models.DateTimeField(verbose_name='Дата і час проведення жеребкування')),
|
|
22
|
+
('location', models.CharField(max_length=255, verbose_name='Місце та спосіб проведення (актова зала, онлайн-трансляція)')),
|
|
23
|
+
('available_places', models.PositiveIntegerField(verbose_name='Кількість вільних місць')),
|
|
24
|
+
('candidates_count', models.PositiveIntegerField(verbose_name='Кількість дітей-претендентів')),
|
|
25
|
+
('committee_order_doc', models.FileField(upload_to='admissions/lottery/', verbose_name='Наказ про утворення конкурсної комісії з 3 осіб')),
|
|
26
|
+
('is_completed', models.BooleanField(default=False, verbose_name='Жеребкування проведено')),
|
|
27
|
+
],
|
|
28
|
+
options={
|
|
29
|
+
'verbose_name': 'Жеребкування на вільні місця',
|
|
30
|
+
'verbose_name_plural': 'Жеребкування на вільні місця',
|
|
31
|
+
'ordering': ['-lottery_datetime'],
|
|
32
|
+
},
|
|
33
|
+
),
|
|
34
|
+
migrations.CreateModel(
|
|
35
|
+
name='ClassCapacity',
|
|
36
|
+
fields=[
|
|
37
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
38
|
+
('grade_name', models.CharField(max_length=50, verbose_name='Клас / паралель (наприклад: 1-А, 10-А)')),
|
|
39
|
+
('total_capacity', models.PositiveIntegerField(verbose_name='Спроможність класу')),
|
|
40
|
+
('enrolled_count', models.PositiveIntegerField(verbose_name='Фактично зараховано')),
|
|
41
|
+
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата оновлення даних')),
|
|
42
|
+
],
|
|
43
|
+
options={
|
|
44
|
+
'verbose_name': 'Спроможність та вільні місця',
|
|
45
|
+
'verbose_name_plural': 'Наявність вільних місць у класах (п. 7 Порядку)',
|
|
46
|
+
'ordering': ['grade_name'],
|
|
47
|
+
},
|
|
48
|
+
),
|
|
49
|
+
migrations.CreateModel(
|
|
50
|
+
name='ServiceTerritory',
|
|
51
|
+
fields=[
|
|
52
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
53
|
+
('street_name', models.CharField(max_length=200, verbose_name='Вулиця / проспект / провулок')),
|
|
54
|
+
('building_numbers', models.CharField(max_length=255, verbose_name='Номери будинків (діапазон або перелік)')),
|
|
55
|
+
('district', models.CharField(blank=True, max_length=100, verbose_name='Район міста / громада')),
|
|
56
|
+
('decision_doc', models.CharField(blank=True, max_length=255, verbose_name='Рішення / розпорядження про закріплення')),
|
|
57
|
+
],
|
|
58
|
+
options={
|
|
59
|
+
'verbose_name': 'Адреса закріпленої території',
|
|
60
|
+
'verbose_name_plural': 'Територія обслуговування (закріплені вулиці)',
|
|
61
|
+
'ordering': ['street_name'],
|
|
62
|
+
},
|
|
63
|
+
),
|
|
64
|
+
migrations.CreateModel(
|
|
65
|
+
name='EnrolledStudent',
|
|
66
|
+
fields=[
|
|
67
|
+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
68
|
+
('last_name', models.CharField(max_length=100, verbose_name='Прізвище зарахованої дитини (виключно прізвище)')),
|
|
69
|
+
('enrollment_date', models.DateField(verbose_name='Дата зарахування')),
|
|
70
|
+
('order_number', models.CharField(blank=True, max_length=100, verbose_name='Номер наказу про зарахування')),
|
|
71
|
+
('is_lottery', models.BooleanField(default=False, verbose_name='Зараховано за результатами жеребкування')),
|
|
72
|
+
('grade', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='students', to='admissions.classcapacity', verbose_name='Клас')),
|
|
73
|
+
],
|
|
74
|
+
options={
|
|
75
|
+
'verbose_name': 'Зарахований учень (захист персональних даних)',
|
|
76
|
+
'verbose_name_plural': 'Списки зарахованих учнів (прізвища)',
|
|
77
|
+
'ordering': ['grade', 'last_name'],
|
|
78
|
+
},
|
|
79
|
+
),
|
|
80
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from django.utils.translation import gettext_lazy as _
|
|
3
|
+
|
|
4
|
+
class AbstractServiceTerritory(models.Model):
|
|
5
|
+
street_name = models.CharField(max_length=200, verbose_name=_('Вулиця / проспект / провулок'))
|
|
6
|
+
building_numbers = models.CharField(max_length=255, verbose_name=_('Номери будинків (діапазон або перелік)'))
|
|
7
|
+
district = models.CharField(max_length=100, blank=True, verbose_name=_('Район міста / громада'))
|
|
8
|
+
decision_doc = models.CharField(max_length=255, blank=True, verbose_name=_('Рішення / розпорядження про закріплення'))
|
|
9
|
+
|
|
10
|
+
class Meta:
|
|
11
|
+
abstract = True
|
|
12
|
+
verbose_name = _('Адреса закріпленої території')
|
|
13
|
+
verbose_name_plural = _('Територія обслуговування (закріплені вулиці)')
|
|
14
|
+
ordering = ['street_name']
|
|
15
|
+
|
|
16
|
+
def __str__(self):
|
|
17
|
+
return f"{self.street_name} ({self.building_numbers})"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ServiceTerritory(AbstractServiceTerritory):
|
|
21
|
+
class Meta(AbstractServiceTerritory.Meta):
|
|
22
|
+
abstract = False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AbstractClassCapacity(models.Model):
|
|
26
|
+
grade_name = models.CharField(max_length=50, verbose_name=_('Клас / паралель (наприклад: 1-А, 10-А)'))
|
|
27
|
+
total_capacity = models.PositiveIntegerField(verbose_name=_('Спроможність класу'))
|
|
28
|
+
enrolled_count = models.PositiveIntegerField(verbose_name=_('Фактично зараховано'))
|
|
29
|
+
updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Дата оновлення даних'))
|
|
30
|
+
|
|
31
|
+
class Meta:
|
|
32
|
+
abstract = True
|
|
33
|
+
verbose_name = _('Спроможність та вільні місця')
|
|
34
|
+
verbose_name_plural = _('Наявність вільних місць у класах (п. 7 Порядку)')
|
|
35
|
+
ordering = ['grade_name']
|
|
36
|
+
|
|
37
|
+
def __str__(self):
|
|
38
|
+
return f"{self.grade_name}: вільних {self.available_slots}"
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def available_slots(self):
|
|
42
|
+
return max(0, self.total_capacity - self.enrolled_count)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ClassCapacity(AbstractClassCapacity):
|
|
46
|
+
class Meta(AbstractClassCapacity.Meta):
|
|
47
|
+
abstract = False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AbstractEnrolledStudent(models.Model):
|
|
51
|
+
"""
|
|
52
|
+
ВАЖЛИВО: Дотримання Закону України «Про захист персональних даних».
|
|
53
|
+
Оприлюднюються виключно прізвища зарахованих дітей!
|
|
54
|
+
"""
|
|
55
|
+
last_name = models.CharField(max_length=100, verbose_name=_('Прізвище зарахованої дитини (виключно прізвище)'))
|
|
56
|
+
enrollment_date = models.DateField(verbose_name=_('Дата зарахування'))
|
|
57
|
+
order_number = models.CharField(max_length=100, blank=True, verbose_name=_('Номер наказу про зарахування'))
|
|
58
|
+
is_lottery = models.BooleanField(default=False, verbose_name=_('Зараховано за результатами жеребкування'))
|
|
59
|
+
|
|
60
|
+
class Meta:
|
|
61
|
+
abstract = True
|
|
62
|
+
verbose_name = _('Зарахований учень (захист персональних даних)')
|
|
63
|
+
verbose_name_plural = _('Списки зарахованих учнів (прізвища)')
|
|
64
|
+
|
|
65
|
+
def __str__(self):
|
|
66
|
+
return f"{self.last_name}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class EnrolledStudent(AbstractEnrolledStudent):
|
|
70
|
+
grade = models.ForeignKey(ClassCapacity, on_delete=models.CASCADE, related_name='students', verbose_name=_('Клас'))
|
|
71
|
+
|
|
72
|
+
class Meta(AbstractEnrolledStudent.Meta):
|
|
73
|
+
abstract = False
|
|
74
|
+
ordering = ['grade', 'last_name']
|
|
75
|
+
|
|
76
|
+
def __str__(self):
|
|
77
|
+
return f"{self.last_name} ({self.grade.grade_name})"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AbstractAdmissionLottery(models.Model):
|
|
81
|
+
title = models.CharField(max_length=255, verbose_name=_('Тема оголошення жеребкування'))
|
|
82
|
+
target_grade = models.CharField(max_length=50, verbose_name=_('Клас для жеребкування'))
|
|
83
|
+
lottery_datetime = models.DateTimeField(verbose_name=_('Дата і час проведення жеребкування'))
|
|
84
|
+
location = models.CharField(max_length=255, verbose_name=_('Місце та спосіб проведення (актова зала, онлайн-трансляція)'))
|
|
85
|
+
available_places = models.PositiveIntegerField(verbose_name=_('Кількість вільних місць'))
|
|
86
|
+
candidates_count = models.PositiveIntegerField(verbose_name=_('Кількість дітей-претендентів'))
|
|
87
|
+
committee_order_doc = models.FileField(
|
|
88
|
+
upload_to='admissions/lottery/',
|
|
89
|
+
verbose_name=_('Наказ про утворення конкурсної комісії з 3 осіб')
|
|
90
|
+
)
|
|
91
|
+
is_completed = models.BooleanField(default=False, verbose_name=_('Жеребкування проведено'))
|
|
92
|
+
|
|
93
|
+
class Meta:
|
|
94
|
+
abstract = True
|
|
95
|
+
verbose_name = _('Жеребкування на вільні місця')
|
|
96
|
+
verbose_name_plural = _('Жеребкування на вільні місця')
|
|
97
|
+
ordering = ['-lottery_datetime']
|
|
98
|
+
|
|
99
|
+
def __str__(self):
|
|
100
|
+
return f"{self.title} ({self.target_grade})"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class AdmissionLottery(AbstractAdmissionLottery):
|
|
104
|
+
class Meta(AbstractAdmissionLottery.Meta):
|
|
105
|
+
abstract = False
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from rest_framework import serializers
|
|
2
|
+
from .models import ServiceTerritory, ClassCapacity, EnrolledStudent, AdmissionLottery
|
|
3
|
+
|
|
4
|
+
class ServiceTerritorySerializer(serializers.ModelSerializer):
|
|
5
|
+
class Meta:
|
|
6
|
+
model = ServiceTerritory
|
|
7
|
+
fields = ['id', 'street_name', 'building_numbers', 'district', 'decision_doc']
|
|
8
|
+
|
|
9
|
+
class EnrolledStudentSerializer(serializers.ModelSerializer):
|
|
10
|
+
class Meta:
|
|
11
|
+
model = EnrolledStudent
|
|
12
|
+
fields = ['id', 'last_name', 'enrollment_date', 'order_number', 'is_lottery']
|
|
13
|
+
|
|
14
|
+
class ClassCapacitySerializer(serializers.ModelSerializer):
|
|
15
|
+
available_slots = serializers.IntegerField(read_only=True)
|
|
16
|
+
students = EnrolledStudentSerializer(many=True, read_only=True)
|
|
17
|
+
|
|
18
|
+
class Meta:
|
|
19
|
+
model = ClassCapacity
|
|
20
|
+
fields = ['id', 'grade_name', 'total_capacity', 'enrolled_count', 'available_slots', 'updated_at', 'students']
|
|
21
|
+
|
|
22
|
+
class AdmissionLotterySerializer(serializers.ModelSerializer):
|
|
23
|
+
class Meta:
|
|
24
|
+
model = AdmissionLottery
|
|
25
|
+
fields = [
|
|
26
|
+
'id', 'title', 'target_grade', 'lottery_datetime', 'location',
|
|
27
|
+
'available_places', 'candidates_count', 'committee_order_doc', 'is_completed'
|
|
28
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from django.test import TestCase
|
|
2
|
+
from datetime import date
|
|
3
|
+
from .models import ClassCapacity, EnrolledStudent
|
|
4
|
+
|
|
5
|
+
class AdmissionsComplianceTests(TestCase):
|
|
6
|
+
def setUp(self):
|
|
7
|
+
self.capacity = ClassCapacity.objects.create(
|
|
8
|
+
grade_name='1-А',
|
|
9
|
+
total_capacity=30,
|
|
10
|
+
enrolled_count=28
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
def test_available_slots_calculation(self):
|
|
14
|
+
self.assertEqual(self.capacity.available_slots, 2)
|
|
15
|
+
|
|
16
|
+
def test_personal_data_protection(self):
|
|
17
|
+
"""
|
|
18
|
+
Перевірка дотримання ЗУ «Про захист персональних даних»:
|
|
19
|
+
У списках оприлюднюються виключно прізвища здобувачів освіти.
|
|
20
|
+
"""
|
|
21
|
+
student = EnrolledStudent.objects.create(
|
|
22
|
+
last_name='Коваленко',
|
|
23
|
+
grade=self.capacity,
|
|
24
|
+
enrollment_date=date(2026, 6, 1),
|
|
25
|
+
order_number='№ 12-У'
|
|
26
|
+
)
|
|
27
|
+
self.assertEqual(student.last_name, 'Коваленко')
|
|
28
|
+
self.assertFalse(hasattr(student, 'first_name'))
|
|
29
|
+
self.assertFalse(hasattr(student, 'passport_or_birth_cert'))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from django.urls import path, include
|
|
2
|
+
from rest_framework.routers import DefaultRouter
|
|
3
|
+
from .views import (
|
|
4
|
+
ServiceTerritoryViewSet, ClassCapacityViewSet,
|
|
5
|
+
EnrolledStudentViewSet, AdmissionLotteryViewSet
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
router = DefaultRouter()
|
|
9
|
+
router.register(r'territory', ServiceTerritoryViewSet, basename='admissions-territory')
|
|
10
|
+
router.register(r'capacity', ClassCapacityViewSet, basename='admissions-capacity')
|
|
11
|
+
router.register(r'students', EnrolledStudentViewSet, basename='admissions-students')
|
|
12
|
+
router.register(r'lottery', AdmissionLotteryViewSet, basename='admissions-lottery')
|
|
13
|
+
|
|
14
|
+
urlpatterns = [
|
|
15
|
+
path('', include(router.urls)),
|
|
16
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from rest_framework import viewsets, permissions, filters
|
|
2
|
+
from .models import ServiceTerritory, ClassCapacity, EnrolledStudent, AdmissionLottery
|
|
3
|
+
from .serializers import (
|
|
4
|
+
ServiceTerritorySerializer, ClassCapacitySerializer,
|
|
5
|
+
EnrolledStudentSerializer, AdmissionLotterySerializer
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
class ServiceTerritoryViewSet(viewsets.ReadOnlyModelViewSet):
|
|
9
|
+
queryset = ServiceTerritory.objects.all()
|
|
10
|
+
serializer_class = ServiceTerritorySerializer
|
|
11
|
+
permission_classes = [permissions.AllowAny]
|
|
12
|
+
filter_backends = [filters.SearchFilter]
|
|
13
|
+
search_fields = ['street_name', 'building_numbers']
|
|
14
|
+
|
|
15
|
+
class ClassCapacityViewSet(viewsets.ReadOnlyModelViewSet):
|
|
16
|
+
queryset = ClassCapacity.objects.all()
|
|
17
|
+
serializer_class = ClassCapacitySerializer
|
|
18
|
+
permission_classes = [permissions.AllowAny]
|
|
19
|
+
|
|
20
|
+
class EnrolledStudentViewSet(viewsets.ReadOnlyModelViewSet):
|
|
21
|
+
queryset = EnrolledStudent.objects.all().select_related('grade')
|
|
22
|
+
serializer_class = EnrolledStudentSerializer
|
|
23
|
+
permission_classes = [permissions.AllowAny]
|
|
24
|
+
filter_backends = [filters.SearchFilter]
|
|
25
|
+
search_fields = ['last_name']
|
|
26
|
+
|
|
27
|
+
class AdmissionLotteryViewSet(viewsets.ReadOnlyModelViewSet):
|
|
28
|
+
queryset = AdmissionLottery.objects.all()
|
|
29
|
+
serializer_class = AdmissionLotterySerializer
|
|
30
|
+
permission_classes = [permissions.AllowAny]
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from django.contrib import admin
|
|
2
|
+
from .models import SchoolProfile, ThemeSettings
|
|
3
|
+
|
|
4
|
+
class ThemeSettingsInline(admin.StackedInline):
|
|
5
|
+
model = ThemeSettings
|
|
6
|
+
can_delete = False
|
|
7
|
+
verbose_name = "Налаштування теми та кольорів сайту"
|
|
8
|
+
|
|
9
|
+
@admin.register(SchoolProfile)
|
|
10
|
+
class SchoolProfileAdmin(admin.ModelAdmin):
|
|
11
|
+
list_display = ('name', 'edrpou', 'founder', 'licensed_capacity', 'actual_students', 'updated_at')
|
|
12
|
+
search_fields = ('name', 'edrpou', 'founder')
|
|
13
|
+
inlines = [ThemeSettingsInline]
|
|
14
|
+
|
|
15
|
+
@admin.register(ThemeSettings)
|
|
16
|
+
class ThemeSettingsAdmin(admin.ModelAdmin):
|
|
17
|
+
list_display = ('school', 'primary_color', 'secondary_color', 'accent_color', 'font_family')
|