我正在尝试在 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””
那么问题来了,如何让用户选择一种单位类型,然后在另一种形式中只显示该类型的单位呢?
您只需要一张表格。通过使用所选的
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'
附加到请求标头。通过阅读代码,其余部分几乎是不言自明的。