init
This commit is contained in:
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@ -0,0 +1,25 @@
|
||||
# Используем официальный образ Python
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Устанавливаем переменные окружения
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
# Устанавливаем рабочую директорию
|
||||
WORKDIR /app
|
||||
|
||||
# Устанавливаем зависимости
|
||||
COPY requirements.txt /app/
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Копируем исходный код проекта
|
||||
COPY . /app/
|
||||
|
||||
# Собираем статические файлы
|
||||
RUN python manage.py collectstatic --noinput
|
||||
|
||||
# Открываем порт
|
||||
EXPOSE 8000
|
||||
|
||||
# Запускаем Gunicorn
|
||||
CMD ["gunicorn", "driving_school.wsgi:application", "--bind", "0.0.0.0:8000"]
|
50
README.md
Normal file
50
README.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Автошкола - Система управления
|
||||
|
||||
Система управления автошколой с функциями для инструкторов, диспетчеров и студентов.
|
||||
|
||||
## Функциональность
|
||||
|
||||
- Управление расписанием занятий
|
||||
- Система записи на занятия
|
||||
- Личный кабинет инструктора
|
||||
- Личный кабинет студента
|
||||
- Аналитика для администраторов
|
||||
- PWA поддержка
|
||||
|
||||
## Установка
|
||||
|
||||
1. Создайте виртуальное окружение:
|
||||
```bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate # для Linux/Mac
|
||||
venv\Scripts\activate # для Windows
|
||||
```
|
||||
|
||||
2. Установите зависимости:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
3. Примените миграции:
|
||||
```bash
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
4. Создайте суперпользователя:
|
||||
```bash
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
5. Запустите сервер:
|
||||
```bash
|
||||
python manage.py runserver
|
||||
```
|
||||
|
||||
## Структура проекта
|
||||
|
||||
- `driving_school/` - основной проект
|
||||
- `accounts/` - приложение для управления пользователями
|
||||
- `schedule/` - приложение для управления расписанием
|
||||
- `instructor/` - приложение для инструкторов
|
||||
- `student/` - приложение для студентов
|
||||
- `analytics/` - приложение для аналитики
|
BIN
accounts/__pycache__/admin.cpython-313.pyc
Normal file
BIN
accounts/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
accounts/__pycache__/forms.cpython-313.pyc
Normal file
BIN
accounts/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
accounts/__pycache__/models.cpython-313.pyc
Normal file
BIN
accounts/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
accounts/__pycache__/urls.cpython-313.pyc
Normal file
BIN
accounts/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
accounts/__pycache__/views.cpython-313.pyc
Normal file
BIN
accounts/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
53
accounts/forms.py
Normal file
53
accounts/forms.py
Normal file
@ -0,0 +1,53 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from .models import Profile
|
||||
from instructor.models import Instructor
|
||||
|
||||
class UserRegistrationForm(UserCreationForm):
|
||||
email = forms.EmailField(required=True)
|
||||
first_name = forms.CharField(required=True)
|
||||
last_name = forms.CharField(required=True)
|
||||
phone = forms.CharField(max_length=15, required=True)
|
||||
address = forms.CharField(widget=forms.Textarea, required=True)
|
||||
birth_date = forms.DateField(required=True, widget=forms.DateInput(attrs={'type': 'date'}))
|
||||
specialization = forms.CharField(max_length=100, required=True)
|
||||
experience_years = forms.IntegerField(min_value=0, required=True)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('username', 'email', 'first_name', 'last_name', 'password1', 'password2')
|
||||
|
||||
def save(self, commit=True):
|
||||
user = super().save(commit=False)
|
||||
user.email = self.cleaned_data['email']
|
||||
user.first_name = self.cleaned_data['first_name']
|
||||
user.last_name = self.cleaned_data['last_name']
|
||||
|
||||
if commit:
|
||||
user.save()
|
||||
|
||||
# Создаем профиль
|
||||
profile = user.profile
|
||||
profile.user_type = 'instructor'
|
||||
profile.phone = self.cleaned_data['phone']
|
||||
profile.address = self.cleaned_data['address']
|
||||
profile.birth_date = self.cleaned_data['birth_date']
|
||||
profile.save()
|
||||
|
||||
# Создаем инструктора
|
||||
instructor = Instructor.objects.create(
|
||||
profile=profile,
|
||||
specialization=self.cleaned_data['specialization'],
|
||||
experience_years=self.cleaned_data['experience_years']
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
class ProfileUpdateForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Profile
|
||||
fields = ['phone', 'address', 'birth_date']
|
||||
widgets = {
|
||||
'birth_date': forms.DateInput(attrs={'type': 'date'}),
|
||||
}
|
30
accounts/migrations/0001_initial.py
Normal file
30
accounts/migrations/0001_initial.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.2 on 2025-06-10 19:01
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Profile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('user_type', models.CharField(choices=[('student', 'Студент'), ('instructor', 'Инструктор'), ('dispatcher', 'Диспетчер'), ('admin', 'Администратор')], max_length=20)),
|
||||
('phone', models.CharField(blank=True, max_length=15)),
|
||||
('address', models.TextField(blank=True)),
|
||||
('birth_date', models.DateField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
]
|
0
accounts/migrations/__init__.py
Normal file
0
accounts/migrations/__init__.py
Normal file
BIN
accounts/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
BIN
accounts/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
Binary file not shown.
BIN
accounts/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
accounts/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
32
accounts/models.py
Normal file
32
accounts/models.py
Normal file
@ -0,0 +1,32 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
class Profile(models.Model):
|
||||
USER_TYPE_CHOICES = (
|
||||
('student', 'Студент'),
|
||||
('instructor', 'Инструктор'),
|
||||
('dispatcher', 'Диспетчер'),
|
||||
('admin', 'Администратор'),
|
||||
)
|
||||
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
user_type = models.CharField(max_length=20, choices=USER_TYPE_CHOICES)
|
||||
phone = models.CharField(max_length=15, blank=True)
|
||||
address = models.TextField(blank=True)
|
||||
birth_date = models.DateField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.username} - {self.get_user_type_display()}"
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def create_user_profile(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
Profile.objects.create(user=instance)
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
def save_user_profile(sender, instance, **kwargs):
|
||||
instance.profile.save()
|
15
accounts/urls.py
Normal file
15
accounts/urls.py
Normal file
@ -0,0 +1,15 @@
|
||||
from django.urls import path
|
||||
from django.contrib.auth import views as auth_views
|
||||
from . import views
|
||||
|
||||
app_name = 'accounts'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.home, name='home'),
|
||||
path('register/', views.register, name='register'),
|
||||
path('register/instructor/', views.register_instructor, name='register_instructor'),
|
||||
path('profile/', views.profile, name='profile'),
|
||||
path('profile/update/', views.profile_update, name='profile_update'),
|
||||
path('login/', auth_views.LoginView.as_view(template_name='accounts/login.html'), name='login'),
|
||||
path('logout/', auth_views.LogoutView.as_view(template_name='accounts/logout.html'), name='logout'),
|
||||
]
|
46
accounts/views.py
Normal file
46
accounts/views.py
Normal file
@ -0,0 +1,46 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from .models import Profile
|
||||
from .forms import UserRegistrationForm, ProfileUpdateForm
|
||||
|
||||
def home(request):
|
||||
return render(request, 'accounts/home.html')
|
||||
|
||||
def register(request):
|
||||
if request.method == 'POST':
|
||||
form = UserRegistrationForm(request.POST)
|
||||
if form.is_valid():
|
||||
user = form.save()
|
||||
messages.success(request, 'Аккаунт успешно создан!')
|
||||
return redirect('login')
|
||||
else:
|
||||
form = UserRegistrationForm()
|
||||
return render(request, 'accounts/register.html', {'form': form})
|
||||
|
||||
def register_instructor(request):
|
||||
if request.method == 'POST':
|
||||
form = UserRegistrationForm(request.POST)
|
||||
if form.is_valid():
|
||||
user = form.save()
|
||||
messages.success(request, 'Инструктор успешно зарегистрирован!')
|
||||
return redirect('login')
|
||||
else:
|
||||
form = UserRegistrationForm()
|
||||
return render(request, 'accounts/register_instructor.html', {'form': form})
|
||||
|
||||
@login_required
|
||||
def profile(request):
|
||||
return render(request, 'accounts/profile.html')
|
||||
|
||||
@login_required
|
||||
def profile_update(request):
|
||||
if request.method == 'POST':
|
||||
form = ProfileUpdateForm(request.POST, instance=request.user.profile)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Профиль успешно обновлен!')
|
||||
return redirect('profile')
|
||||
else:
|
||||
form = ProfileUpdateForm(instance=request.user.profile)
|
||||
return render(request, 'accounts/profile_update.html', {'form': form})
|
BIN
analytics/__pycache__/admin.cpython-313.pyc
Normal file
BIN
analytics/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
analytics/__pycache__/models.cpython-313.pyc
Normal file
BIN
analytics/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
analytics/__pycache__/urls.cpython-313.pyc
Normal file
BIN
analytics/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
analytics/__pycache__/views.cpython-313.pyc
Normal file
BIN
analytics/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
20
analytics/admin.py
Normal file
20
analytics/admin.py
Normal file
@ -0,0 +1,20 @@
|
||||
from django.contrib import admin
|
||||
from .models import CourseAnalytics, InstructorAnalytics, StudentAnalytics
|
||||
|
||||
@admin.register(CourseAnalytics)
|
||||
class CourseAnalyticsAdmin(admin.ModelAdmin):
|
||||
list_display = ('course', 'total_students', 'average_progress', 'completion_rate', 'revenue', 'created_at', 'updated_at')
|
||||
list_filter = ('created_at', 'updated_at')
|
||||
search_fields = ('course__title',)
|
||||
|
||||
@admin.register(InstructorAnalytics)
|
||||
class InstructorAnalyticsAdmin(admin.ModelAdmin):
|
||||
list_display = ('instructor', 'total_lessons', 'total_students', 'average_rating', 'completion_rate', 'created_at', 'updated_at')
|
||||
list_filter = ('created_at', 'updated_at')
|
||||
search_fields = ('instructor__profile__user__username', 'instructor__profile__user__email')
|
||||
|
||||
@admin.register(StudentAnalytics)
|
||||
class StudentAnalyticsAdmin(admin.ModelAdmin):
|
||||
list_display = ('student', 'total_lessons', 'attendance_rate', 'average_grade', 'created_at', 'updated_at')
|
||||
list_filter = ('created_at', 'updated_at')
|
||||
search_fields = ('student__profile__user__username', 'student__profile__user__email')
|
56
analytics/migrations/0001_initial.py
Normal file
56
analytics/migrations/0001_initial.py
Normal file
@ -0,0 +1,56 @@
|
||||
# Generated by Django 5.0.2 on 2025-06-10 20:05
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('instructor', '0001_initial'),
|
||||
('schedule', '0003_alter_lesson_options_remove_lesson_lesson_type_and_more'),
|
||||
('student', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CourseAnalytics',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('total_students', models.IntegerField(default=0)),
|
||||
('average_progress', models.DecimalField(decimal_places=2, default=0, max_digits=5)),
|
||||
('completion_rate', models.DecimalField(decimal_places=2, default=0, max_digits=5)),
|
||||
('revenue', models.DecimalField(decimal_places=2, default=0, max_digits=10)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='schedule.course')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InstructorAnalytics',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('total_lessons', models.IntegerField(default=0)),
|
||||
('total_students', models.IntegerField(default=0)),
|
||||
('average_rating', models.DecimalField(decimal_places=2, default=5.0, max_digits=3)),
|
||||
('completion_rate', models.DecimalField(decimal_places=2, default=0, max_digits=5)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('instructor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='instructor.instructor')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='StudentAnalytics',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('total_lessons', models.IntegerField(default=0)),
|
||||
('attendance_rate', models.DecimalField(decimal_places=2, default=0, max_digits=5)),
|
||||
('average_grade', models.DecimalField(decimal_places=2, default=0, max_digits=3)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('student', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='student.student')),
|
||||
],
|
||||
),
|
||||
]
|
20
analytics/migrations/0002_alter_courseanalytics_course.py
Normal file
20
analytics/migrations/0002_alter_courseanalytics_course.py
Normal file
@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.0.2 on 2025-06-10 20:24
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('analytics', '0001_initial'),
|
||||
('course', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='courseanalytics',
|
||||
name='course',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='course.course'),
|
||||
),
|
||||
]
|
0
analytics/migrations/__init__.py
Normal file
0
analytics/migrations/__init__.py
Normal file
BIN
analytics/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
BIN
analytics/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
analytics/migrations/__pycache__/0002_initial.cpython-313.pyc
Normal file
BIN
analytics/migrations/__pycache__/0002_initial.cpython-313.pyc
Normal file
Binary file not shown.
BIN
analytics/migrations/__pycache__/0003_initial.cpython-313.pyc
Normal file
BIN
analytics/migrations/__pycache__/0003_initial.cpython-313.pyc
Normal file
Binary file not shown.
BIN
analytics/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
analytics/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
41
analytics/models.py
Normal file
41
analytics/models.py
Normal file
@ -0,0 +1,41 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from course.models import Course
|
||||
from schedule.models import Lesson
|
||||
from instructor.models import Instructor
|
||||
from student.models import Student
|
||||
|
||||
class CourseAnalytics(models.Model):
|
||||
course = models.ForeignKey(Course, on_delete=models.CASCADE)
|
||||
total_students = models.IntegerField(default=0)
|
||||
average_progress = models.DecimalField(max_digits=5, decimal_places=2, default=0)
|
||||
completion_rate = models.DecimalField(max_digits=5, decimal_places=2, default=0)
|
||||
revenue = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Аналитика курса {self.course.title}"
|
||||
|
||||
class InstructorAnalytics(models.Model):
|
||||
instructor = models.ForeignKey(Instructor, on_delete=models.CASCADE)
|
||||
total_lessons = models.IntegerField(default=0)
|
||||
total_students = models.IntegerField(default=0)
|
||||
average_rating = models.DecimalField(max_digits=3, decimal_places=2, default=5.00)
|
||||
completion_rate = models.DecimalField(max_digits=5, decimal_places=2, default=0)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Аналитика инструктора {self.instructor}"
|
||||
|
||||
class StudentAnalytics(models.Model):
|
||||
student = models.ForeignKey(Student, on_delete=models.CASCADE)
|
||||
total_lessons = models.IntegerField(default=0)
|
||||
attendance_rate = models.DecimalField(max_digits=5, decimal_places=2, default=0)
|
||||
average_grade = models.DecimalField(max_digits=3, decimal_places=2, default=0)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Аналитика студента {self.student}"
|
13
analytics/urls.py
Normal file
13
analytics/urls.py
Normal file
@ -0,0 +1,13 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'analytics'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.analytics_dashboard, name='analytics_dashboard'),
|
||||
path('courses/', views.course_analytics, name='course_analytics'),
|
||||
path('instructors/', views.instructor_analytics, name='instructor_analytics'),
|
||||
path('students/', views.student_analytics, name='student_analytics'),
|
||||
path('reports/', views.generate_reports, name='generate_reports'),
|
||||
path('export/', views.export_data, name='export_data'),
|
||||
]
|
78
analytics/views.py
Normal file
78
analytics/views.py
Normal file
@ -0,0 +1,78 @@
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
||||
from .models import CourseAnalytics, InstructorAnalytics, StudentAnalytics
|
||||
from schedule.models import Course, Lesson, LessonEnrollment
|
||||
from instructor.models import Instructor
|
||||
from student.models import Student
|
||||
import json
|
||||
from django.http import HttpResponse
|
||||
|
||||
def is_admin(user):
|
||||
return user.profile.user_type == 'admin'
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_admin)
|
||||
def analytics_dashboard(request):
|
||||
course_analytics = CourseAnalytics.objects.all()
|
||||
instructor_analytics = InstructorAnalytics.objects.all()
|
||||
student_analytics = StudentAnalytics.objects.all()
|
||||
|
||||
return render(request, 'analytics/dashboard.html', {
|
||||
'course_analytics': course_analytics,
|
||||
'instructor_analytics': instructor_analytics,
|
||||
'student_analytics': student_analytics
|
||||
})
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_admin)
|
||||
def course_analytics(request):
|
||||
courses = Course.objects.all()
|
||||
analytics = CourseAnalytics.objects.filter(course__in=courses)
|
||||
return render(request, 'analytics/course_analytics.html', {
|
||||
'courses': courses,
|
||||
'analytics': analytics
|
||||
})
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_admin)
|
||||
def instructor_analytics(request):
|
||||
instructors = Instructor.objects.all()
|
||||
analytics = InstructorAnalytics.objects.filter(instructor__in=instructors)
|
||||
return render(request, 'analytics/instructor_analytics.html', {
|
||||
'instructors': instructors,
|
||||
'analytics': analytics
|
||||
})
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_admin)
|
||||
def student_analytics(request):
|
||||
students = Student.objects.all()
|
||||
analytics = StudentAnalytics.objects.filter(student__in=students)
|
||||
return render(request, 'analytics/student_analytics.html', {
|
||||
'students': students,
|
||||
'analytics': analytics
|
||||
})
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_admin)
|
||||
def generate_reports(request):
|
||||
# Здесь будет логика генерации отчетов
|
||||
return render(request, 'analytics/reports.html')
|
||||
|
||||
@login_required
|
||||
@user_passes_test(is_admin)
|
||||
def export_data(request):
|
||||
data = {
|
||||
'courses': list(Course.objects.values()),
|
||||
'instructors': list(Instructor.objects.values()),
|
||||
'students': list(Student.objects.values()),
|
||||
'lessons': list(Lesson.objects.values()),
|
||||
'enrollments': list(LessonEnrollment.objects.values())
|
||||
}
|
||||
|
||||
response = HttpResponse(
|
||||
json.dumps(data, ensure_ascii=False),
|
||||
content_type='application/json'
|
||||
)
|
||||
response['Content-Disposition'] = 'attachment; filename="analytics_data.json"'
|
||||
return response
|
1
course/__init__.py
Normal file
1
course/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
BIN
course/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
course/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
course/__pycache__/admin.cpython-313.pyc
Normal file
BIN
course/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
course/__pycache__/apps.cpython-313.pyc
Normal file
BIN
course/__pycache__/apps.cpython-313.pyc
Normal file
Binary file not shown.
BIN
course/__pycache__/forms.cpython-313.pyc
Normal file
BIN
course/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
course/__pycache__/models.cpython-313.pyc
Normal file
BIN
course/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
course/__pycache__/test_views.cpython-313.pyc
Normal file
BIN
course/__pycache__/test_views.cpython-313.pyc
Normal file
Binary file not shown.
BIN
course/__pycache__/tests.cpython-313.pyc
Normal file
BIN
course/__pycache__/tests.cpython-313.pyc
Normal file
Binary file not shown.
BIN
course/__pycache__/urls.cpython-313.pyc
Normal file
BIN
course/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
course/__pycache__/views.cpython-313.pyc
Normal file
BIN
course/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
9
course/admin.py
Normal file
9
course/admin.py
Normal file
@ -0,0 +1,9 @@
|
||||
from django.contrib import admin
|
||||
from .models import Course
|
||||
|
||||
@admin.register(Course)
|
||||
class CourseAdmin(admin.ModelAdmin):
|
||||
list_display = ('title', 'description', 'created_at', 'updated_at')
|
||||
list_filter = ('created_at', 'updated_at')
|
||||
search_fields = ('title', 'description')
|
||||
date_hierarchy = 'created_at'
|
5
course/apps.py
Normal file
5
course/apps.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class CourseConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'course'
|
11
course/forms.py
Normal file
11
course/forms.py
Normal file
@ -0,0 +1,11 @@
|
||||
from django import forms
|
||||
from .models import Course
|
||||
|
||||
class CourseForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Course
|
||||
fields = ['title', 'description']
|
||||
widgets = {
|
||||
'title': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 4}),
|
||||
}
|
24
course/migrations/0001_initial.py
Normal file
24
course/migrations/0001_initial.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.0.2 on 2025-06-10 19:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Course',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('title', models.CharField(max_length=255)),
|
||||
('description', models.TextField(blank=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
]
|
1
course/migrations/__init__.py
Normal file
1
course/migrations/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
BIN
course/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
BIN
course/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
Binary file not shown.
BIN
course/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
course/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
10
course/models.py
Normal file
10
course/models.py
Normal file
@ -0,0 +1,10 @@
|
||||
from django.db import models
|
||||
|
||||
class Course(models.Model):
|
||||
title = models.CharField(max_length=255)
|
||||
description = models.TextField(blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
10
course/tests.py
Normal file
10
course/tests.py
Normal file
@ -0,0 +1,10 @@
|
||||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
from .models import Course
|
||||
|
||||
class CourseModelTest(TestCase):
|
||||
def setUp(self):
|
||||
self.course = Course.objects.create(
|
||||
title="Тестовый курс",
|
||||
description="Описание тестового курса"
|
||||
)
|
12
course/urls.py
Normal file
12
course/urls.py
Normal file
@ -0,0 +1,12 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'course'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.course_list, name='list'),
|
||||
path('create/', views.create_course, name='create'),
|
||||
path('<int:pk>/', views.course_detail, name='detail'),
|
||||
path('<int:pk>/update/', views.update_course, name='update'),
|
||||
path('<int:pk>/delete/', views.delete_course, name='delete'),
|
||||
]
|
65
course/views.py
Normal file
65
course/views.py
Normal file
@ -0,0 +1,65 @@
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from .models import Course
|
||||
from .forms import CourseForm
|
||||
|
||||
def course_list(request):
|
||||
courses = Course.objects.all().order_by('title')
|
||||
return render(request, 'course/course_list.html', {'courses': courses})
|
||||
|
||||
@login_required
|
||||
def course_detail(request, pk):
|
||||
course = get_object_or_404(Course, pk=pk)
|
||||
return render(request, 'course/course_detail.html', {'course': course})
|
||||
|
||||
@login_required
|
||||
def create_course(request):
|
||||
if not hasattr(request.user, 'instructor'):
|
||||
messages.error(request, 'У вас нет прав для создания курсов.')
|
||||
return redirect('course:list')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = CourseForm(request.POST)
|
||||
if form.is_valid():
|
||||
course = form.save()
|
||||
messages.success(request, 'Курс успешно создан.')
|
||||
return redirect('course:list')
|
||||
else:
|
||||
form = CourseForm()
|
||||
|
||||
return render(request, 'course/course_form.html', {'form': form, 'action': 'create'})
|
||||
|
||||
@login_required
|
||||
def update_course(request, pk):
|
||||
course = get_object_or_404(Course, pk=pk)
|
||||
|
||||
if not hasattr(request.user, 'instructor'):
|
||||
messages.error(request, 'У вас нет прав для редактирования курсов.')
|
||||
return redirect('course:list')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = CourseForm(request.POST, instance=course)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Курс успешно обновлен.')
|
||||
return redirect('course:list')
|
||||
else:
|
||||
form = CourseForm(instance=course)
|
||||
|
||||
return render(request, 'course/course_form.html', {'form': form, 'action': 'update'})
|
||||
|
||||
@login_required
|
||||
def delete_course(request, pk):
|
||||
course = get_object_or_404(Course, pk=pk)
|
||||
|
||||
if not hasattr(request.user, 'instructor'):
|
||||
messages.error(request, 'У вас нет прав для удаления курсов.')
|
||||
return redirect('course:list')
|
||||
|
||||
if request.method == 'POST':
|
||||
course.delete()
|
||||
messages.success(request, 'Курс успешно удален.')
|
||||
return redirect('course:list')
|
||||
|
||||
return render(request, 'course/course_confirm_delete.html', {'course': course})
|
BIN
courses/__pycache__/forms.cpython-313.pyc
Normal file
BIN
courses/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
courses/__pycache__/models.cpython-313.pyc
Normal file
BIN
courses/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
courses/__pycache__/urls.cpython-313.pyc
Normal file
BIN
courses/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
courses/__pycache__/views.cpython-313.pyc
Normal file
BIN
courses/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
13
courses/forms.py
Normal file
13
courses/forms.py
Normal file
@ -0,0 +1,13 @@
|
||||
from django import forms
|
||||
from .models import Course
|
||||
|
||||
class CourseForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Course
|
||||
fields = ['title', 'description', 'duration', 'price']
|
||||
widgets = {
|
||||
'title': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'description': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
'duration': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
'price': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
}
|
12
courses/urls.py
Normal file
12
courses/urls.py
Normal file
@ -0,0 +1,12 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'courses'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.course_list, name='list'),
|
||||
path('<int:pk>/', views.course_detail, name='detail'),
|
||||
path('create/', views.create_course, name='create'),
|
||||
path('<int:pk>/update/', views.update_course, name='update'),
|
||||
path('<int:pk>/delete/', views.delete_course, name='delete'),
|
||||
]
|
66
courses/views.py
Normal file
66
courses/views.py
Normal file
@ -0,0 +1,66 @@
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from .models import Course
|
||||
from .forms import CourseForm
|
||||
|
||||
def course_list(request):
|
||||
courses = Course.objects.all()
|
||||
return render(request, 'courses/course_list.html', {'courses': courses})
|
||||
|
||||
def course_detail(request, pk):
|
||||
course = get_object_or_404(Course, pk=pk)
|
||||
return render(request, 'courses/course_detail.html', {'course': course})
|
||||
|
||||
@login_required
|
||||
def create_course(request):
|
||||
if not hasattr(request.user, 'instructor'):
|
||||
messages.error(request, 'У вас нет прав для создания курсов.')
|
||||
return redirect('courses:list')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = CourseForm(request.POST)
|
||||
if form.is_valid():
|
||||
course = form.save(commit=False)
|
||||
course.instructor = request.user
|
||||
course.save()
|
||||
messages.success(request, 'Курс успешно создан.')
|
||||
return redirect('courses:list')
|
||||
else:
|
||||
form = CourseForm()
|
||||
|
||||
return render(request, 'courses/course_form.html', {'form': form, 'action': 'create'})
|
||||
|
||||
@login_required
|
||||
def update_course(request, pk):
|
||||
course = get_object_or_404(Course, pk=pk)
|
||||
|
||||
if not hasattr(request.user, 'instructor') or course.instructor != request.user:
|
||||
messages.error(request, 'У вас нет прав для редактирования этого курса.')
|
||||
return redirect('courses:list')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = CourseForm(request.POST, instance=course)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Курс успешно обновлен.')
|
||||
return redirect('courses:list')
|
||||
else:
|
||||
form = CourseForm(instance=course)
|
||||
|
||||
return render(request, 'courses/course_form.html', {'form': form, 'action': 'update'})
|
||||
|
||||
@login_required
|
||||
def delete_course(request, pk):
|
||||
course = get_object_or_404(Course, pk=pk)
|
||||
|
||||
if not hasattr(request.user, 'instructor') or course.instructor != request.user:
|
||||
messages.error(request, 'У вас нет прав для удаления этого курса.')
|
||||
return redirect('courses:list')
|
||||
|
||||
if request.method == 'POST':
|
||||
course.delete()
|
||||
messages.success(request, 'Курс успешно удален.')
|
||||
return redirect('courses:list')
|
||||
|
||||
return render(request, 'courses/course_confirm_delete.html', {'course': course})
|
BIN
db.sqlite3
Normal file
BIN
db.sqlite3
Normal file
Binary file not shown.
0
driving_school/__init__.py
Normal file
0
driving_school/__init__.py
Normal file
BIN
driving_school/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
driving_school/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
driving_school/__pycache__/settings.cpython-313.pyc
Normal file
BIN
driving_school/__pycache__/settings.cpython-313.pyc
Normal file
Binary file not shown.
BIN
driving_school/__pycache__/urls.cpython-313.pyc
Normal file
BIN
driving_school/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
driving_school/__pycache__/wsgi.cpython-313.pyc
Normal file
BIN
driving_school/__pycache__/wsgi.cpython-313.pyc
Normal file
Binary file not shown.
16
driving_school/asgi.py
Normal file
16
driving_school/asgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for driving_school project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'driving_school.settings')
|
||||
|
||||
application = get_asgi_application()
|
207
driving_school/settings.py
Normal file
207
driving_school/settings.py
Normal file
@ -0,0 +1,207 @@
|
||||
"""
|
||||
Django settings for driving_school project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 5.0.2.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-your-secret-key-here'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
|
||||
# Third party apps
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'crispy_forms',
|
||||
'crispy_bootstrap5',
|
||||
'widget_tweaks',
|
||||
'pwa',
|
||||
|
||||
# Local apps
|
||||
'course',
|
||||
'accounts',
|
||||
'schedule',
|
||||
'instructor',
|
||||
'student',
|
||||
'analytics',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'allauth.account.middleware.AccountMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'driving_school.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'driving_school.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR / 'db.sqlite3',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'ru-ru'
|
||||
|
||||
TIME_ZONE = 'Europe/Moscow'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
|
||||
|
||||
MEDIA_URL = 'media/'
|
||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
# Authentication settings
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'allauth.account.auth_backends.AuthenticationBackend',
|
||||
]
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
LOGIN_REDIRECT_URL = 'accounts:home'
|
||||
LOGOUT_REDIRECT_URL = 'accounts:home'
|
||||
|
||||
# Crispy Forms
|
||||
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
||||
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||
|
||||
# PWA settings
|
||||
PWA_APP_NAME = 'Автошкола'
|
||||
PWA_APP_DESCRIPTION = "Система управления автошколой"
|
||||
PWA_APP_THEME_COLOR = '#000000'
|
||||
PWA_APP_BACKGROUND_COLOR = '#ffffff'
|
||||
PWA_APP_DISPLAY = 'standalone'
|
||||
PWA_APP_SCOPE = '/'
|
||||
PWA_APP_ORIENTATION = 'any'
|
||||
PWA_APP_START_URL = '/'
|
||||
PWA_APP_STATUS_BAR_COLOR = 'default'
|
||||
PWA_APP_ICONS = [
|
||||
{
|
||||
'src': '/static/images/icons/icon-72x72.png',
|
||||
'sizes': '72x72'
|
||||
},
|
||||
{
|
||||
'src': '/static/images/icons/icon-96x96.png',
|
||||
'sizes': '96x96'
|
||||
},
|
||||
{
|
||||
'src': '/static/images/icons/icon-128x128.png',
|
||||
'sizes': '128x128'
|
||||
},
|
||||
{
|
||||
'src': '/static/images/icons/icon-144x144.png',
|
||||
'sizes': '144x144'
|
||||
},
|
||||
{
|
||||
'src': '/static/images/icons/icon-152x152.png',
|
||||
'sizes': '152x152'
|
||||
},
|
||||
{
|
||||
'src': '/static/images/icons/icon-192x192.png',
|
||||
'sizes': '192x192'
|
||||
},
|
||||
{
|
||||
'src': '/static/images/icons/icon-384x384.png',
|
||||
'sizes': '384x384'
|
||||
},
|
||||
{
|
||||
'src': '/static/images/icons/icon-512x512.png',
|
||||
'sizes': '512x512'
|
||||
}
|
||||
]
|
28
driving_school/urls.py
Normal file
28
driving_school/urls.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""
|
||||
URL configuration for driving_school project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
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.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('', include('accounts.urls', namespace='accounts')),
|
||||
path('courses/', include('course.urls', namespace='course')),
|
||||
path('schedule/', include('schedule.urls', namespace='schedule')),
|
||||
path('instructor/', include('instructor.urls', namespace='instructor')),
|
||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
16
driving_school/wsgi.py
Normal file
16
driving_school/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for driving_school project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'driving_school.settings')
|
||||
|
||||
application = get_wsgi_application()
|
BIN
instructor/__pycache__/admin.cpython-313.pyc
Normal file
BIN
instructor/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
instructor/__pycache__/forms.cpython-313.pyc
Normal file
BIN
instructor/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
instructor/__pycache__/models.cpython-313.pyc
Normal file
BIN
instructor/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
instructor/__pycache__/urls.cpython-313.pyc
Normal file
BIN
instructor/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
instructor/__pycache__/views.cpython-313.pyc
Normal file
BIN
instructor/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
15
instructor/admin.py
Normal file
15
instructor/admin.py
Normal file
@ -0,0 +1,15 @@
|
||||
from django.contrib import admin
|
||||
from .models import Instructor, InstructorSchedule
|
||||
|
||||
@admin.register(Instructor)
|
||||
class InstructorAdmin(admin.ModelAdmin):
|
||||
list_display = ('profile', 'experience_years', 'specialization', 'rating', 'is_available', 'created_at', 'updated_at')
|
||||
list_filter = ('is_available', 'created_at', 'updated_at')
|
||||
search_fields = ('profile__user__username', 'profile__user__email', 'specialization')
|
||||
date_hierarchy = 'created_at'
|
||||
|
||||
@admin.register(InstructorSchedule)
|
||||
class InstructorScheduleAdmin(admin.ModelAdmin):
|
||||
list_display = ('instructor', 'day_of_week', 'start_time', 'end_time', 'is_available')
|
||||
list_filter = ('day_of_week', 'is_available')
|
||||
search_fields = ('instructor__profile__user__username', 'instructor__profile__user__email')
|
26
instructor/forms.py
Normal file
26
instructor/forms.py
Normal file
@ -0,0 +1,26 @@
|
||||
from django import forms
|
||||
from .models import Instructor, InstructorSchedule
|
||||
|
||||
class InstructorScheduleForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = InstructorSchedule
|
||||
fields = ['day_of_week', 'start_time', 'end_time', 'is_available']
|
||||
widgets = {
|
||||
'start_time': forms.TimeInput(attrs={'type': 'time'}),
|
||||
'end_time': forms.TimeInput(attrs={'type': 'time'}),
|
||||
}
|
||||
|
||||
class InstructorProfileForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Instructor
|
||||
fields = ['experience_years', 'specialization', 'is_available']
|
||||
|
||||
class InstructorForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Instructor
|
||||
fields = ['experience_years', 'specialization', 'is_available']
|
||||
widgets = {
|
||||
'experience_years': forms.NumberInput(attrs={'class': 'form-control'}),
|
||||
'specialization': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'is_available': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
}
|
43
instructor/migrations/0001_initial.py
Normal file
43
instructor/migrations/0001_initial.py
Normal file
@ -0,0 +1,43 @@
|
||||
# Generated by Django 5.0.2 on 2025-06-10 19:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Instructor',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('experience_years', models.IntegerField(default=0)),
|
||||
('specialization', models.CharField(max_length=100)),
|
||||
('rating', models.DecimalField(decimal_places=2, default=5.0, max_digits=3)),
|
||||
('is_available', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('profile', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='accounts.profile')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='InstructorSchedule',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('day_of_week', models.IntegerField(choices=[(0, 'Понедельник'), (1, 'Вторник'), (2, 'Среда'), (3, 'Четверг'), (4, 'Пятница'), (5, 'Суббота'), (6, 'Воскресенье')])),
|
||||
('start_time', models.TimeField()),
|
||||
('end_time', models.TimeField()),
|
||||
('is_available', models.BooleanField(default=True)),
|
||||
('instructor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='instructor.instructor')),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('instructor', 'day_of_week')},
|
||||
},
|
||||
),
|
||||
]
|
0
instructor/migrations/__init__.py
Normal file
0
instructor/migrations/__init__.py
Normal file
BIN
instructor/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
BIN
instructor/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
Binary file not shown.
BIN
instructor/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
instructor/migrations/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
36
instructor/models.py
Normal file
36
instructor/models.py
Normal file
@ -0,0 +1,36 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from accounts.models import Profile
|
||||
|
||||
class Instructor(models.Model):
|
||||
profile = models.OneToOneField(Profile, on_delete=models.CASCADE)
|
||||
experience_years = models.IntegerField(default=0)
|
||||
specialization = models.CharField(max_length=100)
|
||||
rating = models.DecimalField(max_digits=3, decimal_places=2, default=5.00)
|
||||
is_available = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.profile.user.get_full_name()} - {self.specialization}"
|
||||
|
||||
class InstructorSchedule(models.Model):
|
||||
instructor = models.ForeignKey(Instructor, on_delete=models.CASCADE)
|
||||
day_of_week = models.IntegerField(choices=[
|
||||
(0, 'Понедельник'),
|
||||
(1, 'Вторник'),
|
||||
(2, 'Среда'),
|
||||
(3, 'Четверг'),
|
||||
(4, 'Пятница'),
|
||||
(5, 'Суббота'),
|
||||
(6, 'Воскресенье'),
|
||||
])
|
||||
start_time = models.TimeField()
|
||||
end_time = models.TimeField()
|
||||
is_available = models.BooleanField(default=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('instructor', 'day_of_week')
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.instructor} - {self.get_day_of_week_display()} ({self.start_time}-{self.end_time})"
|
12
instructor/urls.py
Normal file
12
instructor/urls.py
Normal file
@ -0,0 +1,12 @@
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'instructor'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.instructor_list, name='list'),
|
||||
path('<int:pk>/', views.instructor_detail, name='detail'),
|
||||
path('<int:pk>/schedule/', views.instructor_schedule, name='schedule'),
|
||||
path('profile/', views.instructor_profile, name='profile'),
|
||||
|
||||
]
|
87
instructor/views.py
Normal file
87
instructor/views.py
Normal file
@ -0,0 +1,87 @@
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from .models import Instructor, InstructorSchedule
|
||||
from schedule.models import Lesson, LessonEnrollment
|
||||
from .forms import InstructorScheduleForm, InstructorProfileForm, InstructorForm
|
||||
from student.models import Student
|
||||
|
||||
@login_required
|
||||
def instructor_dashboard(request):
|
||||
instructor = get_object_or_404(Instructor, profile__user=request.user)
|
||||
lessons = Lesson.objects.filter(instructor=instructor).order_by('start_time')
|
||||
return render(request, 'instructor/dashboard.html', {
|
||||
'instructor': instructor,
|
||||
'lessons': lessons
|
||||
})
|
||||
|
||||
@login_required
|
||||
def instructor_schedule(request, pk):
|
||||
instructor = get_object_or_404(Instructor, pk=pk)
|
||||
lessons = instructor.lessons.all().order_by('date', 'start_time')
|
||||
return render(request, 'instructor/instructor_schedule.html', {'instructor': instructor, 'lessons': lessons})
|
||||
|
||||
@login_required
|
||||
def edit_schedule(request):
|
||||
instructor = get_object_or_404(Instructor, profile__user=request.user)
|
||||
if request.method == 'POST':
|
||||
form = InstructorScheduleForm(request.POST)
|
||||
if form.is_valid():
|
||||
schedule = form.save(commit=False)
|
||||
schedule.instructor = instructor
|
||||
schedule.save()
|
||||
messages.success(request, 'Расписание успешно обновлено!')
|
||||
return redirect('instructor_schedule')
|
||||
else:
|
||||
form = InstructorScheduleForm()
|
||||
return render(request, 'instructor/edit_schedule.html', {'form': form})
|
||||
|
||||
@login_required
|
||||
def student_list(request):
|
||||
instructor = get_object_or_404(Instructor, profile__user=request.user)
|
||||
enrollments = LessonEnrollment.objects.filter(lesson__instructor=instructor).select_related('student')
|
||||
students = {enrollment.student for enrollment in enrollments}
|
||||
return render(request, 'instructor/student_list.html', {
|
||||
'instructor': instructor,
|
||||
'students': students
|
||||
})
|
||||
|
||||
@login_required
|
||||
def student_detail(request, pk):
|
||||
instructor = get_object_or_404(Instructor, profile__user=request.user)
|
||||
student = get_object_or_404(Student, pk=pk)
|
||||
enrollments = LessonEnrollment.objects.filter(
|
||||
lesson__instructor=instructor,
|
||||
student=student
|
||||
).select_related('lesson')
|
||||
return render(request, 'instructor/student_detail.html', {
|
||||
'instructor': instructor,
|
||||
'student': student,
|
||||
'enrollments': enrollments
|
||||
})
|
||||
|
||||
@login_required
|
||||
def instructor_profile(request):
|
||||
if not hasattr(request.user, 'instructor'):
|
||||
messages.error(request, 'У вас нет прав для просмотра профиля инструктора.')
|
||||
return redirect('home')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = InstructorForm(request.POST, instance=request.user.instructor)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Профиль успешно обновлен.')
|
||||
return redirect('instructor:profile')
|
||||
else:
|
||||
form = InstructorForm(instance=request.user.instructor)
|
||||
|
||||
return render(request, 'instructor/instructor_profile.html', {'form': form})
|
||||
|
||||
def instructor_list(request):
|
||||
instructors = Instructor.objects.all()
|
||||
return render(request, 'instructor/instructor_list.html', {'instructors': instructors})
|
||||
|
||||
@login_required
|
||||
def instructor_detail(request, pk):
|
||||
instructor = get_object_or_404(Instructor, pk=pk)
|
||||
return render(request, 'instructor/instructor_detail.html', {'instructor': instructor})
|
22
manage.py
Normal file
22
manage.py
Normal file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'driving_school.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@ -0,0 +1,7 @@
|
||||
Django>=5.0,<6.0
|
||||
django-allauth
|
||||
django-crispy-forms
|
||||
crispy-bootstrap5
|
||||
django-widget-tweaks
|
||||
django-pwa
|
||||
gunicorn
|
BIN
schedule/__pycache__/admin.cpython-313.pyc
Normal file
BIN
schedule/__pycache__/admin.cpython-313.pyc
Normal file
Binary file not shown.
BIN
schedule/__pycache__/forms.cpython-313.pyc
Normal file
BIN
schedule/__pycache__/forms.cpython-313.pyc
Normal file
Binary file not shown.
BIN
schedule/__pycache__/models.cpython-313.pyc
Normal file
BIN
schedule/__pycache__/models.cpython-313.pyc
Normal file
Binary file not shown.
BIN
schedule/__pycache__/urls.cpython-313.pyc
Normal file
BIN
schedule/__pycache__/urls.cpython-313.pyc
Normal file
Binary file not shown.
BIN
schedule/__pycache__/views.cpython-313.pyc
Normal file
BIN
schedule/__pycache__/views.cpython-313.pyc
Normal file
Binary file not shown.
15
schedule/admin.py
Normal file
15
schedule/admin.py
Normal file
@ -0,0 +1,15 @@
|
||||
from django.contrib import admin
|
||||
from .models import Lesson, LessonEnrollment
|
||||
|
||||
@admin.register(Lesson)
|
||||
class LessonAdmin(admin.ModelAdmin):
|
||||
list_display = ('course', 'instructor', 'date', 'start_time', 'end_time', 'location', 'max_students', 'created_at', 'updated_at')
|
||||
list_filter = ('date', 'created_at', 'updated_at')
|
||||
search_fields = ('course__title', 'instructor__profile__user__username', 'location')
|
||||
date_hierarchy = 'date'
|
||||
|
||||
@admin.register(LessonEnrollment)
|
||||
class LessonEnrollmentAdmin(admin.ModelAdmin):
|
||||
list_display = ('lesson', 'student', 'created_at')
|
||||
list_filter = ('created_at',)
|
||||
search_fields = ('lesson__course__title', 'student__profile__user__username')
|
29
schedule/forms.py
Normal file
29
schedule/forms.py
Normal file
@ -0,0 +1,29 @@
|
||||
from django import forms
|
||||
from .models import Lesson, LessonEnrollment
|
||||
|
||||
class LessonForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Lesson
|
||||
fields = ['course', 'date', 'start_time', 'end_time', 'location', 'max_students']
|
||||
widgets = {
|
||||
'date': forms.DateInput(attrs={'type': 'date'}),
|
||||
'start_time': forms.TimeInput(attrs={'type': 'time'}),
|
||||
'end_time': forms.TimeInput(attrs={'type': 'time'}),
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
start_time = cleaned_data.get('start_time')
|
||||
end_time = cleaned_data.get('end_time')
|
||||
|
||||
if start_time and end_time:
|
||||
# Проверяем, что время окончания больше времени начала
|
||||
if start_time.time() >= end_time:
|
||||
raise forms.ValidationError('Время окончания должно быть позже времени начала')
|
||||
|
||||
return cleaned_data
|
||||
|
||||
class EnrollmentForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = LessonEnrollment
|
||||
fields = ['lesson', 'student']
|
60
schedule/migrations/0001_initial.py
Normal file
60
schedule/migrations/0001_initial.py
Normal file
@ -0,0 +1,60 @@
|
||||
# Generated by Django 5.0.2 on 2025-06-10 19:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('course', '0001_initial'),
|
||||
('instructor', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Course',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('description', models.TextField()),
|
||||
('duration', models.IntegerField(help_text='Длительность в часах')),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Lesson',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('lesson_type', models.CharField(choices=[('theory', 'Теоретическое'), ('practice', 'Практическое'), ('exam', 'Экзамен')], max_length=20)),
|
||||
('start_time', models.DateTimeField()),
|
||||
('end_time', models.TimeField()),
|
||||
('max_students', models.PositiveIntegerField(default=10)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='course.course')),
|
||||
('instructor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='instructor.instructor')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Занятие',
|
||||
'verbose_name_plural': 'Занятия',
|
||||
'ordering': ['start_time'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='LessonEnrollment',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='enrollments', to='schedule.lesson')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Запись на занятие',
|
||||
'verbose_name_plural': 'Записи на занятия',
|
||||
},
|
||||
),
|
||||
]
|
26
schedule/migrations/0002_initial.py
Normal file
26
schedule/migrations/0002_initial.py
Normal file
@ -0,0 +1,26 @@
|
||||
# Generated by Django 5.0.2 on 2025-06-10 19:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('schedule', '0001_initial'),
|
||||
('student', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='lessonenrollment',
|
||||
name='student',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='enrollments', to='student.student'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='lessonenrollment',
|
||||
unique_together={('lesson', 'student')},
|
||||
),
|
||||
]
|
@ -0,0 +1,55 @@
|
||||
# Generated by Django 5.0.2 on 2025-06-10 20:05
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('schedule', '0002_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='lesson',
|
||||
options={'ordering': ['date', 'start_time'], 'verbose_name': 'Занятие', 'verbose_name_plural': 'Занятия'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='lesson',
|
||||
name='lesson_type',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lesson',
|
||||
name='date',
|
||||
field=models.DateField(default=django.utils.timezone.now),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lesson',
|
||||
name='enrolled_students',
|
||||
field=models.ManyToManyField(blank=True, related_name='enrolled_lessons', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='lesson',
|
||||
name='location',
|
||||
field=models.CharField(default='Учебный класс', max_length=200),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lesson',
|
||||
name='course',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='schedule.course'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lesson',
|
||||
name='instructor',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lesson',
|
||||
name='start_time',
|
||||
field=models.TimeField(),
|
||||
),
|
||||
]
|
@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.0.2 on 2025-06-10 20:23
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('instructor', '0001_initial'),
|
||||
('schedule', '0003_alter_lesson_options_remove_lesson_lesson_type_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='lesson',
|
||||
name='enrolled_students',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='lesson',
|
||||
name='instructor',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='instructor.instructor'),
|
||||
),
|
||||
]
|
@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.0.2 on 2025-06-10 20:24
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('analytics', '0002_alter_courseanalytics_course'),
|
||||
('course', '0001_initial'),
|
||||
('schedule', '0004_remove_lesson_enrolled_students_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='lesson',
|
||||
name='course',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='course.course'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Course',
|
||||
),
|
||||
]
|
0
schedule/migrations/__init__.py
Normal file
0
schedule/migrations/__init__.py
Normal file
BIN
schedule/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
BIN
schedule/migrations/__pycache__/0001_initial.cpython-313.pyc
Normal file
Binary file not shown.
BIN
schedule/migrations/__pycache__/0002_initial.cpython-313.pyc
Normal file
BIN
schedule/migrations/__pycache__/0002_initial.cpython-313.pyc
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user