如何在Django项目中做到在一种形式中选择一个选项后,另一种形式中的选项会缩小?

问题描述 投票:0回答:1

我正在尝试在 Django 中制作我的第一个项目,它是一个单位转换器。我想要一个表格,您可以在其中选择单位类型(如长度、面积、质量等)。选择类型后,在“到单位”和“从单位”字段中应该只显示相应类型的单位。

以下是我的文件内容:

models.py:

from django.db import models

class UnitType(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class Unit(models.Model):
    unitType = models.ForeignKey(UnitType, on_delete = models.CASCADE)
    name = models.CharField(max_length=50)
    conversion_factor = models.DecimalField(max_digits=10, decimal_places=4)

    def __str__(self):
        return self.name

    def convert_to(self, value, to_unit):
        if isinstance(value, (int, float)) and isinstance(to_unit, Unit):
            conversion_factor = self.conversion_factor / to_unit.conversion_factor
            converted_value = round(value * conversion_factor, 4)
            return converted_value
        else:
            raise ValueError("Invalid input for conversion.")

forms.py:

from django import forms
from .models import UnitType, Unit

class UnitTypeForm(forms.Form):
    unit_type = forms.ModelChoiceField(queryset=UnitType.objects.all())

class UnitConversionForm(forms.Form):
    quantity = forms.DecimalField()
    from_unit = forms.ModelChoiceField(queryset=Unit.objects.all())
    to_unit = forms.ModelChoiceField(queryset=Unit.objects.all())
    def __init__ (self, unit_type, *args, **kwargs):
        super(UnitConversionForm, self).__init__(*args, **kwargs)
        self.fields["from_unit"].queryset = Unit.objects.filter(unitType_id=unit_type)
        self.fields["to_unit"].queryset = Unit.objects.filter(unitType_id=unit_type)

views.py:

from django.shortcuts import render
from .forms import UnitTypeForm, UnitConversionForm
from .models import UnitType, Unit

def unit_converter(request, unit_type = None):
    if request.method == 'POST':
        typeForm = UnitTypeForm(request.POST)
        if typeForm.is_valid():
            unit_type = typeForm.cleaned_data['unit_type']
            form = UnitConversionForm(unit_type, request.POST)
            if form.is_valid():
                quantity = form.cleaned_data['quantity']
                from_unit = form.cleaned_data['from_unit']
                to_unit = form.cleaned_data['to_unit']
                from_unit_obj = Unit.objects.get(name=from_unit)
                to_unit_obj = Unit.objects.get(name=to_unit)
                result = from_unit_obj.convert_to(int(quantity), to_unit_obj)
            else:
                result = None
    else:
        typeForm = UnitTypeForm()
        form = UnitConversionForm(unit_type)
        result = None
    return render(request, 'unit_converter.html', {'typeForm': typeForm, 'form': form, 'result': result})

unit_converter.html:

{% load static %}
<!DOCTYPE html>
<html>

<head>
  <title>Unit converter</title>
  <link rel="stylesheet" href="{% static 'style.css' %}">
</head>

<body>
  <h1>Unit converter</h1>
  <form method="post">
      {% csrf_token %}
      {{ typeForm.as_p }}
      {{ form.as_p }}
    <input type="submit" value="Convert">
  </form>
  {% if result is not None %}
    <p>Conversion result: {{ result }}</p>
  {% endif %}

</body>

</html>

我所拥有的不起作用,因为在表单中选择单位类型后,转换表单的列表为空。但是,当我将 html 的表单部分修改为如下所示时:

<form method="post">
    {% csrf_token %}
    {{ typeForm.as_p }}
  <input type="submit" value="Choose">
</form>
  <form method="post">
      {% csrf_token %}
      {{ form.as_p }}
    <input type="submit" value="Convert">
  </form>

然后,单击“选择”按钮后,另一个表单中的选项列表应如常,但存在一些问题:表单上方显示“此字段为必填”消息,然后在完成第二个表单后,Django 给我错误: “/unit_converter/ 处出现 UnboundLocalError 无法访问未与值关联的局部变量“form””

那么问题来了,如何让用户选择一种单位类型,然后在另一种形式中只显示该类型的单位呢?

python django django-models django-forms
1个回答
0
投票

您只需要一张表格。通过使用所选的

unit type
唯一标识符向后端发送请求,通过它过滤
unit
,返回数据并操作 DOM 来显示它。

首先编写一个新视图,在不重新加载页面的情况下返回数据:

views.py

def unit_values(request):
    data = json.loads(request.body)
    unit_type = UnitType.objects.get(pk=data['unit_id'])
    units = list(Unit.objects.filter(unitType=unit_type).values("id", "name"))
    return JsonResponse(units, safe=False)

为其添加 URL 路径。我将其命名空间设置为

unit-values
。并使用 JavaScript 完成其余所有工作:

unit_converter.html

<body>
  <h1>Unit converter</h1>
  <form method="post" data-unit-values-url="{% url 'unit-values' %}">
    ...
  </form>
  {% if result is not None %}
  <p>Conversion result: {{ result }}</p>
  {% endif %}
  <script>
    function getCookie(name) {
      ...
    }

    const clearSelector = (selector) => {
      for (let i = selector.options.length; i >= 1; i--) {
        selector.remove(i);
      };
    }

    const populateSelector = (selector, data) => {
      data.forEach(param => {
        let opt = document.createElement('option');
        opt.value = param.id;
        opt.innerHTML = param.name;
        selector.appendChild(opt);
      });
    }

    const formElement = document.querySelector("form");
    const unitElement = document.getElementById("id_unit_type");
    const fromUnitElement = document.getElementById("id_from_unit");
    const toUnitElement = document.getElementById("id_to_unit");

    unitElement.addEventListener("change", async (event) => {
      const url = formElement.dataset.unitValuesUrl;
      const csrftoken = getCookie('csrftoken');
      const unitId = unitElement.value;
      const response = await fetch(url, {
        method: "POST",
        headers: {
          'X-CSRFToken': csrftoken,
          "Content-Type": "application/json"
        },
        body: JSON.stringify({ 'unit_id': unitId }),
      });

      const data = await response.json();

      clearSelector(fromUnitElement);
      clearSelector(toUnitElement);

      populateSelector(fromUnitElement, data);
      populateSelector(toUnitElement, data);
    });
  </script>
</body>

注意添加了指向新视图的 URL 解析器作为表单的数据属性。使用

getCookie
'X-CSRFToken'
附加到请求标头。通过阅读代码,其余部分几乎是不言自明的。

© www.soinside.com 2019 - 2024. All rights reserved.