update admin panel and visual
This commit is contained in:
+120
-10
@@ -1,5 +1,67 @@
|
||||
from datetime import timedelta
|
||||
|
||||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
ACTIVE_BOOKING_STATUSES = ("new", "confirmed")
|
||||
|
||||
|
||||
class ConsultationSlotQuerySet(models.QuerySet):
|
||||
def available(self):
|
||||
return (
|
||||
self.filter(is_active=True, start_at__gte=timezone.now())
|
||||
.exclude(bookings__status__in=ACTIVE_BOOKING_STATUSES)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
|
||||
class ConsultationSlot(models.Model):
|
||||
class Subject(models.TextChoices):
|
||||
ANY = "any", "Физика или математика"
|
||||
PHYSICS = "physics", "Физика"
|
||||
MATHEMATICS = "mathematics", "Математика"
|
||||
|
||||
class Format(models.TextChoices):
|
||||
ONLINE = "online", "Онлайн"
|
||||
OFFLINE = "offline", "Очно"
|
||||
|
||||
start_at = models.DateTimeField("Начало консультации", unique=True)
|
||||
duration_minutes = models.PositiveSmallIntegerField(
|
||||
"Длительность, минут",
|
||||
choices=((45, "45 минут"), (60, "60 минут"), (90, "90 минут"), (120, "120 минут")),
|
||||
default=60,
|
||||
)
|
||||
subject = models.CharField(
|
||||
"Доступный предмет", max_length=20, choices=Subject.choices, default=Subject.ANY
|
||||
)
|
||||
format = models.CharField("Формат", max_length=20, choices=Format.choices, default=Format.ONLINE)
|
||||
is_active = models.BooleanField("Открыт для записи", default=True)
|
||||
teacher_note = models.CharField("Заметка преподавателя", max_length=300, blank=True)
|
||||
created_at = models.DateTimeField("Создан", auto_now_add=True)
|
||||
|
||||
objects = ConsultationSlotQuerySet.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Свободное время"
|
||||
verbose_name_plural = "Расписание"
|
||||
ordering = ("start_at",)
|
||||
|
||||
def __str__(self):
|
||||
local_start = timezone.localtime(self.start_at)
|
||||
return f"{local_start:%d.%m.%Y, %H:%M} · {self.get_format_display()}"
|
||||
|
||||
@property
|
||||
def end_at(self):
|
||||
return self.start_at + timedelta(minutes=self.duration_minutes)
|
||||
|
||||
@property
|
||||
def is_available(self):
|
||||
return self.is_active and self.start_at >= timezone.now() and not self.bookings.filter(
|
||||
status__in=ACTIVE_BOOKING_STATUSES
|
||||
).exists()
|
||||
|
||||
|
||||
class ConsultationRequest(models.Model):
|
||||
@@ -10,9 +72,22 @@ class ConsultationRequest(models.Model):
|
||||
class Exam(models.TextChoices):
|
||||
OGE = "oge", "ОГЭ"
|
||||
EGE = "ege", "ЕГЭ"
|
||||
SCHOOL = "school", "Школьная программа"
|
||||
OTHER = "other", "Другая консультация"
|
||||
|
||||
name = models.CharField("Имя ученика", max_length=100)
|
||||
class Format(models.TextChoices):
|
||||
ONLINE = "online", "Онлайн"
|
||||
OFFLINE = "offline", "Очно"
|
||||
ANY = "any", "Не имеет значения"
|
||||
|
||||
class Status(models.TextChoices):
|
||||
NEW = "new", "Новая"
|
||||
CONFIRMED = "confirmed", "Подтверждена"
|
||||
COMPLETED = "completed", "Проведена"
|
||||
CANCELLED = "cancelled", "Отменена"
|
||||
NO_SHOW = "no_show", "Не пришёл"
|
||||
|
||||
name = models.CharField("Имя ученика или родителя", max_length=100)
|
||||
phone = models.CharField(
|
||||
"Телефон",
|
||||
max_length=30,
|
||||
@@ -24,19 +99,54 @@ class ConsultationRequest(models.Model):
|
||||
],
|
||||
)
|
||||
contact = models.CharField("Telegram или другой способ связи", max_length=100, blank=True)
|
||||
student_grade = models.PositiveSmallIntegerField(
|
||||
"Класс ученика", choices=tuple((grade, f"{grade} класс") for grade in range(5, 12)), blank=True, null=True
|
||||
)
|
||||
subject = models.CharField("Предмет", max_length=20, choices=Subject.choices)
|
||||
exam = models.CharField("Цель подготовки", max_length=20, choices=Exam.choices)
|
||||
preferred_date = models.DateField("Желаемая дата")
|
||||
preferred_time = models.TimeField("Желаемое время")
|
||||
comment = models.TextField("Комментарий", max_length=1000, blank=True)
|
||||
format = models.CharField("Предпочтительный формат", max_length=20, choices=Format.choices, default=Format.ONLINE)
|
||||
slot = models.ForeignKey(
|
||||
ConsultationSlot,
|
||||
verbose_name="Выбранное время",
|
||||
related_name="bookings",
|
||||
on_delete=models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
preferred_date = models.DateField("Дата консультации", blank=True, null=True)
|
||||
preferred_time = models.TimeField("Время консультации", blank=True, null=True)
|
||||
comment = models.TextField("Комментарий ученика", max_length=1000, blank=True)
|
||||
status = models.CharField("Статус", max_length=20, choices=Status.choices, default=Status.NEW)
|
||||
teacher_note = models.TextField("Заметка преподавателя", max_length=2000, blank=True)
|
||||
meeting_link = models.URLField("Ссылка на встречу", blank=True)
|
||||
cancellation_reason = models.CharField("Причина отмены", max_length=500, blank=True)
|
||||
reminder_sent = models.BooleanField("Напоминание отправлено", default=False)
|
||||
consent_given = models.BooleanField("Согласие на обработку данных", default=False)
|
||||
created_at = models.DateTimeField("Создана", auto_now_add=True)
|
||||
is_processed = models.BooleanField("Заявка обработана", default=False)
|
||||
updated_at = models.DateTimeField("Изменена", auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Заявка на консультацию"
|
||||
verbose_name_plural = "Заявки на консультации"
|
||||
ordering = ("is_processed", "preferred_date", "preferred_time")
|
||||
verbose_name = "Запись на консультацию"
|
||||
verbose_name_plural = "Записи на консультации"
|
||||
ordering = ("preferred_date", "preferred_time", "-created_at")
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=("slot",),
|
||||
condition=Q(status__in=ACTIVE_BOOKING_STATUSES),
|
||||
name="unique_active_slot_booking",
|
||||
)
|
||||
]
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.slot_id:
|
||||
local_start = timezone.localtime(self.slot.start_at)
|
||||
self.preferred_date = local_start.date()
|
||||
self.preferred_time = local_start.time()
|
||||
if self.format == self.Format.ANY:
|
||||
self.format = self.slot.format
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}: {self.get_subject_display()} — {self.preferred_date:%d.%m.%Y}"
|
||||
|
||||
if self.preferred_date and self.preferred_time:
|
||||
return f"{self.name}: {self.get_subject_display()} — {self.preferred_date:%d.%m.%Y}, {self.preferred_time:%H:%M}"
|
||||
return f"{self.name}: {self.get_subject_display()}"
|
||||
|
||||
Reference in New Issue
Block a user