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.
Files changed (101) hide show
  1. django_school_framework-0.2.2/PKG-INFO +53 -0
  2. django_school_framework-0.2.2/README.md +27 -0
  3. django_school_framework-0.2.2/apps/__init__.py +1 -0
  4. django_school_framework-0.2.2/apps/admissions/__init__.py +0 -0
  5. django_school_framework-0.2.2/apps/admissions/admin.py +28 -0
  6. django_school_framework-0.2.2/apps/admissions/apps.py +6 -0
  7. django_school_framework-0.2.2/apps/admissions/migrations/0001_initial.py +80 -0
  8. django_school_framework-0.2.2/apps/admissions/migrations/__init__.py +0 -0
  9. django_school_framework-0.2.2/apps/admissions/models.py +105 -0
  10. django_school_framework-0.2.2/apps/admissions/serializers.py +28 -0
  11. django_school_framework-0.2.2/apps/admissions/tests.py +29 -0
  12. django_school_framework-0.2.2/apps/admissions/urls.py +16 -0
  13. django_school_framework-0.2.2/apps/admissions/views.py +30 -0
  14. django_school_framework-0.2.2/apps/core/__init__.py +0 -0
  15. django_school_framework-0.2.2/apps/core/admin.py +17 -0
  16. django_school_framework-0.2.2/apps/core/apps.py +6 -0
  17. django_school_framework-0.2.2/apps/core/cli.py +1341 -0
  18. django_school_framework-0.2.2/apps/core/compliance.py +236 -0
  19. django_school_framework-0.2.2/apps/core/management/__init__.py +0 -0
  20. django_school_framework-0.2.2/apps/core/management/commands/__init__.py +0 -0
  21. django_school_framework-0.2.2/apps/core/management/commands/seed_school_data.py +281 -0
  22. django_school_framework-0.2.2/apps/core/management/commands/wait_for_db.py +26 -0
  23. django_school_framework-0.2.2/apps/core/migrations/0001_initial.py +55 -0
  24. django_school_framework-0.2.2/apps/core/migrations/0002_alter_schoolprofile_options.py +17 -0
  25. django_school_framework-0.2.2/apps/core/migrations/__init__.py +0 -0
  26. django_school_framework-0.2.2/apps/core/models.py +134 -0
  27. django_school_framework-0.2.2/apps/core/serializers.py +18 -0
  28. django_school_framework-0.2.2/apps/core/templatetags/__init__.py +1 -0
  29. django_school_framework-0.2.2/apps/core/templatetags/compliance_tags.py +13 -0
  30. django_school_framework-0.2.2/apps/core/tests.py +123 -0
  31. django_school_framework-0.2.2/apps/core/urls.py +14 -0
  32. django_school_framework-0.2.2/apps/core/views.py +130 -0
  33. django_school_framework-0.2.2/apps/gallery/__init__.py +0 -0
  34. django_school_framework-0.2.2/apps/gallery/admin.py +18 -0
  35. django_school_framework-0.2.2/apps/gallery/apps.py +6 -0
  36. django_school_framework-0.2.2/apps/gallery/migrations/0001_initial.py +46 -0
  37. django_school_framework-0.2.2/apps/gallery/migrations/__init__.py +0 -0
  38. django_school_framework-0.2.2/apps/gallery/models.py +48 -0
  39. django_school_framework-0.2.2/apps/gallery/serializers.py +14 -0
  40. django_school_framework-0.2.2/apps/gallery/tests.py +31 -0
  41. django_school_framework-0.2.2/apps/gallery/urls.py +11 -0
  42. django_school_framework-0.2.2/apps/gallery/views.py +15 -0
  43. django_school_framework-0.2.2/apps/legal/__init__.py +0 -0
  44. django_school_framework-0.2.2/apps/legal/admin.py +31 -0
  45. django_school_framework-0.2.2/apps/legal/apps.py +6 -0
  46. django_school_framework-0.2.2/apps/legal/migrations/0001_initial.py +105 -0
  47. django_school_framework-0.2.2/apps/legal/migrations/__init__.py +0 -0
  48. django_school_framework-0.2.2/apps/legal/models.py +172 -0
  49. django_school_framework-0.2.2/apps/legal/serializers.py +43 -0
  50. django_school_framework-0.2.2/apps/legal/tests.py +42 -0
  51. django_school_framework-0.2.2/apps/legal/urls.py +17 -0
  52. django_school_framework-0.2.2/apps/legal/views.py +33 -0
  53. django_school_framework-0.2.2/apps/news/__init__.py +0 -0
  54. django_school_framework-0.2.2/apps/news/admin.py +13 -0
  55. django_school_framework-0.2.2/apps/news/apps.py +6 -0
  56. django_school_framework-0.2.2/apps/news/migrations/0001_initial.py +46 -0
  57. django_school_framework-0.2.2/apps/news/migrations/__init__.py +0 -0
  58. django_school_framework-0.2.2/apps/news/models.py +55 -0
  59. django_school_framework-0.2.2/apps/news/serializers.py +14 -0
  60. django_school_framework-0.2.2/apps/news/tests.py +37 -0
  61. django_school_framework-0.2.2/apps/news/urls.py +11 -0
  62. django_school_framework-0.2.2/apps/news/views.py +15 -0
  63. django_school_framework-0.2.2/apps/nutrition/__init__.py +0 -0
  64. django_school_framework-0.2.2/apps/nutrition/admin.py +12 -0
  65. django_school_framework-0.2.2/apps/nutrition/apps.py +6 -0
  66. django_school_framework-0.2.2/apps/nutrition/migrations/0001_initial.py +47 -0
  67. django_school_framework-0.2.2/apps/nutrition/migrations/0002_alter_cateringprovider_options.py +17 -0
  68. django_school_framework-0.2.2/apps/nutrition/migrations/__init__.py +0 -0
  69. django_school_framework-0.2.2/apps/nutrition/models.py +53 -0
  70. django_school_framework-0.2.2/apps/nutrition/serializers.py +17 -0
  71. django_school_framework-0.2.2/apps/nutrition/tests.py +41 -0
  72. django_school_framework-0.2.2/apps/nutrition/urls.py +13 -0
  73. django_school_framework-0.2.2/apps/nutrition/views.py +13 -0
  74. django_school_framework-0.2.2/apps/safety/__init__.py +0 -0
  75. django_school_framework-0.2.2/apps/safety/admin.py +21 -0
  76. django_school_framework-0.2.2/apps/safety/apps.py +6 -0
  77. django_school_framework-0.2.2/apps/safety/migrations/0001_initial.py +48 -0
  78. django_school_framework-0.2.2/apps/safety/migrations/__init__.py +0 -0
  79. django_school_framework-0.2.2/apps/safety/models.py +70 -0
  80. django_school_framework-0.2.2/apps/safety/serializers.py +17 -0
  81. django_school_framework-0.2.2/apps/safety/tests.py +20 -0
  82. django_school_framework-0.2.2/apps/safety/urls.py +11 -0
  83. django_school_framework-0.2.2/apps/safety/views.py +17 -0
  84. django_school_framework-0.2.2/apps/schedule/__init__.py +0 -0
  85. django_school_framework-0.2.2/apps/schedule/admin.py +12 -0
  86. django_school_framework-0.2.2/apps/schedule/apps.py +6 -0
  87. django_school_framework-0.2.2/apps/schedule/migrations/0001_initial.py +49 -0
  88. django_school_framework-0.2.2/apps/schedule/migrations/__init__.py +0 -0
  89. django_school_framework-0.2.2/apps/schedule/models.py +60 -0
  90. django_school_framework-0.2.2/apps/schedule/serializers.py +15 -0
  91. django_school_framework-0.2.2/apps/schedule/tests.py +51 -0
  92. django_school_framework-0.2.2/apps/schedule/urls.py +11 -0
  93. django_school_framework-0.2.2/apps/schedule/views.py +15 -0
  94. django_school_framework-0.2.2/django_school_framework.egg-info/PKG-INFO +53 -0
  95. django_school_framework-0.2.2/django_school_framework.egg-info/SOURCES.txt +99 -0
  96. django_school_framework-0.2.2/django_school_framework.egg-info/dependency_links.txt +1 -0
  97. django_school_framework-0.2.2/django_school_framework.egg-info/entry_points.txt +2 -0
  98. django_school_framework-0.2.2/django_school_framework.egg-info/requires.txt +14 -0
  99. django_school_framework-0.2.2/django_school_framework.egg-info/top_level.txt +1 -0
  100. django_school_framework-0.2.2/pyproject.toml +44 -0
  101. 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
@@ -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,6 @@
1
+ from django.apps import AppConfig
2
+
3
+ class AdmissionsConfig(AppConfig):
4
+ default_auto_field = 'django.db.models.BigAutoField'
5
+ name = 'apps.admissions'
6
+ verbose_name = 'Зарахування учнів, територія та вільні місця'
@@ -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
+ ]
@@ -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')
@@ -0,0 +1,6 @@
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 = 'Загальні відомості та налаштування школи'