You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Django的披萨订购系统:如何根据所选附加项添加配料?

解决方案:为披萨订购系统添加配料选择功能

根据你的需求,我们需要分几步来实现用户选择具体配料的功能,核心是调整模型结构、构建动态表单并处理前后端的验证逻辑。

1. 调整模型结构

首先,我们需要单独创建一个Topping模型来存储所有可选的配料,然后通过订单关联表来记录用户选择的具体配料(因为一个订单中的披萨可能对应多个配料,且不同披萨的配料独立)。

创建Topping模型

from django.db import models

class Topping(models.Model):
    name = models.CharField(max_length=50, unique=True, verbose_name="配料名称")
    
    def __str__(self):
        return self.name

创建订单关联模型

如果你的系统还没有订单模型,需要先添加Order和OrderItem(用来关联订单、基础披萨和所选配料):

class Order(models.Model):
    customer_name = models.CharField(max_length=100, verbose_name="客户姓名")
    created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
    # 可扩展字段:联系电话、配送地址、订单状态等

    class Meta:
        verbose_name_plural = "订单"

    def __str__(self):
        return f"订单 #{self.pk} - {self.customer_name}"

class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items', verbose_name="所属订单")
    pizza = models.ForeignKey(Pizza, on_delete=models.CASCADE, verbose_name="基础披萨")
    toppings = models.ManyToManyField(Topping, blank=True, verbose_name="所选配料")

    class Meta:
        verbose_name_plural = "订单明细"

    def __str__(self):
        topping_names = ", ".join([t.name for t in self.toppings.all()]) if self.toppings.exists() else "无额外配料"
        return f"{self.pizza} - {topping_names}"

2. 构建动态表单

我们需要创建一个表单,根据用户选择的披萨类型(addons字段)动态限制配料的选择数量和状态:

from django import forms
from .models import Pizza, Topping, OrderItem

