diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..c7f513f --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,17 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:C:\Users\Mikan\PycharmProjects\tisbiProFi\db.sqlite3 + $ProjectFileDir$ + + + 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 + + + + + \ No newline at end of file diff --git a/core/mixins.py b/core/mixins.py deleted file mode 100644 index 126da32..0000000 --- a/core/mixins.py +++ /dev/null @@ -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 - diff --git a/core/models.py b/core/models.py index e69de29..bf5641e 100644 --- a/core/models.py +++ b/core/models.py @@ -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 f"{self.last_name} {self.first_name}" + + class Meta: + abstract = True + diff --git a/education/__init__.py b/education/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/admin.py b/education/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/education/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/education/apps.py b/education/apps.py new file mode 100644 index 0000000..4a92e70 --- /dev/null +++ b/education/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EducationConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'education' diff --git a/education/migrations/0001_initial.py b/education/migrations/0001_initial.py new file mode 100644 index 0000000..7ffd609 --- /dev/null +++ b/education/migrations/0001_initial.py @@ -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': 'Студенты', + }, + ), + ] diff --git a/education/migrations/0002_initial.py b/education/migrations/0002_initial.py new file mode 100644 index 0000000..997fda6 --- /dev/null +++ b/education/migrations/0002_initial.py @@ -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')}, + ), + ] diff --git a/education/migrations/__init__.py b/education/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/education/models.py b/education/models.py new file mode 100644 index 0000000..b045768 --- /dev/null +++ b/education/models.py @@ -0,0 +1,93 @@ +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 = "Учебные годы" + + +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 = "Учебные отделения" + + +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") + + +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 = "Специальности" + + +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 = "Группы" + + +class Student(ContactMixin): + group = models.ForeignKey( + Group, on_delete=models.CASCADE, verbose_name="Группа" + ) + account = models.OneToOneField( + Account, on_delete=models.CASCADE, unique=True, verbose_name="Аккаунт" + ) + + def __str__(self): + return f"{self.last_name} {self.first_name} - {self.group}" + + class Meta: + verbose_name = "Студент" + verbose_name_plural = "Студенты" + diff --git a/education/tests.py b/education/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/education/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/education/views.py b/education/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/education/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/employees/__init__.py b/employees/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/employees/admin.py b/employees/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/employees/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/employees/apps.py b/employees/apps.py new file mode 100644 index 0000000..52b700d --- /dev/null +++ b/employees/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EmployeesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'employees' diff --git a/employees/migrations/0001_initial.py b/employees/migrations/0001_initial.py new file mode 100644 index 0000000..dad17bc --- /dev/null +++ b/employees/migrations/0001_initial.py @@ -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': 'Должности', + }, + ), + ] diff --git a/employees/migrations/0002_initial.py b/employees/migrations/0002_initial.py new file mode 100644 index 0000000..294d60b --- /dev/null +++ b/employees/migrations/0002_initial.py @@ -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='Должность'), + ), + ] diff --git a/employees/migrations/__init__.py b/employees/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/employees/models.py b/employees/models.py new file mode 100644 index 0000000..042230d --- /dev/null +++ b/employees/models.py @@ -0,0 +1,45 @@ +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 = "Должности" + + +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 = "Подразделения" + + +class Employee(ContactMixin): + 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="Аккаунт" + ) + + def __str__(self): + return f"{self.last_name} {self.first_name} - {self.position}" + + class Meta: + verbose_name = "Сотрудник" + verbose_name_plural = "Сотрудники" + diff --git a/employees/tests.py b/employees/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/employees/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/employees/views.py b/employees/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/employees/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/events/__init__.py b/events/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/events/admin.py b/events/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/events/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/events/apps.py b/events/apps.py new file mode 100644 index 0000000..20f48f2 --- /dev/null +++ b/events/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class EventsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'events' diff --git a/events/migrations/0001_initial.py b/events/migrations/0001_initial.py new file mode 100644 index 0000000..eaa8341 --- /dev/null +++ b/events/migrations/0001_initial.py @@ -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')}, + }, + ), + ] diff --git a/events/migrations/__init__.py b/events/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/events/models.py b/events/models.py new file mode 100644 index 0000000..c0b5dce --- /dev/null +++ b/events/models.py @@ -0,0 +1,133 @@ +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 = "Статусы проведения" + + +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 = "Деятельности мероприятий" + + +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 = "Мероприятия" + + +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") + + +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") + + +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 = "Участники мероприятий" + + +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 = "Методические файлы" + + +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 = "Релевантные методические файлы" + diff --git a/events/tests.py b/events/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/events/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/events/views.py b/events/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/events/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/outreach/__init__.py b/outreach/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/outreach/admin.py b/outreach/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/outreach/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/outreach/apps.py b/outreach/apps.py new file mode 100644 index 0000000..75c0650 --- /dev/null +++ b/outreach/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class OutreachConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'outreach' diff --git a/outreach/migrations/0001_initial.py b/outreach/migrations/0001_initial.py new file mode 100644 index 0000000..667a4ef --- /dev/null +++ b/outreach/migrations/0001_initial.py @@ -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')}, + }, + ), + ] diff --git a/outreach/migrations/__init__.py b/outreach/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/outreach/models.py b/outreach/models.py new file mode 100644 index 0000000..9046a0f --- /dev/null +++ b/outreach/models.py @@ -0,0 +1,94 @@ +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 = "Виды учебных заведений" + + +class Representative(ContactMixin): + position = models.CharField(max_length=100, blank=True, null=True, verbose_name="Должность") + + def __str__(self): + return f"{self.last_name} {self.first_name} - {self.position}" + + class Meta: + verbose_name = "Представитель" + verbose_name_plural = "Представители" + + +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 = "Школы" + + +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 = "Партнеры" + + +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") + + +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 = "Ответственные за школы" + diff --git a/outreach/tests.py b/outreach/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/outreach/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/outreach/views.py b/outreach/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/outreach/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/surveys/__init__.py b/surveys/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/surveys/admin.py b/surveys/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/surveys/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/surveys/apps.py b/surveys/apps.py new file mode 100644 index 0000000..840a0c6 --- /dev/null +++ b/surveys/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SurveysConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'surveys' diff --git a/surveys/migrations/0001_initial.py b/surveys/migrations/0001_initial.py new file mode 100644 index 0000000..7591626 --- /dev/null +++ b/surveys/migrations/0001_initial.py @@ -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')}, + }, + ), + ] diff --git a/surveys/migrations/__init__.py b/surveys/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/surveys/models.py b/surveys/models.py new file mode 100644 index 0000000..e348002 --- /dev/null +++ b/surveys/models.py @@ -0,0 +1,151 @@ +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 = "Опросы" + + +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 = "Переменные оценки" + + +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 = "Утверждения" + + +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 = "Пары вопросов" + + +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 = "Вопросы на согласие" + + +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 = "Образовательные учреждения" + + +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 = "Профессии" + + +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 = "Специальности" + + +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") + + +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 = "Результаты тестов по профессиям" + diff --git a/surveys/tests.py b/surveys/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/surveys/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/surveys/views.py b/surveys/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/surveys/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/tisbiProFi/settings.py b/tisbiProFi/settings.py index 5e4290a..ed8f66c 100644 --- a/tisbiProFi/settings.py +++ b/tisbiProFi/settings.py @@ -41,6 +41,11 @@ INSTALLED_APPS = [ 'django.contrib.staticfiles', "core.apps.CoreConfig", 'users.apps.UsersConfig', + "employees.apps.EmployeesConfig", + "education.apps.EducationConfig", + "events.apps.EventsConfig", + "outreach.apps.OutreachConfig", + "surveys.apps.SurveysConfig" ] MIDDLEWARE = [ @@ -143,4 +148,4 @@ MEDIA_ROOT = BASE_DIR / 'media' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' -AUTH_USER_MODEL = "users.UserAccount" +AUTH_USER_MODEL = "users.Account" diff --git a/users/admin.py b/users/admin.py index 8c38f3f..8d40910 100644 --- a/users/admin.py +++ b/users/admin.py @@ -1,3 +1,85 @@ +from django import forms from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.utils.translation import gettext_lazy as _ -# Register your models here. +from .models import Account, Role + + +class AccountChangeForm(forms.ModelForm): + password = forms.CharField( + label=_("Password"), + widget=forms.PasswordInput, + required=False, + help_text=_("Raw passwords are not stored, so there is no way to see this user's password.") + ) + + class Meta: + model = Account + fields = '__all__' + + def save(self, commit=True): + password = self.cleaned_data.get('password') + if password: + self.instance.set_password(password) + return super().save(commit=commit) + + +class AccountCreationForm(forms.ModelForm): + password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput) + password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput) + + class Meta: + model = Account + fields = ('login',) + + def clean_password2(self): + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise forms.ValidationError(_("Passwords don't match")) + return password2 + + def save(self, commit=True): + user = super().save(commit=False) + user.set_password(self.cleaned_data["password1"]) + if commit: + user.save() + return user + + +@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=_('Full name')) + def full_name(self, obj): + return str(obj) + + +@admin.register(Role) +class RoleAdmin(admin.ModelAdmin): + list_display = ('name',) + search_fields = ('name',) \ No newline at end of file diff --git a/users/apps.py b/users/apps.py index 72b1401..855bb24 100644 --- a/users/apps.py +++ b/users/apps.py @@ -4,3 +4,4 @@ from django.apps import AppConfig class UsersConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'users' + verbose_name = "Пользователи" diff --git a/users/migrations/0001_initial.py b/users/migrations/0001_initial.py index f9538e4..a81fa82 100644 --- a/users/migrations/0001_initial.py +++ b/users/migrations/0001_initial.py @@ -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': 'Аккаунты', }, ), ] diff --git a/users/models.py b/users/models.py index 094b7da..4758e2d 100644 --- a/users/models.py +++ b/users/models.py @@ -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 self.login class Meta: - verbose_name = 'Пользователь' - verbose_name_plural = 'Пользователи' \ No newline at end of file + verbose_name = "Аккаунт" + verbose_name_plural = "Аккаунты" +