django 中两种模型的一种形式

One form for two models in django

Django==3.1.7
django-crispy-forms==1.11.2

我有 2 个模型:Order 和 OrderList
Order 是 header,OrderList 是相关 Order

的表格部分
class Order(models.Model):
    print_number = models.PositiveIntegerField(
            verbose_name=_("Number"),
            default=get_todays_free_print_number,
        )
    # ... some other fields 

class OrderList(models.Model):
    order = models.ForeignKey(
            Order,
            blank=False,
            null=False,
            on_delete=models.CASCADE
        )
    item = models.ForeignKey(
            Item,
            verbose_name=_("item"),
            blank=True,
            null=True,
            on_delete=models.CASCADE
        )
    # ... some other OrderList fields

问题是如何创建包含两个模型的表单并提供将订单中的 OrderList 头寸添加到表单中的能力 并保存它们。

我做了什么:
forms.py - 我为 OrderList

使用了内联表单集工厂
from django.forms import ModelForm
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from .models import Order, OrderList

class OrderForm(ModelForm):
    class Meta:
        model = Order
        fields = [
            '__all__',
        ]

class OrderListForm(ModelForm):
    class Meta:
        model = OrderList
        fields = [
            '__all__',
        ]

class OrderListFormSetHelper(FormHelper):
    """Use class to display the formset as a table"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.template = 'bootstrap4/table_inline_formset.html'
        
        # I am not sure we should add a button here
        ####################################################
        self.add_input(Submit('submit', 'Submit',
                               css_class='btn btn-primary offset4'))

views.py

@login_required
def orders(request):

    template = f'{APP_NAME}/index.html'

    list_helper = OrderListFormSetHelper()

    list_formset = inlineformset_factory(Order,
                                         OrderList,
                                         OrderListForm,)

    if request.method == 'POST':

        form = OrderForm(request.POST, prefix="header")

        if form.is_valid() and list_formset.is_valid():
            order = form.save()

            order_list = list_formset.save(commit=False)
            order_list.order = order
            order_list.save()

            return HttpResponseRedirect(reverse('order_created'))

    else:  # all other methods means we should create a blank form
        form = OrderForm()

    
    return render(request, template, {'form': form,
                                      'list_form': list_formset,
                                      'list_helper': list_helper})

index.html

<form method="post">
    {% csrf_token %}
    {% crispy form  %}
    {% crispy list_form list_helper %}

    <!-- the button below doesn't make sense because it does nothing.
          the self.add_input in forms.py already adds a submit button.
               -->
    <button type="submit" class="btn btn-primary">
        {% translate "Send an order" %}
    </button>

</form>

生成的 html 呈现页面如下:

但是当我按下提交按钮时 它清理订单相关字段并将它们标记为空白

您使用 crispy 模板标签呈现表单。它使用 FormHelper class 来帮助呈现您的表单,默认情况下属性 form_tag 设置为 True 这使得它为您呈现 form 标签.这意味着你是 嵌套 表单标签,它不起作用并且在 HTML5 标准下是不可能的。您需要将此属性设置为 False 以防止出现这种情况:

class OrderForm(ModelForm):
    class Meta:
        model = Order
        fields = [
            '__all__',
        ]
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper(self) # Explicitly set helper to prevent automatic creation
        self.helper.form_tag = False # Don't render form tag
        self.helper.disable_csrf = True # Don't render CSRF token

接下来,在您在视图中制作的助手中,您还必须设置这些属性。此外,您所说的 list_formset 不是表单集的 instance 而是 class,因此您实际上需要实例化表单集 class 并使用它:

@login_required
def orders(request):

    template = f'{APP_NAME}/index.html'

    list_helper = OrderListFormSetHelper()
    list_helper.form_tag = False # Don't render form tag
    list_helper.disable_csrf = True # Don't render CSRF token

    OrderListFormSet = inlineformset_factory(Order,
                                         OrderList,
                                         OrderListForm,)

    if request.method == 'POST':

        form = OrderForm(request.POST, prefix="header")
        list_formset = OrderListFormSet(request.POST, instance=form.instance) # Instantiate formset

        if form.is_valid() and list_formset.is_valid():
            order = form.save()

            order_list = list_formset.save()
            # Remove below two line, have already instantiated formset with `form.instance` and called save without `commit=False`
            # order_list.order = order
            # order_list.save()

            return HttpResponseRedirect(reverse('order_created'))

    else:  # all other methods means we should create a blank form
        form = OrderForm()
        list_formset = OrderListFormSet(instance=form.instance) # Instantiate formset

    
    return render(request, template, {'form': form,
                                      'list_form': list_formset,
                                      'list_helper': list_helper})