class OrderItemForm(forms.ModelForm):
    class Meta:
        model = OrderItem
        fields = ['pizza', 'toppings']
        widgets = {
            'toppings': forms.SelectMultiple(attrs={'class': 'form-control'}),
            'pizza': forms.Select(attrs={'class': 'form-control'}),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 初始化配料选项
        self.fields['toppings'].queryset = Topping.objects.all()

        # 编辑已有订单项时,根据披萨类型设置配料字段状态
        if self.instance.pk:
            self._set_topping_field_rules(self.instance.pizza)

    def _set_topping_field_rules(self, pizza):
        """根据披萨的addons类型设置配料字段的规则"""
        toppings_field = self.fields['toppings']
        if pizza.addons == 'Cheese':
            toppings_field.widget.attrs['disabled'] = True
            toppings_field.required = False
        elif pizza.addons == '1 Topping':
            toppings_field.widget.attrs['max'] = 1
            toppings_field.required = True
        elif pizza.addons in ['2 Toppings', '3 Toppings']:
            toppings_field.widget.attrs['max'] = int(pizza.addons[0])
            toppings_field.required = True
        elif pizza.addons == 'Special':
            toppings_field.widget.attrs['disabled'] = True
            # 如果Special是固定配料,可以自动选中预设配料
            special_toppings = Topping.objects.filter(name__in=['Pepperoni', 'Mushroom', 'Onion'])
            self.initial['toppings'] = special_toppings

    def clean(self):
        cleaned_data = super().clean()
        pizza = cleaned_data.get('pizza')
        toppings = cleaned_data.get('toppings')

        if pizza and toppings:
            # 后端验证:确保配料数量符合披萨类型要求
            if pizza.addons == 'Cheese' and toppings.exists():
                self.add_error('toppings', '该披萨类型不允许添加额外配料')
            elif pizza.addons == '1 Topping' and len(toppings) != 1:
                self.add_error('toppings', '该披萨必须选择恰好1种配料')
            elif pizza.addons == '2 Toppings' and len(toppings) != 2:
                self.add_error('toppings', '该披萨必须选择恰好2种配料')
            elif pizza.addons == '3 Toppings' and len(toppings) != 3:
                self.add_error('toppings', '该披萨必须选择恰好3种配料')
        return cleaned_data

3. 实现前端动态交互

为了提升用户体验,需要用JavaScript监听披萨选择的变化,实时调整配料字段的状态:

<!-- templates/add_order_item.html -->
<form method="post" id="order-item-form">
    {% csrf_token %}
    <div class="form-group">
        {{ form.pizza.label_tag }}
        {{ form.pizza }}
    </div>
    <div class="form-group">
        {{ form.toppings.label_tag }}
        {{ form.toppings }}
        {% if form.toppings.errors %}
            <div class="text-danger">{{ form.toppings.errors.0 }}</div>
        {% endif %}
    </div>
    <button type="submit" class="btn btn-primary">添加到订单</button>
</form>

<script>
const pizzaSelect = document.getElementById('id_pizza');
const toppingsSelect = document.getElementById('id_toppings');

// 初始化时设置默认规则(如果有初始披萨选择)
if (pizzaSelect.value) {
    fetchPizzaDetails(pizzaSelect.value);
}

// 监听披萨选择变化
pizzaSelect.addEventListener('change', function() {
    if (this.value) {
        fetchPizzaDetails(this.value);
    } else {
        // 重置配料字段
        toppingsSelect.disabled = false;
        toppingsSelect.multiple = true;
        toppingsSelect.required = false;
        toppingsSelect.selectedIndex = -1;
    }
});

// 发送AJAX请求获取披萨详情
function fetchPizzaDetails(pizzaId) {
    fetch(`/get-pizza-details/${pizzaId}/`)
        .then(response => response.json())
        .then(data => {
            // 根据披萨的addons类型调整配料字段
            switch(data.addons) {
                case 'Cheese':
                    toppingsSelect.disabled = true;
                    toppingsSelect.selectedIndex = -1;
                    toppingsSelect.required = false;
                    break;
                case '1 Topping':
                    toppingsSelect.disabled = false;
                    toppingsSelect.multiple = false; // 改为单选
                    toppingsSelect.required = true;
                    break;
                case '2 Toppings':
                case '3 Toppings':
                    toppingsSelect.disabled = false;
                    toppingsSelect.multiple = true;
                    toppingsSelect.required = true;
                    // 添加多选数量限制
                    const maxToppings = parseInt(data.addons[0]);
                    toppingsSelect.addEventListener('change', function() {
                        if (this.selectedOptions.length > maxToppings) {
                            alert(`该披萨最多只能选择${maxToppings}种配料`);
                            this.selectedOptions[this.selectedOptions.length - 1].selected = false;
                        }
                    });
                    break;
                case 'Special':
                    toppingsSelect.disabled = true;
                    // 自动选中固定配料(可选)
                    break;
            }
        })
        .catch(error => console.error('Error fetching pizza details:', error));
}
</script>

4. 添加视图和路由

创建AJAX视图获取披萨详情

# views.py
from django.http import JsonResponse
from .models import Pizza

def get_pizza_details(request, pizza_id):
    try:
        pizza = Pizza.objects.get(id=pizza_id)
        return JsonResponse({
            'addons': pizza.addons,
            'price': str(pizza.price)
        })
    except Pizza.DoesNotExist:
        return JsonResponse({'error': 'Pizza not found'}, status=404)

创建表单处理视图

def add_order_item(request):
    if request.method == 'POST':
        form = OrderItemForm(request.POST)
        if form.is_valid():
            # 这里替换为你的实际订单关联逻辑,比如获取当前用户的订单
            order = Order.objects.create(customer_name="测试客户") 
            order_item = form.save(commit=False)
            order_item.order = order
            order_item.save()
            form.save_m2m() # 保存多对多关系(配料)
            return redirect('order_detail', order_id=order.pk)
    else:
        form = OrderItemForm()
    return render(request, 'add_order_item.html', {'form': form})

def order_detail(request, order_id):
    order = Order.objects.get(pk=order_id)
    return render(request, 'order_detail.html', {'order': order})

添加路由

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('add-order-item/', views.add_order_item, name='add_order_item'),
    path('order/<int:order_id>/', views.order_detail, name='order_detail'),
    path('get-pizza-details/<int:pizza_id>/', views.get_pizza_details, name='get_pizza_details'),
    # 其他路由...
]

5. 测试功能

  1. 先在后台添加一些配料(比如Pepperoni、Mushroom、Onion等)。
  2. 访问/add-order-item/页面,选择不同类型的披萨,测试配料字段是否正确动态调整。
  3. 提交表单,验证后端是否正确验证配料数量,并且订单明细中是否正确记录了所选配料。

这样就完成了用户选择具体配料的功能,既满足了不同披萨类型的配料数量限制,又能准确记录用户的选择。

内容的提问来源于stack exchange,提问作者salmans911

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 18:07:56