diff --git a/myproject/orders/forms.py b/myproject/orders/forms.py
index 890a706..2e73257 100644
--- a/myproject/orders/forms.py
+++ b/myproject/orders/forms.py
@@ -146,13 +146,13 @@ class OrderForm(forms.ModelForm):
)
time_from = forms.TimeField(
- required=True,
+ required=False,
widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
label='Время доставки от'
)
time_to = forms.TimeField(
- required=True,
+ required=False,
widget=forms.TimeInput(attrs={'class': 'form-control', 'type': 'time'}),
label='Время доставки до'
)
@@ -321,13 +321,13 @@ class OrderForm(forms.ModelForm):
if not delivery_date:
raise forms.ValidationError({'delivery_date': 'Необходимо указать дату доставки'})
- if not time_from:
- raise forms.ValidationError({'time_from': 'Необходимо указать время начала доставки'})
+ # Время необязательно, но если указано одно, должно быть указано и другое
+ if (time_from and not time_to) or (time_to and not time_from):
+ raise forms.ValidationError({
+ 'time_to': 'Если указано время начала, необходимо указать и время окончания, и наоборот'
+ })
- if not time_to:
- raise forms.ValidationError({'time_to': 'Необходимо указать время окончания доставки'})
-
- # Проверяем, что время "до" позже времени "от"
+ # Проверяем, что время "до" позже времени "от" (если оба указаны)
if time_from and time_to and time_from >= time_to:
raise forms.ValidationError({
'time_to': 'Время окончания доставки должно быть позже времени начала'
diff --git a/myproject/orders/migrations/0004_make_delivery_time_optional.py b/myproject/orders/migrations/0004_make_delivery_time_optional.py
new file mode 100644
index 0000000..c79a8be
--- /dev/null
+++ b/myproject/orders/migrations/0004_make_delivery_time_optional.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.0.10 on 2025-12-24 15:18
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('orders', '0003_remove_customer_is_recipient'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='delivery',
+ name='time_from',
+ field=models.TimeField(blank=True, help_text='Начальное время временного интервала доставки (необязательно)', null=True, verbose_name='Время доставки от'),
+ ),
+ migrations.AlterField(
+ model_name='delivery',
+ name='time_to',
+ field=models.TimeField(blank=True, help_text='Конечное время временного интервала доставки (необязательно)', null=True, verbose_name='Время доставки до'),
+ ),
+ ]
diff --git a/myproject/orders/models/delivery.py b/myproject/orders/models/delivery.py
index 2ca0714..c8a5454 100644
--- a/myproject/orders/models/delivery.py
+++ b/myproject/orders/models/delivery.py
@@ -67,13 +67,17 @@ class Delivery(models.Model):
)
time_from = models.TimeField(
+ null=True,
+ blank=True,
verbose_name='Время доставки от',
- help_text='Начальное время временного интервала доставки'
+ help_text='Начальное время временного интервала доставки (необязательно)'
)
time_to = models.TimeField(
+ null=True,
+ blank=True,
verbose_name='Время доставки до',
- help_text='Конечное время временного интервала доставки'
+ help_text='Конечное время временного интервала доставки (необязательно)'
)
cost = models.DecimalField(
diff --git a/myproject/orders/static/orders/css/delivery_datetime.css b/myproject/orders/static/orders/css/delivery_datetime.css
new file mode 100644
index 0000000..53adb69
--- /dev/null
+++ b/myproject/orders/static/orders/css/delivery_datetime.css
@@ -0,0 +1,161 @@
+/* Стили для улучшенного интерфейса даты и времени доставки */
+
+.delivery-datetime-wrapper {
+ position: relative;
+}
+
+/* Контейнер для всех кнопок быстрого выбора */
+#delivery-datetime-quick-buttons {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 1rem;
+ margin-top: 0.5rem;
+}
+
+/* Быстрые кнопки для даты */
+.delivery-date-quick-buttons {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+}
+
+.delivery-date-quick-btn {
+ padding: 0.375rem 0.75rem;
+ font-size: 0.875rem;
+ border: 1px solid #dee2e6;
+ background-color: #fff;
+ color: #495057;
+ border-radius: 0.375rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ white-space: nowrap;
+}
+
+.delivery-date-quick-btn:hover {
+ background-color: #f8f9fa;
+ border-color: #0d6efd;
+ color: #0d6efd;
+}
+
+.delivery-date-quick-btn.active {
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+ color: #fff;
+}
+
+.delivery-date-quick-btn i {
+ margin-right: 0.25rem;
+}
+
+/* Предустановленные интервалы времени */
+.delivery-time-presets {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+}
+
+/* Дополнительный отступ для группы времени */
+.delivery-time-presets {
+ margin-left: 0.5rem;
+ padding-left: 0.5rem;
+ border-left: 1px solid #dee2e6;
+}
+
+.delivery-time-preset-btn {
+ padding: 0.5rem 0.75rem;
+ font-size: 0.875rem;
+ border: 1px solid #dee2e6;
+ background-color: #fff;
+ color: #495057;
+ border-radius: 0.375rem;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ text-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.delivery-time-preset-btn:hover {
+ background-color: #f8f9fa;
+ border-color: #0d6efd;
+ color: #0d6efd;
+}
+
+.delivery-time-preset-btn.active {
+ background-color: #0d6efd;
+ border-color: #0d6efd;
+ color: #fff;
+}
+
+.delivery-time-preset-btn i {
+ margin-right: 0.25rem;
+}
+
+/* Группа полей времени */
+.delivery-time-group {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.delivery-time-group .form-control {
+ flex: 1;
+}
+
+.delivery-time-separator {
+ color: #6c757d;
+ font-weight: 500;
+ user-select: none;
+}
+
+/* Улучшенные поля ввода */
+.delivery-datetime-wrapper .form-control {
+ position: relative;
+}
+
+.delivery-datetime-wrapper .form-control:focus {
+ border-color: #0d6efd;
+ box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
+}
+
+/* Подсказки под полями */
+.delivery-datetime-hint {
+ font-size: 0.875rem;
+ color: #6c757d;
+ margin-top: 0.25rem;
+ display: flex;
+ align-items: center;
+ gap: 0.25rem;
+}
+
+.delivery-datetime-hint i {
+ font-size: 0.75rem;
+}
+
+/* Адаптивность */
+@media (max-width: 768px) {
+ .delivery-date-quick-buttons {
+ flex-direction: column;
+ }
+
+ .delivery-date-quick-btn {
+ width: 100%;
+ text-align: center;
+ }
+
+ .delivery-time-presets {
+ grid-template-columns: 1fr;
+ }
+
+ .delivery-time-group {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .delivery-time-separator {
+ display: none;
+ }
+}
+
diff --git a/myproject/orders/static/orders/js/delivery_datetime.js b/myproject/orders/static/orders/js/delivery_datetime.js
new file mode 100644
index 0000000..5614ac8
--- /dev/null
+++ b/myproject/orders/static/orders/js/delivery_datetime.js
@@ -0,0 +1,203 @@
+/**
+ * Улучшенный интерфейс для ввода даты и времени доставки
+ * Добавляет быстрые кнопки для даты и предустановленные интервалы времени
+ */
+
+(function() {
+ 'use strict';
+
+ /**
+ * Инициализация улучшенного интерфейса даты и времени
+ * @param {string} dateFieldId - ID поля даты
+ * @param {string} timeFromFieldId - ID поля времени "от"
+ * @param {string} timeToFieldId - ID поля времени "до"
+ */
+ function initDeliveryDateTime(dateFieldId, timeFromFieldId, timeToFieldId) {
+ const dateField = document.getElementById(dateFieldId);
+ const timeFromField = document.getElementById(timeFromFieldId);
+ const timeToField = document.getElementById(timeToFieldId);
+
+ if (!dateField || !timeFromField || !timeToField) {
+ console.warn('[Delivery DateTime] Поля не найдены');
+ return;
+ }
+
+ // Находим общий контейнер для кнопок
+ const buttonsContainer = document.getElementById('delivery-datetime-quick-buttons');
+ if (!buttonsContainer) {
+ console.warn('[Delivery DateTime] Контейнер для кнопок не найден');
+ return;
+ }
+
+ // Добавляем быстрые кнопки для даты
+ addDateQuickButtons(buttonsContainer, dateField);
+
+ // Добавляем предустановленные интервалы времени
+ addTimePresets(buttonsContainer, timeFromField, timeToField);
+ }
+
+ /**
+ * Добавляет быстрые кнопки для выбора даты
+ */
+ function addDateQuickButtons(container, dateField) {
+ const quickButtonsContainer = document.createElement('div');
+ quickButtonsContainer.className = 'delivery-date-quick-buttons';
+
+ const buttons = [
+ { label: 'Сегодня', days: 0, icon: 'bi-calendar-day' },
+ { label: 'Завтра', days: 1, icon: 'bi-calendar-check' }
+ ];
+
+ buttons.forEach(button => {
+ const btn = document.createElement('button');
+ btn.type = 'button';
+ btn.className = 'delivery-date-quick-btn';
+ btn.innerHTML = `${button.label}`;
+
+ btn.addEventListener('click', function() {
+ const date = new Date();
+ date.setDate(date.getDate() + button.days);
+ const dateString = formatDateForInput(date);
+ dateField.value = dateString;
+
+ // Обновляем активное состояние кнопок
+ quickButtonsContainer.querySelectorAll('.delivery-date-quick-btn').forEach(b => {
+ b.classList.remove('active');
+ });
+ btn.classList.add('active');
+
+ // Триггерим событие change для валидации
+ dateField.dispatchEvent(new Event('change', { bubbles: true }));
+ });
+
+ quickButtonsContainer.appendChild(btn);
+ });
+
+ // Проверяем текущую дату и активируем соответствующую кнопку
+ if (dateField.value) {
+ const currentDate = new Date(dateField.value);
+ const today = new Date();
+ today.setHours(0, 0, 0, 0);
+ currentDate.setHours(0, 0, 0, 0);
+
+ const diffDays = Math.round((currentDate - today) / (1000 * 60 * 60 * 24));
+
+ if (diffDays === 0) {
+ quickButtonsContainer.children[0].classList.add('active');
+ } else if (diffDays === 1) {
+ quickButtonsContainer.children[1].classList.add('active');
+ }
+ }
+
+ // Слушаем изменения даты вручную
+ dateField.addEventListener('change', function() {
+ quickButtonsContainer.querySelectorAll('.delivery-date-quick-btn').forEach(b => {
+ b.classList.remove('active');
+ });
+ });
+
+ container.appendChild(quickButtonsContainer);
+ }
+
+ /**
+ * Добавляет предустановленные интервалы времени
+ */
+ function addTimePresets(container, timeFromField, timeToField) {
+ // Создаем контейнер для пресетов
+ const presetsContainer = document.createElement('div');
+ presetsContainer.className = 'delivery-time-presets';
+
+ const presets = [
+ { label: 'Утро', from: '09:00', to: '12:00', icon: 'bi-sunrise' },
+ { label: 'День', from: '12:00', to: '15:00', icon: 'bi-sun' },
+ { label: 'Вечер', from: '15:00', to: '18:00', icon: 'bi-sunset' },
+ { label: 'Поздно', from: '18:00', to: '21:00', icon: 'bi-moon' }
+ ];
+
+ presets.forEach(preset => {
+ const btn = document.createElement('button');
+ btn.type = 'button';
+ btn.className = 'delivery-time-preset-btn';
+ btn.innerHTML = `${preset.label}`;
+
+ btn.addEventListener('click', function() {
+ timeFromField.value = preset.from;
+ timeToField.value = preset.to;
+
+ // Обновляем активное состояние кнопок
+ presetsContainer.querySelectorAll('.delivery-time-preset-btn').forEach(b => {
+ b.classList.remove('active');
+ });
+ btn.classList.add('active');
+
+ // Триггерим события change для валидации
+ timeFromField.dispatchEvent(new Event('change', { bubbles: true }));
+ timeToField.dispatchEvent(new Event('change', { bubbles: true }));
+ });
+
+ presetsContainer.appendChild(btn);
+ });
+
+ // Проверяем текущие значения и активируем соответствующий пресет
+ if (timeFromField.value && timeToField.value) {
+ presets.forEach((preset, index) => {
+ if (timeFromField.value === preset.from && timeToField.value === preset.to) {
+ presetsContainer.children[index].classList.add('active');
+ }
+ });
+ }
+
+ // Слушаем изменения времени вручную
+ const clearPresets = function() {
+ presetsContainer.querySelectorAll('.delivery-time-preset-btn').forEach(b => {
+ b.classList.remove('active');
+ });
+ };
+ timeFromField.addEventListener('change', clearPresets);
+ timeToField.addEventListener('change', clearPresets);
+
+ // Вставляем пресеты в контейнер
+ container.appendChild(presetsContainer);
+ }
+
+ /**
+ * Форматирует дату для input type="date" (YYYY-MM-DD)
+ */
+ function formatDateForInput(date) {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, '0');
+ const day = String(date.getDate()).padStart(2, '0');
+ return `${year}-${month}-${day}`;
+ }
+
+ /**
+ * Автоматическая инициализация при загрузке DOM
+ */
+ function autoInit() {
+ document.addEventListener('DOMContentLoaded', function() {
+ // Ищем поля по стандартным ID Django форм
+ const dateField = document.getElementById('id_delivery_date');
+ const timeFromField = document.getElementById('id_time_from');
+ const timeToField = document.getElementById('id_time_to');
+
+ if (dateField && timeFromField && timeToField) {
+ initDeliveryDateTime(
+ 'id_delivery_date',
+ 'id_time_from',
+ 'id_time_to'
+ );
+ }
+ });
+ }
+
+ // Экспортируем функции для глобального использования
+ window.DeliveryDateTime = {
+ init: initDeliveryDateTime,
+ autoInit: autoInit
+ };
+
+ // Автоматическая инициализация
+ autoInit();
+
+})();
+
diff --git a/myproject/orders/templates/orders/order_form.html b/myproject/orders/templates/orders/order_form.html
index 9f93ec0..8af2c28 100644
--- a/myproject/orders/templates/orders/order_form.html
+++ b/myproject/orders/templates/orders/order_form.html
@@ -6,6 +6,7 @@
{% block extra_css %}
+