Compare commits
10 Commits
63228923a7
...
ead0ee40ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ead0ee40ee | ||
|
|
d8545bb7cf | ||
|
|
a0a337045b | ||
|
|
59df2c55d9 | ||
|
|
5623d01134 | ||
|
|
ba4df6674b | ||
|
|
f1dd7fa22a | ||
|
|
c0865fdf51 | ||
|
|
30ef912c0b | ||
|
|
d70c5f73f5 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/db.sqlite3
|
||||
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
17
.idea/dataSources.xml
generated
Normal file
17
.idea/dataSources.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="db" uuid="018e0f6c-5916-4f25-8264-dc6301fd54fd">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:C:\Users\Mikan\PycharmProjects\tisbiProFi\db.sqlite3</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
<libraries>
|
||||
<library>
|
||||
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.43.0/org/xerial/sqlite-jdbc/3.43.0.0/sqlite-jdbc-3.43.0.0.jar</url>
|
||||
</library>
|
||||
</libraries>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
@@ -2,5 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/frontend/react-source" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,15 +0,0 @@
|
||||
from django.db import models
|
||||
|
||||
class ContactMixin(models.Model):
|
||||
first_name = models.CharField(max_length=100, verbose_name="Имя")
|
||||
last_name = models.CharField(max_length=100, verbose_name="Фамилия")
|
||||
middle_name = models.CharField(max_length=100, blank=True, null=True, verbose_name="Отчество")
|
||||
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name="Телефон")
|
||||
email = models.EmailField(blank=True, null=True, verbose_name="Почта")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.last_name} {self.first_name}"
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from django.db import models
|
||||
|
||||
class ContactMixin(models.Model):
|
||||
first_name = models.CharField(max_length=100, verbose_name="Имя")
|
||||
last_name = models.CharField(max_length=100, verbose_name="Фамилия", blank=True, null=True)
|
||||
middle_name = models.CharField(max_length=100, blank=True, null=True, verbose_name="Отчество")
|
||||
phone = models.CharField(max_length=20, blank=True, null=True, verbose_name="Телефон")
|
||||
email = models.EmailField(blank=True, null=True, verbose_name="Почта")
|
||||
|
||||
def __str__(self):
|
||||
return " ".join(filter(bool, [self.last_name, self.first_name]))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
0
education/__init__.py
Normal file
0
education/__init__.py
Normal file
58
education/admin.py
Normal file
58
education/admin.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from django.contrib import admin
|
||||
from .models import AcademicYear, Department, DepartmentHead, Specialty, Group, Student
|
||||
|
||||
|
||||
@admin.register(AcademicYear)
|
||||
class AcademicYearAdmin(admin.ModelAdmin):
|
||||
list_display = ('year',)
|
||||
search_fields = ('year',)
|
||||
|
||||
|
||||
@admin.register(Department)
|
||||
class DepartmentAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
@admin.register(DepartmentHead)
|
||||
class DepartmentHeadAdmin(admin.ModelAdmin):
|
||||
list_display = ('department', 'head')
|
||||
list_filter = ('department',)
|
||||
autocomplete_fields = ('department', 'head')
|
||||
search_fields = ('department__name', 'head__account__first_name', 'head__account__last_name')
|
||||
|
||||
|
||||
@admin.register(Specialty)
|
||||
class SpecialtyAdmin(admin.ModelAdmin):
|
||||
list_display = ('code', 'name', 'department')
|
||||
list_filter = ('department',)
|
||||
search_fields = ('code', 'name', 'department__name')
|
||||
|
||||
|
||||
@admin.register(Group)
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'specialty', 'curator')
|
||||
list_filter = ('specialty__department', 'specialty')
|
||||
search_fields = ('name', 'specialty__name', 'curator__account__first_name', 'curator__account__last_name')
|
||||
autocomplete_fields = ('specialty', 'curator')
|
||||
|
||||
|
||||
@admin.register(Student)
|
||||
class StudentAdmin(admin.ModelAdmin):
|
||||
list_display = ('full_name', 'group', 'account')
|
||||
list_filter = ('group__specialty__department', 'group__specialty', 'group')
|
||||
search_fields = (
|
||||
'account__first_name', 'account__last_name', 'account__middle_name',
|
||||
'group__name', 'account__login'
|
||||
)
|
||||
autocomplete_fields = ('group', 'account')
|
||||
|
||||
@admin.display(description='ФИО')
|
||||
def full_name(self, obj):
|
||||
return str(obj)
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('account', 'group')
|
||||
}),
|
||||
)
|
||||
7
education/apps.py
Normal file
7
education/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class EducationConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'education'
|
||||
verbose_name = "Учебный процесс"
|
||||
57
education/filters.py
Normal file
57
education/filters.py
Normal file
@@ -0,0 +1,57 @@
|
||||
import django_filters
|
||||
from .models import AcademicYear, Department, DepartmentHead, Specialty, Group, Student
|
||||
|
||||
class AcademicYearFilter(django_filters.FilterSet):
|
||||
year = django_filters.CharFilter(lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = AcademicYear
|
||||
fields = ["year"]
|
||||
|
||||
|
||||
class DepartmentFilter(django_filters.FilterSet):
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = Department
|
||||
fields = ["name"]
|
||||
|
||||
|
||||
class DepartmentHeadFilter(django_filters.FilterSet):
|
||||
department_name = django_filters.CharFilter(field_name="department__name", lookup_expr="icontains")
|
||||
head_name = django_filters.CharFilter(field_name="head__first_name", lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = DepartmentHead
|
||||
fields = ["department_name", "head_name"]
|
||||
|
||||
|
||||
class SpecialtyFilter(django_filters.FilterSet):
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
code = django_filters.CharFilter(lookup_expr="icontains")
|
||||
department_name = django_filters.CharFilter(field_name="department__name", lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = Specialty
|
||||
fields = ["name", "code", "department_name"]
|
||||
|
||||
|
||||
class GroupFilter(django_filters.FilterSet):
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
curator_name = django_filters.CharFilter(field_name="curator__first_name", lookup_expr="icontains")
|
||||
specialty_name = django_filters.CharFilter(field_name="specialty__name", lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ["name", "curator_name", "specialty_name"]
|
||||
|
||||
|
||||
class StudentFilter(django_filters.FilterSet):
|
||||
first_name = django_filters.CharFilter(field_name="account__first_name", lookup_expr="icontains")
|
||||
last_name = django_filters.CharFilter(field_name="account__last_name", lookup_expr="icontains")
|
||||
group_name = django_filters.CharFilter(field_name="group__name", lookup_expr="icontains")
|
||||
specialty_name = django_filters.CharFilter(field_name="group__specialty__name", lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = Student
|
||||
fields = ["first_name", "last_name", "group_name", "specialty_name"]
|
||||
84
education/migrations/0001_initial.py
Normal file
84
education/migrations/0001_initial.py
Normal file
@@ -0,0 +1,84 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AcademicYear',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('year', models.CharField(max_length=9, verbose_name='Год')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Учебный год',
|
||||
'verbose_name_plural': 'Учебные годы',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Department',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, verbose_name='Название отделения')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Учебное отделение',
|
||||
'verbose_name_plural': 'Учебные отделения',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DepartmentHead',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Руководитель отделения',
|
||||
'verbose_name_plural': 'Руководители отделений',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Group',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=20, verbose_name='Название группы')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Группа',
|
||||
'verbose_name_plural': 'Группы',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Specialty',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Название специальности')),
|
||||
('code', models.CharField(max_length=20, verbose_name='Код специальности')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Специальность',
|
||||
'verbose_name_plural': 'Специальности',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Student',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=100, verbose_name='Имя')),
|
||||
('last_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Фамилия')),
|
||||
('middle_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Отчество')),
|
||||
('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Телефон')),
|
||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Почта')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Студент',
|
||||
'verbose_name_plural': 'Студенты',
|
||||
},
|
||||
),
|
||||
]
|
||||
58
education/migrations/0002_initial.py
Normal file
58
education/migrations/0002_initial.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:14
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('employees', '0001_initial'),
|
||||
('education', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='student',
|
||||
name='account',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Аккаунт'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='student',
|
||||
name='group',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.group', verbose_name='Группа'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='specialty',
|
||||
name='department',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.department', verbose_name='Учебное отделение'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='curator',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='employees.employee', verbose_name='Куратор'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='group',
|
||||
name='specialty',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.specialty', verbose_name='Специальность'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='departmenthead',
|
||||
name='department',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.department', verbose_name='Учебное отделение'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='departmenthead',
|
||||
name='head',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='employees.employee', verbose_name='Руководитель'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='departmenthead',
|
||||
unique_together={('department', 'head')},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:56
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('education', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='student',
|
||||
name='email',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='student',
|
||||
name='first_name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='student',
|
||||
name='last_name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='student',
|
||||
name='middle_name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='student',
|
||||
name='phone',
|
||||
),
|
||||
]
|
||||
0
education/migrations/__init__.py
Normal file
0
education/migrations/__init__.py
Normal file
135
education/models.py
Normal file
135
education/models.py
Normal file
@@ -0,0 +1,135 @@
|
||||
from django.db import models
|
||||
from core.models import ContactMixin # Предполагается, что ContactMixin находится в приложении core
|
||||
from employees.models import Employee # Предполагается, что модель Employee находится в приложении employees
|
||||
from users.models import Account # Предполагается, что модель Account находится в приложении users
|
||||
|
||||
## Учебный процесс (название приложения: education)
|
||||
|
||||
class AcademicYear(models.Model):
|
||||
year = models.CharField(max_length=9, verbose_name="Год") # Например, "2023-2024"
|
||||
|
||||
def __str__(self):
|
||||
return self.year
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Учебный год"
|
||||
verbose_name_plural = "Учебные годы"
|
||||
ordering = ['-year']
|
||||
|
||||
|
||||
class Department(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name="Название отделения")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Учебное отделение"
|
||||
verbose_name_plural = "Учебные отделения"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class DepartmentHead(models.Model):
|
||||
department = models.ForeignKey(
|
||||
Department, on_delete=models.CASCADE, verbose_name="Учебное отделение"
|
||||
)
|
||||
head = models.OneToOneField(
|
||||
Employee, on_delete=models.CASCADE, verbose_name="Руководитель"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.head} - {self.department}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Руководитель отделения"
|
||||
verbose_name_plural = "Руководители отделений"
|
||||
unique_together = ("department", "head")
|
||||
ordering = ["department", "head"]
|
||||
|
||||
|
||||
class Specialty(models.Model):
|
||||
name = models.CharField(max_length=200, verbose_name="Название специальности")
|
||||
code = models.CharField(max_length=20, verbose_name="Код специальности")
|
||||
department = models.ForeignKey(
|
||||
Department, on_delete=models.CASCADE, verbose_name="Учебное отделение"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.code} - {self.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Специальность"
|
||||
verbose_name_plural = "Специальности"
|
||||
ordering = ["code"]
|
||||
|
||||
|
||||
class Group(models.Model):
|
||||
name = models.CharField(max_length=20, verbose_name="Название группы")
|
||||
curator = models.ForeignKey(
|
||||
Employee, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Куратор"
|
||||
)
|
||||
specialty = models.ForeignKey(
|
||||
Specialty, on_delete=models.CASCADE, verbose_name="Специальность"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Студенческая группа"
|
||||
verbose_name_plural = "Студенческие группы"
|
||||
ordering = ["name", "specialty"]
|
||||
|
||||
|
||||
class Student(models.Model):
|
||||
group = models.ForeignKey(
|
||||
Group, on_delete=models.CASCADE, verbose_name="Группа"
|
||||
)
|
||||
account = models.OneToOneField(
|
||||
Account, on_delete=models.CASCADE, unique=True, verbose_name="Аккаунт"
|
||||
)
|
||||
|
||||
@property
|
||||
def first_name(self):
|
||||
return self.account.first_name
|
||||
@first_name.setter
|
||||
def first_name(self, value):
|
||||
self.account.first_name = value
|
||||
|
||||
@property
|
||||
def last_name(self):
|
||||
return self.account.last_name
|
||||
@last_name.setter
|
||||
def last_name(self, value):
|
||||
self.account.last_name = value
|
||||
|
||||
@property
|
||||
def middle_name(self):
|
||||
return self.account.middle_name
|
||||
@middle_name.setter
|
||||
def middle_name(self, value):
|
||||
self.account.middle_name = value
|
||||
|
||||
@property
|
||||
def phone(self):
|
||||
return self.account.phone
|
||||
@phone.setter
|
||||
def phone(self, value):
|
||||
self.account.phone = value
|
||||
|
||||
@property
|
||||
def email(self):
|
||||
return self.account.email
|
||||
@email.setter
|
||||
def email(self, value):
|
||||
self.account.email = value
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.last_name} {self.first_name} - {self.group}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Студент"
|
||||
verbose_name_plural = "Студенты"
|
||||
ordering = ["group", "account"]
|
||||
|
||||
75
education/serializers.py
Normal file
75
education/serializers.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from rest_framework import serializers
|
||||
from .models import AcademicYear, Department, DepartmentHead, Specialty, Group, Student
|
||||
|
||||
class AcademicYearSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AcademicYear
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class StudyDepartmentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Department
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class StudyDepartmentHeadSerializer(serializers.ModelSerializer):
|
||||
head_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = DepartmentHead
|
||||
fields = ["id", "department", "head", "head_name"]
|
||||
|
||||
def get_head_name(self, obj):
|
||||
return f"{obj.head.first_name} {obj.head.last_name}"
|
||||
|
||||
|
||||
class SpecialtySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Specialty
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class GroupSerializer(serializers.ModelSerializer):
|
||||
curator_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ["id", "name", "curator", "specialty", "curator_name"]
|
||||
|
||||
def get_curator_name(self, obj):
|
||||
if obj.curator:
|
||||
return f"{obj.curator.first_name} {obj.curator.last_name}"
|
||||
return None
|
||||
|
||||
|
||||
class StudentSerializer(serializers.ModelSerializer):
|
||||
first_name = serializers.CharField(source="account.first_name")
|
||||
last_name = serializers.CharField(source="account.last_name")
|
||||
middle_name = serializers.CharField(source="account.middle_name")
|
||||
phone = serializers.CharField(source="account.phone")
|
||||
email = serializers.CharField(source="account.email")
|
||||
|
||||
class Meta:
|
||||
model = Student
|
||||
fields = [
|
||||
"id",
|
||||
"group",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"middle_name",
|
||||
"phone",
|
||||
"email",
|
||||
]
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
account_data = validated_data.pop("account", {})
|
||||
instance.account.first_name = account_data.get("first_name", instance.account.first_name)
|
||||
instance.account.last_name = account_data.get("last_name", instance.account.last_name)
|
||||
instance.account.middle_name = account_data.get("middle_name", instance.account.middle_name)
|
||||
instance.account.phone = account_data.get("phone", instance.account.phone)
|
||||
instance.account.email = account_data.get("email", instance.account.email)
|
||||
instance.account.save()
|
||||
return super().update(instance, validated_data)
|
||||
|
||||
|
||||
3
education/tests.py
Normal file
3
education/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
22
education/urls.py
Normal file
22
education/urls.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import (
|
||||
AcademicYearViewSet,
|
||||
DepartmentViewSet,
|
||||
DepartmentHeadViewSet,
|
||||
SpecialtyViewSet,
|
||||
GroupViewSet,
|
||||
StudentViewSet,
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"academic-years", AcademicYearViewSet, basename="academic-year")
|
||||
router.register(r"departments", DepartmentViewSet, basename="department")
|
||||
router.register(r"department-heads", DepartmentHeadViewSet, basename="department-head")
|
||||
router.register(r"specialties", SpecialtyViewSet, basename="specialty")
|
||||
router.register(r"groups", GroupViewSet, basename="group")
|
||||
router.register(r"students", StudentViewSet, basename="student")
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
73
education/views.py
Normal file
73
education/views.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from .models import AcademicYear, Department, DepartmentHead, Specialty, Group, Student
|
||||
from .serializers import (
|
||||
AcademicYearSerializer,
|
||||
StudyDepartmentSerializer,
|
||||
StudyDepartmentHeadSerializer,
|
||||
SpecialtySerializer,
|
||||
GroupSerializer,
|
||||
StudentSerializer,
|
||||
)
|
||||
from .filters import (
|
||||
AcademicYearFilter,
|
||||
DepartmentFilter,
|
||||
DepartmentHeadFilter,
|
||||
SpecialtyFilter,
|
||||
GroupFilter,
|
||||
StudentFilter,
|
||||
)
|
||||
|
||||
class AcademicYearViewSet(viewsets.ModelViewSet):
|
||||
queryset = AcademicYear.objects.all()
|
||||
serializer_class = AcademicYearSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = AcademicYearFilter
|
||||
search_fields = ["year"]
|
||||
ordering_fields = ["year"]
|
||||
|
||||
|
||||
class DepartmentViewSet(viewsets.ModelViewSet):
|
||||
queryset = Department.objects.all()
|
||||
serializer_class = StudyDepartmentSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = DepartmentFilter
|
||||
search_fields = ["name"]
|
||||
ordering_fields = ["name"]
|
||||
|
||||
|
||||
class DepartmentHeadViewSet(viewsets.ModelViewSet):
|
||||
queryset = DepartmentHead.objects.all()
|
||||
serializer_class = StudyDepartmentHeadSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = DepartmentHeadFilter
|
||||
search_fields = ["department__name", "head__first_name"]
|
||||
ordering_fields = ["department", "head"]
|
||||
|
||||
|
||||
class SpecialtyViewSet(viewsets.ModelViewSet):
|
||||
queryset = Specialty.objects.all()
|
||||
serializer_class = SpecialtySerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = SpecialtyFilter
|
||||
search_fields = ["name", "code", "department__name"]
|
||||
ordering_fields = ["name", "code"]
|
||||
|
||||
|
||||
class GroupViewSet(viewsets.ModelViewSet):
|
||||
queryset = Group.objects.all()
|
||||
serializer_class = GroupSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = GroupFilter
|
||||
search_fields = ["name", "curator__first_name", "specialty__name"]
|
||||
ordering_fields = ["name", "specialty"]
|
||||
|
||||
|
||||
class StudentViewSet(viewsets.ModelViewSet):
|
||||
queryset = Student.objects.all()
|
||||
serializer_class = StudentSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = StudentFilter
|
||||
search_fields = ["account__first_name", "account__last_name", "group__name"]
|
||||
ordering_fields = ["group", "account__last_name"]
|
||||
0
employees/__init__.py
Normal file
0
employees/__init__.py
Normal file
35
employees/admin.py
Normal file
35
employees/admin.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from django.contrib import admin
|
||||
from .models import Position, Department, Employee
|
||||
|
||||
|
||||
@admin.register(Position)
|
||||
class PositionAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
@admin.register(Department)
|
||||
class DepartmentAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
@admin.register(Employee)
|
||||
class EmployeeAdmin(admin.ModelAdmin):
|
||||
list_display = ('full_name', 'position', 'department', 'account')
|
||||
list_filter = ('department', 'position')
|
||||
search_fields = (
|
||||
'account__first_name', 'account__last_name', 'account__middle_name',
|
||||
'account__phone', 'account__email', 'position__name', 'department__name'
|
||||
)
|
||||
autocomplete_fields = ('department', 'position', 'account')
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('account', 'department', 'position')
|
||||
}),
|
||||
)
|
||||
|
||||
@admin.display(description='ФИО')
|
||||
def full_name(self, obj):
|
||||
return str(obj)
|
||||
8
employees/apps.py
Normal file
8
employees/apps.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class EmployeesConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'employees'
|
||||
verbose_name = "Структура колледжа"
|
||||
|
||||
12
employees/filters.py
Normal file
12
employees/filters.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import django_filters
|
||||
from .models import Employee
|
||||
|
||||
class EmployeeFilter(django_filters.FilterSet):
|
||||
first_name = django_filters.CharFilter(field_name="account__first_name", lookup_expr="icontains")
|
||||
last_name = django_filters.CharFilter(field_name="account__last_name", lookup_expr="icontains")
|
||||
department_name = django_filters.CharFilter(field_name="department__name", lookup_expr="icontains")
|
||||
position_name = django_filters.CharFilter(field_name="position__name", lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = Employee
|
||||
fields = ["first_name", "last_name", "department_name", "position_name"]
|
||||
51
employees/migrations/0001_initial.py
Normal file
51
employees/migrations/0001_initial.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Department',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, verbose_name='Название подразделения')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Подразделение',
|
||||
'verbose_name_plural': 'Подразделения',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Employee',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=100, verbose_name='Имя')),
|
||||
('last_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Фамилия')),
|
||||
('middle_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Отчество')),
|
||||
('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Телефон')),
|
||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Почта')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Сотрудник',
|
||||
'verbose_name_plural': 'Сотрудники',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Position',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, verbose_name='Название должности')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Должность',
|
||||
'verbose_name_plural': 'Должности',
|
||||
},
|
||||
),
|
||||
]
|
||||
33
employees/migrations/0002_initial.py
Normal file
33
employees/migrations/0002_initial.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:14
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('employees', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='employee',
|
||||
name='account',
|
||||
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='Аккаунт'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='employee',
|
||||
name='department',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='employees.department', verbose_name='Подразделение'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='employee',
|
||||
name='position',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='employees.position', verbose_name='Должность'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:56
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('employees', '0002_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='employee',
|
||||
name='email',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='employee',
|
||||
name='first_name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='employee',
|
||||
name='last_name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='employee',
|
||||
name='middle_name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='employee',
|
||||
name='phone',
|
||||
),
|
||||
]
|
||||
0
employees/migrations/__init__.py
Normal file
0
employees/migrations/__init__.py
Normal file
89
employees/models.py
Normal file
89
employees/models.py
Normal file
@@ -0,0 +1,89 @@
|
||||
from django.db import models
|
||||
from core.models import ContactMixin # Предполагается, что ContactMixin находится в приложении core
|
||||
|
||||
## Учет сотрудников (название приложения: employees)
|
||||
|
||||
class Position(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name="Название должности")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Должность"
|
||||
verbose_name_plural = "Должности"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class Department(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name="Название подразделения")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Подразделение"
|
||||
verbose_name_plural = "Подразделения"
|
||||
ordering = ["name"]
|
||||
|
||||
class Employee(models.Model):
|
||||
department = models.ForeignKey(
|
||||
Department, on_delete=models.CASCADE, verbose_name="Подразделение"
|
||||
)
|
||||
position = models.ForeignKey(
|
||||
Position, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Должность"
|
||||
)
|
||||
account = models.OneToOneField(
|
||||
"users.Account", on_delete=models.CASCADE, unique=True, verbose_name="Аккаунт"
|
||||
)
|
||||
|
||||
@property
|
||||
def first_name(self):
|
||||
return self.account.first_name
|
||||
|
||||
@first_name.setter
|
||||
def first_name(self, value):
|
||||
self.account.first_name = value
|
||||
|
||||
@property
|
||||
def last_name(self):
|
||||
return self.account.last_name
|
||||
|
||||
@last_name.setter
|
||||
def last_name(self, value):
|
||||
self.account.last_name = value
|
||||
|
||||
@property
|
||||
def middle_name(self):
|
||||
return self.account.middle_name
|
||||
|
||||
@middle_name.setter
|
||||
def middle_name(self, value):
|
||||
self.account.middle_name = value
|
||||
|
||||
@property
|
||||
def phone(self):
|
||||
return self.account.phone
|
||||
|
||||
@phone.setter
|
||||
def phone(self, value):
|
||||
self.account.phone = value
|
||||
|
||||
@property
|
||||
def email(self):
|
||||
return self.account.email
|
||||
|
||||
@email.setter
|
||||
def email(self, value):
|
||||
self.account.email = value
|
||||
|
||||
def __str__(self):
|
||||
|
||||
full_name = " ".join(filter(bool, [self.last_name, self.first_name]))
|
||||
|
||||
return f"{full_name} - {self.position}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Сотрудник"
|
||||
verbose_name_plural = "Сотрудники"
|
||||
ordering = ["department", "position", "account"]
|
||||
44
employees/serializers.py
Normal file
44
employees/serializers.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Position, Department, Employee
|
||||
|
||||
class PositionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Position
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class DepartmentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Department
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class EmployeeSerializer(serializers.ModelSerializer):
|
||||
first_name = serializers.CharField(source="account.first_name")
|
||||
last_name = serializers.CharField(source="account.last_name")
|
||||
middle_name = serializers.CharField(source="account.middle_name", allow_null=True)
|
||||
phone = serializers.CharField(source="account.phone", allow_null=True)
|
||||
email = serializers.CharField(source="account.email")
|
||||
|
||||
class Meta:
|
||||
model = Employee
|
||||
fields = [
|
||||
"id",
|
||||
"department",
|
||||
"position",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"middle_name",
|
||||
"phone",
|
||||
"email",
|
||||
]
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
account_data = validated_data.pop("account", {})
|
||||
instance.account.first_name = account_data.get("first_name", instance.account.first_name)
|
||||
instance.account.last_name = account_data.get("last_name", instance.account.last_name)
|
||||
instance.account.middle_name = account_data.get("middle_name", instance.account.middle_name)
|
||||
instance.account.phone = account_data.get("phone", instance.account.phone)
|
||||
instance.account.email = account_data.get("email", instance.account.email)
|
||||
instance.account.save()
|
||||
return super().update(instance, validated_data)
|
||||
3
employees/tests.py
Normal file
3
employees/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
12
employees/urls.py
Normal file
12
employees/urls.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import PositionViewSet, DepartmentViewSet, EmployeeViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"positions", PositionViewSet, basename="position")
|
||||
router.register(r"departments", DepartmentViewSet, basename="department")
|
||||
router.register(r"employees", EmployeeViewSet, basename="employee")
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
30
employees/views.py
Normal file
30
employees/views.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from .models import Position, Department, Employee
|
||||
from .serializers import PositionSerializer, DepartmentSerializer, EmployeeSerializer
|
||||
from .filters import EmployeeFilter # Фильтры будут добавлены позже
|
||||
|
||||
class PositionViewSet(viewsets.ModelViewSet):
|
||||
queryset = Position.objects.all()
|
||||
serializer_class = PositionSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
search_fields = ["name"]
|
||||
ordering_fields = ["name"]
|
||||
|
||||
|
||||
class DepartmentViewSet(viewsets.ModelViewSet):
|
||||
queryset = Department.objects.all()
|
||||
serializer_class = DepartmentSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
search_fields = ["name"]
|
||||
ordering_fields = ["name"]
|
||||
|
||||
|
||||
class EmployeeViewSet(viewsets.ModelViewSet):
|
||||
queryset = Employee.objects.all()
|
||||
serializer_class = EmployeeSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = EmployeeFilter
|
||||
search_fields = ["account__first_name", "account__last_name", "position__name", "department__name"]
|
||||
ordering_fields = ["department", "position", "account__last_name"]
|
||||
0
events/__init__.py
Normal file
0
events/__init__.py
Normal file
117
events/admin.py
Normal file
117
events/admin.py
Normal file
@@ -0,0 +1,117 @@
|
||||
from django.contrib import admin
|
||||
from .models import (
|
||||
EventStatus, ActivityType, Event, EventOrganizer,
|
||||
EventDetail, EventParticipant, MethodicalFile, RelevantMethodicalFile
|
||||
)
|
||||
|
||||
|
||||
@admin.register(EventStatus)
|
||||
class EventStatusAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
@admin.register(ActivityType)
|
||||
class ActivityTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
class EventOrganizerInline(admin.TabularInline):
|
||||
model = EventOrganizer
|
||||
extra = 1
|
||||
autocomplete_fields = ('employee',)
|
||||
|
||||
|
||||
class EventDetailInline(admin.TabularInline):
|
||||
model = EventDetail
|
||||
extra = 1
|
||||
autocomplete_fields = ('activity_type',)
|
||||
|
||||
|
||||
class EventParticipantInline(admin.TabularInline):
|
||||
model = EventParticipant
|
||||
extra = 1
|
||||
autocomplete_fields = ('student',)
|
||||
|
||||
|
||||
@admin.register(Event)
|
||||
class EventAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'date', 'location', 'status', 'academic_year')
|
||||
list_filter = ('status', 'academic_year', 'date')
|
||||
search_fields = ('name', 'location')
|
||||
inlines = [EventOrganizerInline, EventDetailInline, EventParticipantInline]
|
||||
autocomplete_fields = ('academic_year', 'status')
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('name', 'academic_year', 'status', 'date', 'location')
|
||||
}),
|
||||
('Дополнительно', {
|
||||
'fields': ('description',)
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@admin.register(EventOrganizer)
|
||||
class EventOrganizerAdmin(admin.ModelAdmin):
|
||||
list_display = ('event', 'employee')
|
||||
list_filter = ('event__academic_year',)
|
||||
search_fields = ('employee__account__first_name', 'employee__account__last_name', 'event__name')
|
||||
autocomplete_fields = ('employee', 'event')
|
||||
|
||||
|
||||
@admin.register(EventDetail)
|
||||
class EventDetailAdmin(admin.ModelAdmin):
|
||||
list_display = ('event', 'activity_type')
|
||||
list_filter = ('activity_type',)
|
||||
search_fields = ('event__name', 'activity_type__name')
|
||||
autocomplete_fields = ('event', 'activity_type')
|
||||
|
||||
|
||||
@admin.register(EventParticipant)
|
||||
class EventParticipantAdmin(admin.ModelAdmin):
|
||||
list_display = ('student_full_name', 'event', 'points')
|
||||
list_filter = ('event__academic_year', 'event')
|
||||
search_fields = (
|
||||
'student__account__first_name', 'student__account__last_name',
|
||||
'event__name'
|
||||
)
|
||||
autocomplete_fields = ('student', 'event')
|
||||
|
||||
@admin.display(description='ФИО студента')
|
||||
def student_full_name(self, obj):
|
||||
return str(obj.student)
|
||||
|
||||
|
||||
|
||||
class RelevantMethodicalFileInline(admin.TabularInline):
|
||||
model = RelevantMethodicalFile
|
||||
extra = 1
|
||||
autocomplete_fields = ('activity_type',)
|
||||
|
||||
@admin.register(MethodicalFile)
|
||||
class MethodicalFileAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'specialty', 'upload_date', 'employee')
|
||||
list_filter = ('specialty', 'upload_date')
|
||||
search_fields = ('name', 'specialty__name', 'employee__account__first_name', 'employee__account__last_name')
|
||||
autocomplete_fields = ('specialty', 'employee')
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('name', 'specialty', 'file_path')
|
||||
}),
|
||||
('Дополнительно', {
|
||||
'fields': ('employee',)
|
||||
}),
|
||||
)
|
||||
|
||||
inlines = [RelevantMethodicalFileInline]
|
||||
|
||||
|
||||
@admin.register(RelevantMethodicalFile)
|
||||
class RelevantMethodicalFileAdmin(admin.ModelAdmin):
|
||||
list_display = ('methodical_file', 'activity_type')
|
||||
list_filter = ('activity_type',)
|
||||
search_fields = ('methodical_file__name', 'activity_type__name')
|
||||
autocomplete_fields = ('methodical_file', 'activity_type')
|
||||
7
events/apps.py
Normal file
7
events/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class EventsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'events'
|
||||
verbose_name = 'Мероприятия'
|
||||
34
events/filters.py
Normal file
34
events/filters.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import django_filters
|
||||
from .models import Event, EventParticipant, MethodicalFile
|
||||
|
||||
class EventFilter(django_filters.FilterSet):
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
location = django_filters.CharFilter(lookup_expr="icontains")
|
||||
date_after = django_filters.DateFilter(field_name="date", lookup_expr="gte")
|
||||
date_before = django_filters.DateFilter(field_name="date", lookup_expr="lte")
|
||||
|
||||
class Meta:
|
||||
model = Event
|
||||
fields = ["name", "location", "date_after", "date_before"]
|
||||
|
||||
|
||||
class EventParticipantFilter(django_filters.FilterSet):
|
||||
student_name = django_filters.CharFilter(
|
||||
field_name="student__account__first_name", lookup_expr="icontains"
|
||||
)
|
||||
event_name = django_filters.CharFilter(field_name="event__name", lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = EventParticipant
|
||||
fields = ["student_name", "event_name", "points"]
|
||||
|
||||
|
||||
class MethodicalFileFilter(django_filters.FilterSet):
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
specialty_name = django_filters.CharFilter(
|
||||
field_name="specialty__name", lookup_expr="icontains"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = MethodicalFile
|
||||
fields = ["name", "specialty_name", "upload_date"]
|
||||
125
events/migrations/0001_initial.py
Normal file
125
events/migrations/0001_initial.py
Normal file
@@ -0,0 +1,125 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:14
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('employees', '0001_initial'),
|
||||
('education', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ActivityType',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, verbose_name='Название вида деятельности')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Деятельность мероприятия',
|
||||
'verbose_name_plural': 'Деятельности мероприятий',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Event',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Название')),
|
||||
('location', models.CharField(blank=True, max_length=255, null=True, verbose_name='Место проведения')),
|
||||
('date', models.DateField(verbose_name='Дата проведения')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
||||
('academic_year', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.academicyear', verbose_name='Учебный год')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Мероприятие',
|
||||
'verbose_name_plural': 'Мероприятия',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventStatus',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100, verbose_name='Название статуса')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Статус проведения',
|
||||
'verbose_name_plural': 'Статусы проведения',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='MethodicalFile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Название')),
|
||||
('file_path', models.FileField(max_length=500, upload_to='', verbose_name='Файл')),
|
||||
('upload_date', models.DateField(auto_now_add=True, verbose_name='Дата добавления')),
|
||||
('employee', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='employees.employee', verbose_name='Сотрудник')),
|
||||
('specialty', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.specialty', verbose_name='Специальность')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Методический файл',
|
||||
'verbose_name_plural': 'Методические файлы',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RelevantMethodicalFile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('activity_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.activitytype', verbose_name='Вид деятельности')),
|
||||
('methodical_file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.methodicalfile', verbose_name='Методический файл')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Релевантный методический файл',
|
||||
'verbose_name_plural': 'Релевантные методические файлы',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventParticipant',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('points', models.PositiveIntegerField(blank=True, default=0, verbose_name='Баллы за участие')),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.event', verbose_name='Мероприятие')),
|
||||
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.student', verbose_name='Студент')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Участник мероприятия',
|
||||
'verbose_name_plural': 'Участники мероприятий',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='event',
|
||||
name='status',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='events.eventstatus', verbose_name='Статус мероприятия'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventOrganizer',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='employees.employee', verbose_name='Сотрудник-организатор')),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.event', verbose_name='Мероприятие')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Организатор мероприятия',
|
||||
'verbose_name_plural': 'Организаторы мероприятий',
|
||||
'unique_together': {('employee', 'event')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EventDetail',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('activity_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.activitytype', verbose_name='Вид деятельности')),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.event', verbose_name='Мероприятие')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Деталь мероприятия',
|
||||
'verbose_name_plural': 'Детали мероприятий',
|
||||
'unique_together': {('activity_type', 'event')},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
events/migrations/__init__.py
Normal file
0
events/migrations/__init__.py
Normal file
135
events/models.py
Normal file
135
events/models.py
Normal file
@@ -0,0 +1,135 @@
|
||||
from django.db import models
|
||||
from education.models import AcademicYear, Specialty, Student # Предполагается, что модели находятся в приложении education
|
||||
from employees.models import Employee # Предполагается, что модель Employee находится в приложении employees
|
||||
|
||||
## Учет мероприятий (название приложения: events)
|
||||
|
||||
class EventStatus(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name="Название статуса")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Статус проведения"
|
||||
verbose_name_plural = "Статусы проведения"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class ActivityType(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name="Название вида деятельности")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Деятельность мероприятия"
|
||||
verbose_name_plural = "Деятельности мероприятий"
|
||||
ordering = ["name"]
|
||||
|
||||
class Event(models.Model):
|
||||
academic_year = models.ForeignKey(
|
||||
AcademicYear, on_delete=models.CASCADE, verbose_name="Учебный год"
|
||||
)
|
||||
status = models.ForeignKey(
|
||||
EventStatus, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Статус мероприятия"
|
||||
)
|
||||
name = models.CharField(max_length=200, verbose_name="Название")
|
||||
location = models.CharField(max_length=255, blank=True, null=True, verbose_name="Место проведения")
|
||||
date = models.DateField(verbose_name="Дата проведения")
|
||||
description = models.TextField(blank=True, null=True, verbose_name="Описание")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.date})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Мероприятие"
|
||||
verbose_name_plural = "Мероприятия"
|
||||
ordering = ["date", "name"]
|
||||
|
||||
class EventOrganizer(models.Model):
|
||||
employee = models.ForeignKey(
|
||||
Employee, on_delete=models.CASCADE, verbose_name="Сотрудник-организатор"
|
||||
)
|
||||
event = models.ForeignKey(
|
||||
Event, on_delete=models.CASCADE, verbose_name="Мероприятие"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.employee} - {self.event}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Организатор мероприятия"
|
||||
verbose_name_plural = "Организаторы мероприятий"
|
||||
unique_together = ("employee", "event")
|
||||
ordering = ["employee", "event"]
|
||||
|
||||
class EventDetail(models.Model):
|
||||
activity_type = models.ForeignKey(
|
||||
ActivityType, on_delete=models.CASCADE, verbose_name="Вид деятельности"
|
||||
)
|
||||
event = models.ForeignKey(
|
||||
Event, on_delete=models.CASCADE, verbose_name="Мероприятие"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.activity_type} - {self.event}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Деталь мероприятия"
|
||||
verbose_name_plural = "Детали мероприятий"
|
||||
unique_together = ("activity_type", "event")
|
||||
ordering = ["activity_type", "event"]
|
||||
|
||||
class EventParticipant(models.Model):
|
||||
student = models.ForeignKey(
|
||||
Student, on_delete=models.CASCADE, verbose_name="Студент"
|
||||
)
|
||||
event = models.ForeignKey(
|
||||
Event, on_delete=models.CASCADE, verbose_name="Мероприятие"
|
||||
)
|
||||
points = models.PositiveIntegerField(blank=True, default=0, verbose_name="Баллы за участие")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.student} - {self.event}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Участник мероприятия"
|
||||
verbose_name_plural = "Участники мероприятий"
|
||||
ordering = ["event", "student"]
|
||||
|
||||
class MethodicalFile(models.Model):
|
||||
specialty = models.ForeignKey(
|
||||
Specialty, on_delete=models.CASCADE, verbose_name="Специальность"
|
||||
)
|
||||
name = models.CharField(max_length=200, verbose_name="Название")
|
||||
file_path = models.FileField(max_length=500, verbose_name="Файл")
|
||||
upload_date = models.DateField(auto_now_add=True, verbose_name="Дата добавления")
|
||||
employee = models.ForeignKey(
|
||||
Employee, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Сотрудник"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.specialty})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Методический файл"
|
||||
verbose_name_plural = "Методические файлы"
|
||||
ordering = ["specialty", "name", "upload_date", "employee"]
|
||||
|
||||
class RelevantMethodicalFile(models.Model):
|
||||
methodical_file = models.ForeignKey(
|
||||
MethodicalFile, on_delete=models.CASCADE, verbose_name="Методический файл"
|
||||
)
|
||||
activity_type = models.ForeignKey(
|
||||
ActivityType, on_delete=models.CASCADE, verbose_name="Вид деятельности"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.methodical_file} - {self.activity_type}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Релевантный методический файл"
|
||||
verbose_name_plural = "Релевантные методические файлы"
|
||||
|
||||
ordering = ["methodical_file", "activity_type"]
|
||||
68
events/serializers.py
Normal file
68
events/serializers.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from rest_framework import serializers
|
||||
from .models import (
|
||||
EventStatus,
|
||||
ActivityType,
|
||||
Event,
|
||||
EventOrganizer,
|
||||
EventDetail,
|
||||
EventParticipant,
|
||||
MethodicalFile,
|
||||
RelevantMethodicalFile,
|
||||
)
|
||||
|
||||
class EventStatusSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = EventStatus
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class ActivityTypeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ActivityType
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class EventSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Event
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class EventOrganizerSerializer(serializers.ModelSerializer):
|
||||
employee_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = EventOrganizer
|
||||
fields = ["id", "employee", "event", "employee_name"]
|
||||
|
||||
def get_employee_name(self, obj):
|
||||
return f"{obj.employee.account.first_name} {obj.employee.account.last_name}"
|
||||
|
||||
|
||||
class EventDetailSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = EventDetail
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class EventParticipantSerializer(serializers.ModelSerializer):
|
||||
student_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = EventParticipant
|
||||
fields = ["id", "student", "event", "points", "student_name"]
|
||||
|
||||
def get_student_name(self, obj):
|
||||
return f"{obj.student.account.last_name} {obj.student.account.first_name}"
|
||||
|
||||
|
||||
class MethodicalFileSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = MethodicalFile
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class RelevantMethodicalFileSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = RelevantMethodicalFile
|
||||
fields = "__all__"
|
||||
3
events/tests.py
Normal file
3
events/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
26
events/urls.py
Normal file
26
events/urls.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import (
|
||||
EventStatusViewSet,
|
||||
ActivityTypeViewSet,
|
||||
EventViewSet,
|
||||
EventOrganizerViewSet,
|
||||
EventDetailViewSet,
|
||||
EventParticipantViewSet,
|
||||
MethodicalFileViewSet,
|
||||
RelevantMethodicalFileViewSet,
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"event-statuses", EventStatusViewSet, basename="event-status")
|
||||
router.register(r"activity-types", ActivityTypeViewSet, basename="activity-type")
|
||||
router.register(r"events", EventViewSet, basename="event")
|
||||
router.register(r"event-organizers", EventOrganizerViewSet, basename="event-organizer")
|
||||
router.register(r"event-details", EventDetailViewSet, basename="event-detail")
|
||||
router.register(r"event-participants", EventParticipantViewSet, basename="event-participant")
|
||||
router.register(r"methodical-files", MethodicalFileViewSet, basename="methodical-file")
|
||||
router.register(r"relevant-methodical-files", RelevantMethodicalFileViewSet, basename="relevant-methodical-file")
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
97
events/views.py
Normal file
97
events/views.py
Normal file
@@ -0,0 +1,97 @@
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from .models import (
|
||||
EventStatus,
|
||||
ActivityType,
|
||||
Event,
|
||||
EventOrganizer,
|
||||
EventDetail,
|
||||
EventParticipant,
|
||||
MethodicalFile,
|
||||
RelevantMethodicalFile,
|
||||
)
|
||||
from .serializers import (
|
||||
EventStatusSerializer,
|
||||
ActivityTypeSerializer,
|
||||
EventSerializer,
|
||||
EventOrganizerSerializer,
|
||||
EventDetailSerializer,
|
||||
EventParticipantSerializer,
|
||||
MethodicalFileSerializer,
|
||||
RelevantMethodicalFileSerializer,
|
||||
)
|
||||
from .filters import (
|
||||
EventFilter,
|
||||
EventParticipantFilter,
|
||||
MethodicalFileFilter,
|
||||
) # Фильтры будут добавлены позже
|
||||
|
||||
class EventStatusViewSet(viewsets.ModelViewSet):
|
||||
queryset = EventStatus.objects.all()
|
||||
serializer_class = EventStatusSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
search_fields = ["name"]
|
||||
ordering_fields = ["name"]
|
||||
|
||||
|
||||
class ActivityTypeViewSet(viewsets.ModelViewSet):
|
||||
queryset = ActivityType.objects.all()
|
||||
serializer_class = ActivityTypeSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
search_fields = ["name"]
|
||||
ordering_fields = ["name"]
|
||||
|
||||
|
||||
class EventViewSet(viewsets.ModelViewSet):
|
||||
queryset = Event.objects.all()
|
||||
serializer_class = EventSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = EventFilter
|
||||
search_fields = ["name", "location", "description"]
|
||||
ordering_fields = ["date", "name"]
|
||||
|
||||
|
||||
class EventOrganizerViewSet(viewsets.ModelViewSet):
|
||||
queryset = EventOrganizer.objects.all()
|
||||
serializer_class = EventOrganizerSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ["employee", "event"]
|
||||
search_fields = ["employee__account__first_name", "employee__account__last_name"]
|
||||
ordering_fields = ["employee", "event"]
|
||||
|
||||
|
||||
class EventDetailViewSet(viewsets.ModelViewSet):
|
||||
queryset = EventDetail.objects.all()
|
||||
serializer_class = EventDetailSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ["activity_type", "event"]
|
||||
search_fields = ["activity_type__name"]
|
||||
ordering_fields = ["activity_type", "event"]
|
||||
|
||||
|
||||
class EventParticipantViewSet(viewsets.ModelViewSet):
|
||||
queryset = EventParticipant.objects.all()
|
||||
serializer_class = EventParticipantSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = EventParticipantFilter
|
||||
search_fields = ["student__account__first_name", "student__account__last_name"]
|
||||
ordering_fields = ["event", "student", "points"]
|
||||
|
||||
|
||||
class MethodicalFileViewSet(viewsets.ModelViewSet):
|
||||
queryset = MethodicalFile.objects.all()
|
||||
serializer_class = MethodicalFileSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = MethodicalFileFilter
|
||||
search_fields = ["name", "specialty__name"]
|
||||
ordering_fields = ["specialty", "name", "upload_date"]
|
||||
|
||||
|
||||
class RelevantMethodicalFileViewSet(viewsets.ModelViewSet):
|
||||
queryset = RelevantMethodicalFile.objects.all()
|
||||
serializer_class = RelevantMethodicalFileSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ["methodical_file", "activity_type"]
|
||||
search_fields = ["methodical_file__name", "activity_type__name"]
|
||||
ordering_fields = ["methodical_file", "activity_type"]
|
||||
0
frontend/__init__.py
Normal file
0
frontend/__init__.py
Normal file
3
frontend/admin.py
Normal file
3
frontend/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
frontend/apps.py
Normal file
6
frontend/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FrontendConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'frontend'
|
||||
0
frontend/migrations/__init__.py
Normal file
0
frontend/migrations/__init__.py
Normal file
3
frontend/models.py
Normal file
3
frontend/models.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
frontend/tests.py
Normal file
3
frontend/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
frontend/views.py
Normal file
3
frontend/views.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
0
outreach/__init__.py
Normal file
0
outreach/__init__.py
Normal file
90
outreach/admin.py
Normal file
90
outreach/admin.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from django.contrib import admin
|
||||
from .models import (
|
||||
InstitutionType, Representative, School, Partner,
|
||||
PartnerRepresentative, SchoolResponsibility
|
||||
)
|
||||
|
||||
|
||||
@admin.register(InstitutionType)
|
||||
class InstitutionTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
@admin.register(Representative)
|
||||
class RepresentativeAdmin(admin.ModelAdmin):
|
||||
list_display = ('full_name', 'position')
|
||||
search_fields = ('first_name', 'last_name', 'middle_name', 'phone', 'email', 'position')
|
||||
|
||||
@admin.display(description='ФИО')
|
||||
def full_name(self, obj):
|
||||
return str(obj)
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('first_name', 'last_name', 'middle_name', 'position')
|
||||
}),
|
||||
('Контактные данные', {
|
||||
'fields': ('phone', 'email')
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
class PartnerRepresentativeInline(admin.TabularInline):
|
||||
model = PartnerRepresentative
|
||||
extra = 1
|
||||
autocomplete_fields = ('representative',)
|
||||
|
||||
|
||||
@admin.register(Partner)
|
||||
class PartnerAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
inlines = [PartnerRepresentativeInline]
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('name', 'description')
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@admin.register(PartnerRepresentative)
|
||||
class PartnerRepresentativeAdmin(admin.ModelAdmin):
|
||||
list_display = ('representative', 'partner')
|
||||
search_fields = ('representative__first_name', 'representative__last_name', 'partner__name')
|
||||
autocomplete_fields = ('representative', 'partner')
|
||||
|
||||
|
||||
@admin.register(School)
|
||||
class SchoolAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'institution_type', 'director')
|
||||
list_filter = ('institution_type',)
|
||||
search_fields = ('name', 'address', 'director__first_name', 'director__last_name')
|
||||
autocomplete_fields = ('director', 'institution_type')
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('name', 'institution_type', 'address')
|
||||
}),
|
||||
('Директор', {
|
||||
'fields': ('director',)
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@admin.register(SchoolResponsibility)
|
||||
class SchoolResponsibilityAdmin(admin.ModelAdmin):
|
||||
list_display = ('academic_year', 'school', 'responsible')
|
||||
list_filter = ('academic_year', 'school__institution_type')
|
||||
search_fields = (
|
||||
'school__name', 'responsible__first_name',
|
||||
'responsible__last_name', 'academic_year__year'
|
||||
)
|
||||
autocomplete_fields = ('school', 'responsible', 'academic_year')
|
||||
|
||||
fieldsets = (
|
||||
('Ответственность', {
|
||||
'fields': ('academic_year', 'school', 'responsible')
|
||||
}),
|
||||
)
|
||||
7
outreach/apps.py
Normal file
7
outreach/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class OutreachConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'outreach'
|
||||
verbose_name = 'Профориентационная работа'
|
||||
25
outreach/filters.py
Normal file
25
outreach/filters.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import django_filters
|
||||
from .models import School, SchoolResponsibility
|
||||
|
||||
class SchoolFilter(django_filters.FilterSet):
|
||||
name = django_filters.CharFilter(lookup_expr="icontains")
|
||||
address = django_filters.CharFilter(lookup_expr="icontains")
|
||||
institution_type_name = django_filters.CharFilter(
|
||||
field_name="institution_type__name", lookup_expr="icontains"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = School
|
||||
fields = ["name", "address", "institution_type_name"]
|
||||
|
||||
|
||||
class SchoolResponsibilityFilter(django_filters.FilterSet):
|
||||
academic_year = django_filters.CharFilter(field_name="academic_year__year", lookup_expr="exact")
|
||||
school_name = django_filters.CharFilter(field_name="school__name", lookup_expr="icontains")
|
||||
responsible_name = django_filters.CharFilter(
|
||||
field_name="responsible__account__first_name", lookup_expr="icontains"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = SchoolResponsibility
|
||||
fields = ["academic_year", "school_name", "responsible_name"]
|
||||
96
outreach/migrations/0001_initial.py
Normal file
96
outreach/migrations/0001_initial.py
Normal file
@@ -0,0 +1,96 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:14
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('employees', '0001_initial'),
|
||||
('education', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='InstitutionType',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, verbose_name='Название вида учебного заведения')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Вид учебного заведения',
|
||||
'verbose_name_plural': 'Виды учебных заведений',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Partner',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Название')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Партнер',
|
||||
'verbose_name_plural': 'Партнеры',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Representative',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('first_name', models.CharField(max_length=100, verbose_name='Имя')),
|
||||
('last_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Фамилия')),
|
||||
('middle_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Отчество')),
|
||||
('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Телефон')),
|
||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Почта')),
|
||||
('position', models.CharField(blank=True, max_length=100, null=True, verbose_name='Должность')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Представитель',
|
||||
'verbose_name_plural': 'Представители',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='School',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Название')),
|
||||
('address', models.CharField(max_length=300, verbose_name='Адрес')),
|
||||
('director', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='outreach.representative', verbose_name='Директор')),
|
||||
('institution_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='outreach.institutiontype', verbose_name='Вид учебного заведения')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Школа',
|
||||
'verbose_name_plural': 'Школы',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SchoolResponsibility',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('academic_year', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='education.academicyear', verbose_name='Учебный год')),
|
||||
('responsible', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='employees.employee', verbose_name='Ответственный сотрудник')),
|
||||
('school', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='outreach.school', verbose_name='Школа')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Ответственный за школу',
|
||||
'verbose_name_plural': 'Ответственные за школы',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PartnerRepresentative',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('partner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='outreach.partner', verbose_name='Партнер')),
|
||||
('representative', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='outreach.representative', verbose_name='Представитель')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Представитель партнера',
|
||||
'verbose_name_plural': 'Представители партнеров',
|
||||
'unique_together': {('representative', 'partner')},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
outreach/migrations/__init__.py
Normal file
0
outreach/migrations/__init__.py
Normal file
95
outreach/models.py
Normal file
95
outreach/models.py
Normal file
@@ -0,0 +1,95 @@
|
||||
from django.db import models
|
||||
from core.models import ContactMixin # Предполагается, что ContactMixin находится в приложении core
|
||||
from education.models import AcademicYear # Предполагается, что AcademicYear находится в приложении education
|
||||
from employees.models import Employee # Предполагается, что модель Employee находится в приложении employees
|
||||
|
||||
## Профориентационная деятельность (название приложения: outreach)
|
||||
|
||||
class InstitutionType(models.Model):
|
||||
name = models.CharField(max_length=50, verbose_name="Название вида учебного заведения")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Вид учебного заведения"
|
||||
verbose_name_plural = "Виды учебных заведений"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class Representative(ContactMixin):
|
||||
position = models.CharField(max_length=100, blank=True, null=True, verbose_name="Должность")
|
||||
|
||||
def __str__(self):
|
||||
return f"{super().__str__()}"+(" - "+self.position if self.position else "")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Представитель"
|
||||
verbose_name_plural = "Представители"
|
||||
ordering = ["position"]
|
||||
|
||||
class School(models.Model):
|
||||
director = models.ForeignKey(
|
||||
Representative, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Директор"
|
||||
)
|
||||
institution_type = models.ForeignKey(
|
||||
InstitutionType, on_delete=models.CASCADE, verbose_name="Вид учебного заведения"
|
||||
)
|
||||
name = models.CharField(max_length=200, verbose_name="Название")
|
||||
address = models.CharField(max_length=300, verbose_name="Адрес")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.institution_type})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Школа"
|
||||
verbose_name_plural = "Школы"
|
||||
ordering = ["institution_type", "name", "address"]
|
||||
|
||||
class Partner(models.Model):
|
||||
name = models.CharField(max_length=200, verbose_name="Название")
|
||||
description = models.TextField(blank=True, null=True, verbose_name="Описание")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Партнер"
|
||||
verbose_name_plural = "Партнеры"
|
||||
ordering = ["name"]
|
||||
|
||||
class PartnerRepresentative(models.Model):
|
||||
representative = models.OneToOneField(
|
||||
Representative, on_delete=models.CASCADE, verbose_name="Представитель"
|
||||
)
|
||||
partner = models.ForeignKey(
|
||||
Partner, on_delete=models.CASCADE, verbose_name="Партнер"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.representative} - {self.partner}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Представитель партнера"
|
||||
verbose_name_plural = "Представители партнеров"
|
||||
unique_together = ("representative", "partner")
|
||||
ordering = ["partner", "representative"]
|
||||
|
||||
class SchoolResponsibility(models.Model):
|
||||
academic_year = models.ForeignKey(
|
||||
AcademicYear, on_delete=models.CASCADE, verbose_name="Учебный год"
|
||||
)
|
||||
responsible = models.ForeignKey(
|
||||
Employee, on_delete=models.CASCADE, verbose_name="Ответственный сотрудник"
|
||||
)
|
||||
school = models.ForeignKey(
|
||||
School, on_delete=models.CASCADE, verbose_name="Школа"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.responsible} - {self.school} ({self.academic_year})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Ответственный за школу"
|
||||
verbose_name_plural = "Ответственные за школы"
|
||||
ordering = ["academic_year", "responsible", "school"]
|
||||
67
outreach/serializers.py
Normal file
67
outreach/serializers.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from rest_framework import serializers
|
||||
from .models import (
|
||||
InstitutionType,
|
||||
Representative,
|
||||
School,
|
||||
Partner,
|
||||
PartnerRepresentative,
|
||||
SchoolResponsibility,
|
||||
)
|
||||
|
||||
class InstitutionTypeSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = InstitutionType
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class RepresentativeSerializer(serializers.ModelSerializer):
|
||||
full_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = Representative
|
||||
fields = ["id", "first_name", "last_name", "middle_name", "phone", "email", "position", "full_name"]
|
||||
|
||||
def get_full_name(self, obj):
|
||||
return f"{obj.first_name} {obj.last_name}" + (f" {obj.middle_name}" if obj.middle_name else "")
|
||||
|
||||
|
||||
class SchoolSerializer(serializers.ModelSerializer):
|
||||
director_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = School
|
||||
fields = ["id", "director", "institution_type", "name", "address", "director_name"]
|
||||
|
||||
def get_director_name(self, obj):
|
||||
if obj.director:
|
||||
return f"{obj.director.first_name} {obj.director.last_name}"
|
||||
return None
|
||||
|
||||
|
||||
class PartnerSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Partner
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class PartnerRepresentativeSerializer(serializers.ModelSerializer):
|
||||
representative_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = PartnerRepresentative
|
||||
fields = ["id", "representative", "partner", "representative_name"]
|
||||
|
||||
def get_representative_name(self, obj):
|
||||
return f"{obj.representative.first_name} {obj.representative.last_name}"
|
||||
|
||||
|
||||
class SchoolResponsibilitySerializer(serializers.ModelSerializer):
|
||||
responsible_name = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = SchoolResponsibility
|
||||
fields = ["id", "academic_year", "responsible", "school", "responsible_name"]
|
||||
|
||||
def get_responsible_name(self, obj):
|
||||
return f"{obj.responsible.account.first_name} {obj.responsible.account.last_name}"
|
||||
|
||||
3
outreach/tests.py
Normal file
3
outreach/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
22
outreach/urls.py
Normal file
22
outreach/urls.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import (
|
||||
InstitutionTypeViewSet,
|
||||
RepresentativeViewSet,
|
||||
SchoolViewSet,
|
||||
PartnerViewSet,
|
||||
PartnerRepresentativeViewSet,
|
||||
SchoolResponsibilityViewSet,
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"institution-types", InstitutionTypeViewSet, basename="institution-type")
|
||||
router.register(r"representatives", RepresentativeViewSet, basename="representative")
|
||||
router.register(r"schools", SchoolViewSet, basename="school")
|
||||
router.register(r"partners", PartnerViewSet, basename="partner")
|
||||
router.register(r"partner-representatives", PartnerRepresentativeViewSet, basename="partner-representative")
|
||||
router.register(r"school-responsibilities", SchoolResponsibilityViewSet, basename="school-responsibility")
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
73
outreach/views.py
Normal file
73
outreach/views.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from .models import (
|
||||
InstitutionType,
|
||||
Representative,
|
||||
School,
|
||||
Partner,
|
||||
PartnerRepresentative,
|
||||
SchoolResponsibility,
|
||||
)
|
||||
from .serializers import (
|
||||
InstitutionTypeSerializer,
|
||||
RepresentativeSerializer,
|
||||
SchoolSerializer,
|
||||
PartnerSerializer,
|
||||
PartnerRepresentativeSerializer,
|
||||
SchoolResponsibilitySerializer,
|
||||
)
|
||||
from .filters import (
|
||||
SchoolFilter,
|
||||
SchoolResponsibilityFilter,
|
||||
) # Фильтры будут добавлены позже
|
||||
|
||||
class InstitutionTypeViewSet(viewsets.ModelViewSet):
|
||||
queryset = InstitutionType.objects.all()
|
||||
serializer_class = InstitutionTypeSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
search_fields = ["name"]
|
||||
ordering_fields = ["name"]
|
||||
|
||||
|
||||
class RepresentativeViewSet(viewsets.ModelViewSet):
|
||||
queryset = Representative.objects.all()
|
||||
serializer_class = RepresentativeSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
search_fields = ["first_name", "last_name", "position"]
|
||||
ordering_fields = ["position"]
|
||||
|
||||
|
||||
class SchoolViewSet(viewsets.ModelViewSet):
|
||||
queryset = School.objects.all()
|
||||
serializer_class = SchoolSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = SchoolFilter
|
||||
search_fields = ["name", "address"]
|
||||
ordering_fields = ["institution_type", "name"]
|
||||
|
||||
|
||||
class PartnerViewSet(viewsets.ModelViewSet):
|
||||
queryset = Partner.objects.all()
|
||||
serializer_class = PartnerSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
search_fields = ["name", "description"]
|
||||
ordering_fields = ["name"]
|
||||
|
||||
|
||||
class PartnerRepresentativeViewSet(viewsets.ModelViewSet):
|
||||
queryset = PartnerRepresentative.objects.all()
|
||||
serializer_class = PartnerRepresentativeSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ["representative", "partner"]
|
||||
search_fields = ["representative__first_name", "partner__name"]
|
||||
ordering_fields = ["partner", "representative"]
|
||||
|
||||
|
||||
class SchoolResponsibilityViewSet(viewsets.ModelViewSet):
|
||||
queryset = SchoolResponsibility.objects.all()
|
||||
serializer_class = SchoolResponsibilitySerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = SchoolResponsibilityFilter
|
||||
search_fields = ["school__name", "responsible__account__first_name"]
|
||||
ordering_fields = ["academic_year", "responsible", "school"]
|
||||
@@ -1 +1,13 @@
|
||||
django~=4.2.21
|
||||
django~=4.2.21
|
||||
django-cors-headers
|
||||
djangorestframework
|
||||
drf-yasg
|
||||
Pillow
|
||||
requests
|
||||
django-autoslug
|
||||
transliterate
|
||||
pytest-django
|
||||
pytest
|
||||
django-filter
|
||||
djangorestframework-simplejwt
|
||||
djoser
|
||||
0
surveys/__init__.py
Normal file
0
surveys/__init__.py
Normal file
104
surveys/admin.py
Normal file
104
surveys/admin.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from django.contrib import admin
|
||||
from .models import (
|
||||
Survey, ScoreVariable, Statement, QuestionPair, AgreementQuestion,
|
||||
EducationalInstitution, Profession, Specialty, ProfessionSpecialtyLink, ProfessionTestResult
|
||||
)
|
||||
|
||||
|
||||
class ScoreVariableInline(admin.TabularInline):
|
||||
model = ScoreVariable
|
||||
extra = 1
|
||||
|
||||
|
||||
class QuestionPairInline(admin.TabularInline):
|
||||
model = QuestionPair
|
||||
extra = 1
|
||||
autocomplete_fields = ('statement_a', 'statement_b')
|
||||
|
||||
|
||||
class AgreementQuestionInline(admin.TabularInline):
|
||||
model = AgreementQuestion
|
||||
extra = 1
|
||||
autocomplete_fields = ('score_variable',)
|
||||
|
||||
|
||||
@admin.register(Survey)
|
||||
class SurveyAdmin(admin.ModelAdmin):
|
||||
list_display = ('title',)
|
||||
search_fields = ('title',)
|
||||
inlines = [ScoreVariableInline, QuestionPairInline, AgreementQuestionInline]
|
||||
|
||||
|
||||
@admin.register(ScoreVariable)
|
||||
class ScoreVariableAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'survey', 'name')
|
||||
list_filter = ('survey',)
|
||||
search_fields = ('title', 'name', 'description')
|
||||
autocomplete_fields = ('survey',)
|
||||
|
||||
|
||||
@admin.register(Statement)
|
||||
class StatementAdmin(admin.ModelAdmin):
|
||||
list_display = ('statement', 'score_variable')
|
||||
list_filter = ('score_variable__survey',)
|
||||
search_fields = ('statement', 'score_variable__title')
|
||||
autocomplete_fields = ('score_variable',)
|
||||
|
||||
|
||||
@admin.register(QuestionPair)
|
||||
class QuestionPairAdmin(admin.ModelAdmin):
|
||||
list_display = ('survey', 'statement_a', 'statement_b')
|
||||
list_filter = ('survey',)
|
||||
search_fields = ('statement_a__statement', 'statement_b__statement')
|
||||
autocomplete_fields = ('survey', 'statement_a', 'statement_b')
|
||||
|
||||
|
||||
@admin.register(AgreementQuestion)
|
||||
class AgreementQuestionAdmin(admin.ModelAdmin):
|
||||
list_display = ('question', 'survey', 'score_variable')
|
||||
list_filter = ('survey',)
|
||||
search_fields = ('question', 'score_variable__title')
|
||||
autocomplete_fields = ('survey', 'score_variable')
|
||||
|
||||
|
||||
@admin.register(EducationalInstitution)
|
||||
class EducationalInstitutionAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
|
||||
|
||||
@admin.register(Profession)
|
||||
class ProfessionAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name', 'description')
|
||||
|
||||
|
||||
class ProfessionSpecialtyLinkInline(admin.TabularInline):
|
||||
model = ProfessionSpecialtyLink
|
||||
extra = 1
|
||||
autocomplete_fields = ('specialty',)
|
||||
|
||||
|
||||
@admin.register(Specialty)
|
||||
class SpecialtyAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'institution', 'link')
|
||||
list_filter = ('institution',)
|
||||
search_fields = ('name', 'institution__name')
|
||||
inlines = [ProfessionSpecialtyLinkInline]
|
||||
autocomplete_fields = ('institution',)
|
||||
|
||||
|
||||
@admin.register(ProfessionSpecialtyLink)
|
||||
class ProfessionSpecialtyLinkAdmin(admin.ModelAdmin):
|
||||
list_display = ('profession', 'specialty')
|
||||
list_filter = ('profession',)
|
||||
search_fields = ('profession__name', 'specialty__name')
|
||||
autocomplete_fields = ('profession', 'specialty')
|
||||
|
||||
|
||||
@admin.register(ProfessionTestResult)
|
||||
class ProfessionTestResultAdmin(admin.ModelAdmin):
|
||||
list_display = ('profession', 'survey_score_variable')
|
||||
list_filter = ('profession',)
|
||||
search_fields = ('profession__name', 'survey_score_variable__title')
|
||||
autocomplete_fields = ('profession', 'survey_score_variable')
|
||||
7
surveys/apps.py
Normal file
7
surveys/apps.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SurveysConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'surveys'
|
||||
verbose_name = "Анкетирование и Тестирование"
|
||||
19
surveys/filters.py
Normal file
19
surveys/filters.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import django_filters
|
||||
from .models import QuestionPair, Specialty
|
||||
|
||||
class QuestionPairFilter(django_filters.FilterSet):
|
||||
survey_title = django_filters.CharFilter(field_name="survey__title", lookup_expr="icontains")
|
||||
statement_a_text = django_filters.CharFilter(field_name="statement_a__statement", lookup_expr="icontains")
|
||||
statement_b_text = django_filters.CharFilter(field_name="statement_b__statement", lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = QuestionPair
|
||||
fields = ["survey_title", "statement_a_text", "statement_b_text"]
|
||||
|
||||
|
||||
class SpecialtyFilter(django_filters.FilterSet):
|
||||
institution_name = django_filters.CharFilter(field_name="institution__name", lookup_expr="icontains")
|
||||
|
||||
class Meta:
|
||||
model = Specialty
|
||||
fields = ["name", "institution_name"]
|
||||
143
surveys/migrations/0001_initial.py
Normal file
143
surveys/migrations/0001_initial.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:14
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EducationalInstitution',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=256, verbose_name='Название учреждения')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Образовательное учреждение',
|
||||
'verbose_name_plural': 'Образовательные учреждения',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Profession',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=256, verbose_name='Название профессии')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Описание')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Профессия',
|
||||
'verbose_name_plural': 'Профессии',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ScoreVariable',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=30, verbose_name='Имя переменной')),
|
||||
('title', models.CharField(max_length=70, verbose_name='Название переменной')),
|
||||
('description', models.TextField(verbose_name='Описание')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Переменная оценки',
|
||||
'verbose_name_plural': 'Переменные оценки',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Survey',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=70, verbose_name='Название опроса')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Опрос',
|
||||
'verbose_name_plural': 'Опросы',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Statement',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('statement', models.CharField(max_length=256, verbose_name='Утверждение')),
|
||||
('score_variable', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='surveys.scorevariable', verbose_name='Переменная оценки')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Утверждение',
|
||||
'verbose_name_plural': 'Утверждения',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Specialty',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=256, verbose_name='Название специальности')),
|
||||
('link', models.URLField(blank=True, null=True, verbose_name='Ссылка')),
|
||||
('institution', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.educationalinstitution', verbose_name='Образовательное учреждение')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Специальность',
|
||||
'verbose_name_plural': 'Специальности',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scorevariable',
|
||||
name='survey',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.survey', verbose_name='Опрос'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='QuestionPair',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('statement_a', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='pair_a', to='surveys.statement', verbose_name='Утверждение A')),
|
||||
('statement_b', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='pair_b', to='surveys.statement', verbose_name='Утверждение B')),
|
||||
('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.survey', verbose_name='Опрос')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Пара вопросов',
|
||||
'verbose_name_plural': 'Пары вопросов',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProfessionTestResult',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('profession', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.profession', verbose_name='Профессия')),
|
||||
('survey_score_variable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.scorevariable', verbose_name='Переменная оценки')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Результат теста по профессии',
|
||||
'verbose_name_plural': 'Результаты тестов по профессиям',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AgreementQuestion',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('question', models.CharField(max_length=256, verbose_name='Вопрос')),
|
||||
('score_variable', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.scorevariable', verbose_name='Переменная оценки')),
|
||||
('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.survey', verbose_name='Опрос')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Вопрос на согласие',
|
||||
'verbose_name_plural': 'Вопросы на согласие',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ProfessionSpecialtyLink',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('profession', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.profession', verbose_name='Профессия')),
|
||||
('specialty', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='surveys.specialty', verbose_name='Специальность')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Связь между профессией и специальностью',
|
||||
'verbose_name_plural': 'Связи между профессиями и специальностями',
|
||||
'unique_together': {('profession', 'specialty')},
|
||||
},
|
||||
),
|
||||
]
|
||||
0
surveys/migrations/__init__.py
Normal file
0
surveys/migrations/__init__.py
Normal file
161
surveys/models.py
Normal file
161
surveys/models.py
Normal file
@@ -0,0 +1,161 @@
|
||||
from django.db import models
|
||||
|
||||
|
||||
## Модуль Анкетирование (название приложения: surveys)
|
||||
|
||||
class Survey(models.Model):
|
||||
title = models.CharField(max_length=70, verbose_name="Название опроса")
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Опрос"
|
||||
verbose_name_plural = "Опросы"
|
||||
ordering = ["title"]
|
||||
|
||||
|
||||
class ScoreVariable(models.Model):
|
||||
name = models.CharField(max_length=30, verbose_name="Имя переменной")
|
||||
title = models.CharField(max_length=70, verbose_name="Название переменной")
|
||||
description = models.TextField(verbose_name="Описание")
|
||||
survey = models.ForeignKey(
|
||||
Survey, on_delete=models.CASCADE, verbose_name="Опрос"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title} ({self.survey})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Переменная оценки"
|
||||
verbose_name_plural = "Переменные оценки"
|
||||
ordering = ["survey", "name", "title"]
|
||||
|
||||
|
||||
class Statement(models.Model):
|
||||
statement = models.CharField(max_length=256, verbose_name="Утверждение")
|
||||
score_variable = models.ForeignKey(
|
||||
ScoreVariable, on_delete=models.SET_NULL, blank=True, null=True, verbose_name="Переменная оценки"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.statement
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Утверждение"
|
||||
verbose_name_plural = "Утверждения"
|
||||
ordering = ["statement", "score_variable"]
|
||||
|
||||
|
||||
class QuestionPair(models.Model):
|
||||
survey = models.ForeignKey(
|
||||
Survey, on_delete=models.CASCADE, verbose_name="Опрос"
|
||||
)
|
||||
statement_a = models.OneToOneField(
|
||||
Statement, on_delete=models.CASCADE, related_name="pair_a", verbose_name="Утверждение A"
|
||||
)
|
||||
statement_b = models.OneToOneField(
|
||||
Statement, on_delete=models.CASCADE, related_name="pair_b", verbose_name="Утверждение B"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"Пара: {self.statement_a} - {self.statement_b}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Пара вопросов"
|
||||
verbose_name_plural = "Пары вопросов"
|
||||
ordering = ["survey", "statement_a", "statement_b"]
|
||||
|
||||
|
||||
class AgreementQuestion(models.Model):
|
||||
survey = models.ForeignKey(
|
||||
Survey, on_delete=models.CASCADE, verbose_name="Опрос"
|
||||
)
|
||||
question = models.CharField(max_length=256, verbose_name="Вопрос")
|
||||
score_variable = models.ForeignKey(
|
||||
ScoreVariable, on_delete=models.CASCADE, verbose_name="Переменная оценки"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.question
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Вопрос на согласие"
|
||||
verbose_name_plural = "Вопросы на согласие"
|
||||
ordering = ["survey", "question"]
|
||||
|
||||
|
||||
class EducationalInstitution(models.Model):
|
||||
name = models.CharField(max_length=256, verbose_name="Название учреждения")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Образовательное учреждение"
|
||||
verbose_name_plural = "Образовательные учреждения"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class Profession(models.Model):
|
||||
name = models.CharField(max_length=256, verbose_name="Название профессии")
|
||||
description = models.TextField(blank=True, null=True, verbose_name="Описание")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Профессия"
|
||||
verbose_name_plural = "Профессии"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class Specialty(models.Model):
|
||||
name = models.CharField(max_length=256, verbose_name="Название специальности")
|
||||
institution = models.ForeignKey(
|
||||
EducationalInstitution, on_delete=models.CASCADE, verbose_name="Образовательное учреждение"
|
||||
)
|
||||
link = models.URLField(verbose_name="Ссылка", null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.institution})"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Специальность"
|
||||
verbose_name_plural = "Специальности"
|
||||
ordering = ["name", "institution"]
|
||||
|
||||
|
||||
class ProfessionSpecialtyLink(models.Model):
|
||||
profession = models.ForeignKey(
|
||||
Profession, on_delete=models.CASCADE, verbose_name="Профессия"
|
||||
)
|
||||
specialty = models.ForeignKey(
|
||||
Specialty, on_delete=models.CASCADE, verbose_name="Специальность"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.profession} - {self.specialty}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Связь между профессией и специальностью"
|
||||
verbose_name_plural = "Связи между профессиями и специальностями"
|
||||
unique_together = ("profession", "specialty")
|
||||
ordering = ["profession", "specialty"]
|
||||
|
||||
|
||||
class ProfessionTestResult(models.Model):
|
||||
profession = models.ForeignKey(
|
||||
Profession, on_delete=models.CASCADE, verbose_name="Профессия"
|
||||
)
|
||||
survey_score_variable = models.ForeignKey(
|
||||
ScoreVariable, on_delete=models.CASCADE, verbose_name="Переменная оценки"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.profession} - {self.survey_score_variable}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Результат теста по профессии"
|
||||
verbose_name_plural = "Результаты тестов по профессиям"
|
||||
ordering = ["profession", "survey_score_variable"]
|
||||
81
surveys/serializers.py
Normal file
81
surveys/serializers.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from rest_framework import serializers
|
||||
from .models import (
|
||||
Survey,
|
||||
ScoreVariable,
|
||||
Statement,
|
||||
QuestionPair,
|
||||
AgreementQuestion,
|
||||
EducationalInstitution,
|
||||
Profession,
|
||||
Specialty,
|
||||
ProfessionSpecialtyLink,
|
||||
ProfessionTestResult,
|
||||
)
|
||||
|
||||
class SurveySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Survey
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class ScoreVariableSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ScoreVariable
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class StatementSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Statement
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class QuestionPairSerializer(serializers.ModelSerializer):
|
||||
statement_a_text = serializers.SerializerMethodField()
|
||||
statement_b_text = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = QuestionPair
|
||||
fields = ["id", "survey", "statement_a", "statement_b", "statement_a_text", "statement_b_text"]
|
||||
|
||||
def get_statement_a_text(self, obj):
|
||||
return obj.statement_a.statement
|
||||
|
||||
def get_statement_b_text(self, obj):
|
||||
return obj.statement_b.statement
|
||||
|
||||
|
||||
class AgreementQuestionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = AgreementQuestion
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class EducationalInstitutionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = EducationalInstitution
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class ProfessionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Profession
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class SurveySpecialtySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Specialty
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class ProfessionSpecialtyLinkSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ProfessionSpecialtyLink
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class ProfessionTestResultSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ProfessionTestResult
|
||||
fields = "__all__"
|
||||
3
surveys/tests.py
Normal file
3
surveys/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
30
surveys/urls.py
Normal file
30
surveys/urls.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import (
|
||||
SurveyViewSet,
|
||||
ScoreVariableViewSet,
|
||||
StatementViewSet,
|
||||
QuestionPairViewSet,
|
||||
AgreementQuestionViewSet,
|
||||
EducationalInstitutionViewSet,
|
||||
ProfessionViewSet,
|
||||
SpecialtyViewSet,
|
||||
ProfessionSpecialtyLinkViewSet,
|
||||
ProfessionTestResultViewSet,
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"surveys", SurveyViewSet, basename="survey")
|
||||
router.register(r"score-variables", ScoreVariableViewSet, basename="score-variable")
|
||||
router.register(r"statements", StatementViewSet, basename="statement")
|
||||
router.register(r"question-pairs", QuestionPairViewSet, basename="question-pair")
|
||||
router.register(r"agreement-questions", AgreementQuestionViewSet, basename="agreement-question")
|
||||
router.register(r"institutions", EducationalInstitutionViewSet, basename="institution")
|
||||
router.register(r"professions", ProfessionViewSet, basename="profession")
|
||||
router.register(r"specialties", SpecialtyViewSet, basename="specialty")
|
||||
router.register(r"profession-specialty-links", ProfessionSpecialtyLinkViewSet, basename="profession-specialty-link")
|
||||
router.register(r"profession-test-results", ProfessionTestResultViewSet, basename="profession-test-result")
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
117
surveys/views.py
Normal file
117
surveys/views.py
Normal file
@@ -0,0 +1,117 @@
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from .models import (
|
||||
Survey,
|
||||
ScoreVariable,
|
||||
Statement,
|
||||
QuestionPair,
|
||||
AgreementQuestion,
|
||||
EducationalInstitution,
|
||||
Profession,
|
||||
Specialty,
|
||||
ProfessionSpecialtyLink,
|
||||
ProfessionTestResult,
|
||||
)
|
||||
from .serializers import (
|
||||
SurveySerializer,
|
||||
ScoreVariableSerializer,
|
||||
StatementSerializer,
|
||||
QuestionPairSerializer,
|
||||
AgreementQuestionSerializer,
|
||||
EducationalInstitutionSerializer,
|
||||
ProfessionSerializer,
|
||||
SurveySpecialtySerializer,
|
||||
ProfessionSpecialtyLinkSerializer,
|
||||
ProfessionTestResultSerializer,
|
||||
)
|
||||
from .filters import (
|
||||
QuestionPairFilter,
|
||||
SpecialtyFilter,
|
||||
) # Фильтры будут добавлены позже
|
||||
|
||||
class SurveyViewSet(viewsets.ModelViewSet):
|
||||
queryset = Survey.objects.all()
|
||||
serializer_class = SurveySerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
search_fields = ["title"]
|
||||
ordering_fields = ["title"]
|
||||
|
||||
|
||||
class ScoreVariableViewSet(viewsets.ModelViewSet):
|
||||
queryset = ScoreVariable.objects.all()
|
||||
serializer_class = ScoreVariableSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ["survey"]
|
||||
search_fields = ["name", "title"]
|
||||
ordering_fields = ["survey", "name"]
|
||||
|
||||
|
||||
class StatementViewSet(viewsets.ModelViewSet):
|
||||
queryset = Statement.objects.all()
|
||||
serializer_class = StatementSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ["score_variable"]
|
||||
search_fields = ["statement"]
|
||||
ordering_fields = ["statement"]
|
||||
|
||||
|
||||
class QuestionPairViewSet(viewsets.ModelViewSet):
|
||||
queryset = QuestionPair.objects.all()
|
||||
serializer_class = QuestionPairSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = QuestionPairFilter
|
||||
search_fields = ["statement_a__statement", "statement_b__statement"]
|
||||
ordering_fields = ["survey", "statement_a", "statement_b"]
|
||||
|
||||
|
||||
class AgreementQuestionViewSet(viewsets.ModelViewSet):
|
||||
queryset = AgreementQuestion.objects.all()
|
||||
serializer_class = AgreementQuestionSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ["survey", "score_variable"]
|
||||
search_fields = ["question"]
|
||||
ordering_fields = ["survey", "question"]
|
||||
|
||||
|
||||
class EducationalInstitutionViewSet(viewsets.ModelViewSet):
|
||||
queryset = EducationalInstitution.objects.all()
|
||||
serializer_class = EducationalInstitutionSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
search_fields = ["name"]
|
||||
ordering_fields = ["name"]
|
||||
|
||||
|
||||
class ProfessionViewSet(viewsets.ModelViewSet):
|
||||
queryset = Profession.objects.all()
|
||||
serializer_class = ProfessionSerializer
|
||||
filter_backends = [SearchFilter, OrderingFilter]
|
||||
search_fields = ["name"]
|
||||
ordering_fields = ["name"]
|
||||
|
||||
|
||||
class SpecialtyViewSet(viewsets.ModelViewSet):
|
||||
queryset = Specialty.objects.all()
|
||||
serializer_class = SurveySpecialtySerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_class = SpecialtyFilter
|
||||
search_fields = ["name", "institution__name"]
|
||||
ordering_fields = ["name", "institution"]
|
||||
|
||||
|
||||
class ProfessionSpecialtyLinkViewSet(viewsets.ModelViewSet):
|
||||
queryset = ProfessionSpecialtyLink.objects.all()
|
||||
serializer_class = ProfessionSpecialtyLinkSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ["profession", "specialty"]
|
||||
search_fields = ["profession__name", "specialty__name"]
|
||||
ordering_fields = ["profession", "specialty"]
|
||||
|
||||
|
||||
class ProfessionTestResultViewSet(viewsets.ModelViewSet):
|
||||
queryset = ProfessionTestResult.objects.all()
|
||||
serializer_class = ProfessionTestResultSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ["profession", "survey_score_variable"]
|
||||
search_fields = ["profession__name", "survey_score_variable__title"]
|
||||
ordering_fields = ["profession", "survey_score_variable"]
|
||||
@@ -10,6 +10,7 @@ For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||
"""
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
@@ -39,13 +40,26 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'corsheaders',
|
||||
'rest_framework',
|
||||
"django_filters",
|
||||
'drf_yasg',
|
||||
'djoser',
|
||||
'rest_framework_simplejwt',
|
||||
"core.apps.CoreConfig",
|
||||
'users.apps.UsersConfig',
|
||||
"employees.apps.EmployeesConfig",
|
||||
"education.apps.EducationConfig",
|
||||
"events.apps.EventsConfig",
|
||||
"outreach.apps.OutreachConfig",
|
||||
"surveys.apps.SurveysConfig",
|
||||
"frontend.apps.FrontendConfig"
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
@@ -143,4 +157,49 @@ MEDIA_ROOT = BASE_DIR / 'media'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
AUTH_USER_MODEL = "users.UserAccount"
|
||||
AUTH_USER_MODEL = "users.Account"
|
||||
|
||||
|
||||
CORS_ORIGIN_WHITELIST = ['http://localhost:8000',
|
||||
'http://localhost:3000']
|
||||
CSRF_TRUSTED_ORIGINS = ['http://localhost:8000',
|
||||
'http://localhost:3000']
|
||||
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.URLPathVersioning",
|
||||
"DEFAULT_VERSION": "v1",
|
||||
|
||||
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
|
||||
"PAGE_SIZE": 10,
|
||||
|
||||
"DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"],
|
||||
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"rest_framework_simplejwt.authentication.JWTAuthentication",
|
||||
"rest_framework.authentication.SessionAuthentication", # Сессионная аутентификация
|
||||
),
|
||||
|
||||
}
|
||||
|
||||
SIMPLE_JWT = {
|
||||
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=30),
|
||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=1),
|
||||
}
|
||||
|
||||
DJOSER = {
|
||||
"LOGIN_FIELD": "login", # Используем поле login вместо username
|
||||
"USER_CREATE_PASSWORD_RETYPE": True,
|
||||
"SET_PASSWORD_RETYPE": True,
|
||||
"PASSWORD_RESET_CONFIRM_URL": "password/reset/confirm/{uid}/{token}",
|
||||
"USERNAME_RESET_CONFIRM_URL": "username/reset/confirm/{uid}/{token}",
|
||||
"ACTIVATION_URL": "activate/{uid}/{token}",
|
||||
"SEND_ACTIVATION_EMAIL": False, # Отключаем отправку email, если не нужно
|
||||
"SERIALIZERS": {
|
||||
"user_create": "users.serializers.AccountSerializer",
|
||||
"user": "users.serializers.AccountSerializer",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,9 +14,42 @@ Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
from django.urls import path, include
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework import permissions
|
||||
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="ProFi API",
|
||||
default_version='v1',
|
||||
description="API for ProFi (v1)",
|
||||
contact=openapi.Contact(email="support@novelplus.ru"),
|
||||
|
||||
|
||||
),
|
||||
public=True,
|
||||
permission_classes=[permissions.AllowAny],
|
||||
)
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
path('api/v1/education/', include("education.urls")),
|
||||
path('api/v1/employees/', include("employees.urls")),
|
||||
path('api/v1/events/', include("events.urls")),
|
||||
path('api/v1/outreach/', include("outreach.urls")),
|
||||
path('api/v1/surveys/', include("surveys.urls")),
|
||||
path('api/v1/users/', include("users.urls")),
|
||||
|
||||
path('docs/swagger<format>/', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||
path('docs/swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||
path('docs/redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
|
||||
@@ -1,3 +1,61 @@
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import Account, Role
|
||||
|
||||
|
||||
class AccountChangeForm(UserChangeForm):
|
||||
|
||||
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
|
||||
class AccountCreationForm(UserCreationForm):
|
||||
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = ('login',)
|
||||
|
||||
|
||||
@admin.register(Account)
|
||||
class AccountAdmin(BaseUserAdmin):
|
||||
form = AccountChangeForm
|
||||
add_form = AccountCreationForm
|
||||
|
||||
list_display = ('login', 'full_name', 'role', 'is_staff', 'is_active')
|
||||
list_filter = ('is_staff', 'is_active', 'role')
|
||||
search_fields = ('login', 'first_name', 'last_name', 'middle_name', 'phone', 'email')
|
||||
ordering = ('login',)
|
||||
filter_horizontal = ('groups', 'user_permissions')
|
||||
|
||||
fieldsets = (
|
||||
(None, {'fields': ('login', 'password')}),
|
||||
(_('Personal info'), {'fields': ('first_name', 'last_name', 'middle_name', 'phone', 'email')}),
|
||||
(_('Permissions'), {
|
||||
'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'),
|
||||
}),
|
||||
(_('Role'), {'fields': ('role',)}),
|
||||
)
|
||||
add_fieldsets = (
|
||||
(None, {
|
||||
'classes': ('wide',),
|
||||
'fields': ('login', 'password1', 'password2'),
|
||||
}),
|
||||
)
|
||||
|
||||
@admin.display(description=_('ФИО'))
|
||||
def full_name(self, obj):
|
||||
return str(obj)
|
||||
|
||||
|
||||
@admin.register(Role)
|
||||
class RoleAdmin(admin.ModelAdmin):
|
||||
list_display = ('name',)
|
||||
search_fields = ('name',)
|
||||
|
||||
# Register your models here.
|
||||
|
||||
@@ -4,3 +4,4 @@ from django.apps import AppConfig
|
||||
class UsersConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'users'
|
||||
verbose_name = "Пользователи"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-14 16:31
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:21
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
@@ -17,35 +17,35 @@ class Migration(migrations.Migration):
|
||||
name='Role',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=20, unique=True, verbose_name='Название роли')),
|
||||
('name', models.CharField(max_length=100, verbose_name='Название роли')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Роль',
|
||||
'verbose_name_plural': 'Роли',
|
||||
'verbose_name': 'Роль пользователя',
|
||||
'verbose_name_plural': 'Роли пользователей',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserAccount',
|
||||
name='Account',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('first_name', models.CharField(max_length=100, verbose_name='Имя')),
|
||||
('last_name', models.CharField(max_length=100, verbose_name='Фамилия')),
|
||||
('last_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Фамилия')),
|
||||
('middle_name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Отчество')),
|
||||
('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Телефон')),
|
||||
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Почта')),
|
||||
('username', models.CharField(max_length=150, unique=True, verbose_name='Имя пользователя')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Активен')),
|
||||
('login', models.CharField(max_length=150, unique=True, verbose_name='Логин')),
|
||||
('is_staff', models.BooleanField(default=False, verbose_name='Сотрудник')),
|
||||
('is_active', models.BooleanField(default=True, verbose_name='Активен')),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('role', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.role', verbose_name='Роль')),
|
||||
('role', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.role', verbose_name='Роль пользователя')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Пользователь',
|
||||
'verbose_name_plural': 'Пользователи',
|
||||
'verbose_name': 'Аккаунт',
|
||||
'verbose_name_plural': 'Аккаунты',
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
19
users/migrations/0002_alter_account_role.py
Normal file
19
users/migrations/0002_alter_account_role.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:30
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='account',
|
||||
name='role',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.role', unique=True, verbose_name='Роль пользователя'),
|
||||
),
|
||||
]
|
||||
19
users/migrations/0003_alter_account_role.py
Normal file
19
users/migrations/0003_alter_account_role.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 4.2.21 on 2025-05-18 18:39
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0002_alter_account_role'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='account',
|
||||
name='role',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='users.role', verbose_name='Роль пользователя'),
|
||||
),
|
||||
]
|
||||
@@ -1,48 +1,54 @@
|
||||
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
|
||||
from django.db import models
|
||||
|
||||
from core.mixins import ContactMixin
|
||||
from core.models import ContactMixin
|
||||
|
||||
|
||||
## Пользователи
|
||||
|
||||
class Role(models.Model):
|
||||
name = models.CharField(max_length=20, unique=True, verbose_name='Название роли')
|
||||
name = models.CharField(max_length=100, verbose_name="Название роли")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Роль'
|
||||
verbose_name_plural = 'Роли'
|
||||
verbose_name = "Роль пользователя"
|
||||
verbose_name_plural = "Роли пользователей"
|
||||
|
||||
class UserAccountManager(BaseUserManager):
|
||||
def create_user(self, username, password=None, role=None, **extra_fields):
|
||||
if not username:
|
||||
raise ValueError('Имя пользователя должно быть указано')
|
||||
user = self.model(username=username, role=role, **extra_fields)
|
||||
|
||||
class AccountManager(BaseUserManager):
|
||||
def create_user(self, login, password=None, **extra_fields):
|
||||
if not login:
|
||||
raise ValueError("Login must be set")
|
||||
user = self.model(login=login, **extra_fields)
|
||||
user.set_password(password)
|
||||
user.save(using=self._db)
|
||||
return user
|
||||
|
||||
def create_superuser(self, username, password=None, **extra_fields):
|
||||
admin_role, _ = Role.objects.get_or_create(name='admin')
|
||||
extra_fields.setdefault('is_staff', True)
|
||||
extra_fields.setdefault('is_superuser', True)
|
||||
return self.create_user(username, password, role=admin_role, **extra_fields)
|
||||
def create_superuser(self, login, password=None, **extra_fields):
|
||||
extra_fields.setdefault("is_staff", True)
|
||||
extra_fields.setdefault("is_superuser", True)
|
||||
return self.create_user(login, password, **extra_fields)
|
||||
|
||||
class UserAccount(ContactMixin, AbstractBaseUser, PermissionsMixin):
|
||||
username = models.CharField(max_length=150, unique=True, verbose_name='Имя пользователя')
|
||||
role = models.ForeignKey(Role, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='Роль')
|
||||
is_active = models.BooleanField(default=True, verbose_name='Активен')
|
||||
is_staff = models.BooleanField(default=False, verbose_name='Сотрудник')
|
||||
|
||||
objects = UserAccountManager()
|
||||
class Account(ContactMixin, AbstractBaseUser, PermissionsMixin):
|
||||
login = models.CharField(max_length=150, unique=True, verbose_name="Логин")
|
||||
role = models.ForeignKey(
|
||||
Role, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Роль пользователя"
|
||||
)
|
||||
is_staff = models.BooleanField(default=False, verbose_name="Сотрудник")
|
||||
is_active = models.BooleanField(default=True, verbose_name="Активен")
|
||||
|
||||
USERNAME_FIELD = 'username'
|
||||
USERNAME_FIELD = "login"
|
||||
REQUIRED_FIELDS = []
|
||||
|
||||
objects = AccountManager()
|
||||
|
||||
def __str__(self):
|
||||
return self.username+" ("+ContactMixin.__str__(self)+")"
|
||||
return super().__str__() or self.login
|
||||
|
||||
class Meta:
|
||||
verbose_name = 'Пользователь'
|
||||
verbose_name_plural = 'Пользователи'
|
||||
verbose_name = "Аккаунт"
|
||||
verbose_name_plural = "Аккаунты"
|
||||
|
||||
|
||||
22
users/serializers.py
Normal file
22
users/serializers.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from rest_framework import serializers
|
||||
from djoser.serializers import UserCreateSerializer
|
||||
from .models import Account, Role
|
||||
|
||||
class RoleSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Role
|
||||
fields = "__all__"
|
||||
|
||||
|
||||
class AccountSerializer(UserCreateSerializer):
|
||||
role = serializers.PrimaryKeyRelatedField(queryset=Role.objects.all(), required=False)
|
||||
|
||||
class Meta(UserCreateSerializer.Meta):
|
||||
model = Account
|
||||
fields = ["id", "login", "role", "is_staff", "is_active", "first_name", "last_name", "middle_name", "phone", "email"]
|
||||
|
||||
def create(self, validated_data):
|
||||
user = super().create(validated_data)
|
||||
user.set_password(validated_data["password"])
|
||||
user.save()
|
||||
return user
|
||||
13
users/urls.py
Normal file
13
users/urls.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import AccountViewSet, RoleViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"users", AccountViewSet, basename="user")
|
||||
router.register(r"roles", RoleViewSet, basename="role")
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
path("auth/", include("djoser.urls")),
|
||||
path("auth/", include("djoser.urls.jwt")),
|
||||
]
|
||||
@@ -1,3 +1,15 @@
|
||||
from django.shortcuts import render
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView
|
||||
from djoser.views import UserViewSet as DjoserUserViewSet
|
||||
from .models import Account, Role
|
||||
from .serializers import AccountSerializer, RoleSerializer
|
||||
|
||||
# Create your views here.
|
||||
class AccountViewSet(DjoserUserViewSet):
|
||||
queryset = Account.objects.all()
|
||||
serializer_class = AccountSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
class RoleViewSet(viewsets.ModelViewSet):
|
||||
queryset = Role.objects.all()
|
||||
serializer_class = RoleSerializer
|
||||
Reference in New Issue
Block a user