Рефакторинг: отделение 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:
151
myproject/orders/models/delivery.py
Normal file
151
myproject/orders/models/delivery.py
Normal 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)
|
||||
Reference in New Issue
Block a user