Рефакторинг: отделение Delivery от Order, обязательные поля доставки, исправление доменов

- Отделена модель Delivery от Order (OneToOne связь)
- Добавлены обязательные поля delivery_date, time_from, time_to в Delivery
- Delivery обязательна при создании заказа (кроме черновиков)
- Добавлены методы calculate_total() и reset_delivery_cost() в Order
- Добавлена валидация полей доставки в OrderForm
- Исправлено создание доменов - убран порт из домена в БД
- Исправлен редирект после установки пароля (правильный формат URL)
- Исправлена ошибка NoReverseMatch в navbar для public схемы
- Удалены все старые миграции (база создается с нуля)
- Обновлены views для работы с новой моделью Delivery
This commit is contained in:
2025-12-23 23:52:59 +03:00
parent d29c736252
commit 94fe363cb1
61 changed files with 1342 additions and 2189 deletions

View File

@@ -0,0 +1,151 @@
# -*- coding: utf-8 -*-
from django.db import models
from django.core.exceptions import ValidationError
class Delivery(models.Model):
"""
Модель доставки заказа.
Один заказ имеет одну доставку.
"""
# Константы для типов доставки
DELIVERY_TYPE_COURIER = 'courier'
DELIVERY_TYPE_PICKUP = 'pickup'
DELIVERY_TYPE_CHOICES = [
(DELIVERY_TYPE_COURIER, 'Доставка курьером'),
(DELIVERY_TYPE_PICKUP, 'Самовывоз'),
]
# === Связи ===
order = models.OneToOneField(
'orders.Order',
on_delete=models.CASCADE,
related_name='delivery',
verbose_name='Заказ',
help_text='Заказ, к которому относится доставка'
)
# Адрес доставки (только для курьерской доставки)
address = models.ForeignKey(
'orders.Address',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='deliveries',
verbose_name='Адрес доставки',
help_text='Адрес для курьерской доставки. На один адрес может быть много доставок'
)
# Склад для самовывоза (только для самовывоза)
pickup_warehouse = models.ForeignKey(
'inventory.Warehouse',
on_delete=models.PROTECT,
null=True,
blank=True,
related_name='deliveries',
verbose_name='Склад самовывоза',
help_text='Склад для самовывоза заказа'
)
# === Основные поля ===
delivery_type = models.CharField(
max_length=20,
choices=DELIVERY_TYPE_CHOICES,
default=DELIVERY_TYPE_COURIER,
verbose_name='Способ доставки',
db_index=True
)
# Дата и время доставки
delivery_date = models.DateField(
verbose_name='Дата доставки',
help_text='Дата, когда должна быть выполнена доставка'
)
time_from = models.TimeField(
verbose_name='Время доставки от',
help_text='Начальное время временного интервала доставки'
)
time_to = models.TimeField(
verbose_name='Время доставки до',
help_text='Конечное время временного интервала доставки'
)
cost = models.DecimalField(
max_digits=10,
decimal_places=2,
default=0,
verbose_name='Стоимость доставки',
help_text='Стоимость доставки в рублях. 0 для бесплатной доставки/самовывоза'
)
# === Метаданные ===
created_at = models.DateTimeField(
auto_now_add=True,
verbose_name='Дата создания'
)
updated_at = models.DateTimeField(
auto_now=True,
verbose_name='Дата обновления'
)
class Meta:
verbose_name = 'Доставка'
verbose_name_plural = 'Доставки'
ordering = ['-created_at']
indexes = [
models.Index(fields=['delivery_type']),
models.Index(fields=['created_at']),
models.Index(fields=['delivery_date']),
models.Index(fields=['time_from']),
models.Index(fields=['time_to']),
]
def __str__(self):
"""Строковое представление доставки"""
type_display = self.get_delivery_type_display()
return f"{type_display} для заказа #{self.order.order_number}"
def clean(self):
"""Валидация модели"""
super().clean()
# Проверка: для курьерской доставки должен быть адрес
if self.delivery_type == self.DELIVERY_TYPE_COURIER:
if not self.address:
raise ValidationError({
'address': 'Для курьерской доставки необходимо указать адрес'
})
if self.pickup_warehouse:
raise ValidationError({
'pickup_warehouse': 'Для курьерской доставки склад не указывается'
})
# Проверка: для самовывоза должен быть склад
if self.delivery_type == self.DELIVERY_TYPE_PICKUP:
if not self.pickup_warehouse:
raise ValidationError({
'pickup_warehouse': 'Для самовывоза необходимо указать склад'
})
if self.address:
raise ValidationError({
'address': 'Для самовывоза адрес не указывается'
})
# Проверка: время "до" должно быть позже времени "от"
if self.time_from and self.time_to and self.time_from >= self.time_to:
raise ValidationError({
'time_to': 'Время окончания доставки должно быть позже времени начала'
})
def save(self, *args, **kwargs):
"""Переопределение save для вызова валидации"""
self.full_clean()
super().save(*args, **kwargs)