PAYATHON 2026

如何通过 Django field choices 更新数据库?

AI 小顾

结论

DetailView 只用于展示对象,不会处理表单提交。要修改 Lead.lead_owner,可以使用 ModelForm 和 UpdateView。

Django 会根据模型字段的 choices 生成 <select>,选中数据库中保存的当前值,并在表单验证通过后更新对应的 Lead 记录。一般不需要在模板中手动维护选项列表。

1. 创建 ModelForm

# forms.py

from django import forms

from .models import Lead


class LeadOwnerUpdateForm(forms.ModelForm):
    class Meta:
        model = Lead
        fields = ["lead_owner"]

lead_owner 定义了 choices,因此 Django 会将它渲染成下拉框。

2. 将 DetailView 改为 UpdateView

# views.py

from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse
from django.views.generic import UpdateView

from .forms import LeadOwnerUpdateForm
from .models import Lead


class LeadDetailView(LoginRequiredMixin, UpdateView):
    model = Lead
    form_class = LeadOwnerUpdateForm
    template_name = "lead-details.html"
    context_object_name = "lead"

    def get_success_url(self):
        return reverse("lead-detail", kwargs={"pk": self.object.pk})

UpdateView 会根据 URL 中的 pk 查询 Lead,用当前记录初始化表单,并选中当前的 lead_owner。收到 POST 请求后,它会验证数据并调用 form.save() 更新数据库,保存成功后再跳转回详情页面。

这里建议使用单数形式的 lead 作为上下文变量,因为页面展示的是一条记录,不是记录集合。

如果 agent 只能修改分配给自己的 lead,还需要限制查询范围,例如:

def get_queryset(self):
    return Lead.objects.filter(agent__user=self.request.user)

这段代码假设 Agent 模型通过名为 user 的字段关联用户。实际项目中应按具体的模型结构调整。

3. 配置 URL

# urls.py

from django.urls import path

from .views import LeadDetailView

urlpatterns = [
    path("leads/<int:pk>/", LeadDetailView.as_view(), name="lead-detail"),
]

访问 /leads/12/ 时,Django 会读取并更新主键为 12 的 Lead。

4. 在模板中显示表单

最简单的方式是直接渲染字段:

<form method="post">
    {% csrf_token %}

    {{ form.lead_owner.label_tag }}
    {{ form.lead_owner }}

    {% if form.lead_owner.errors %}
        <div class="field-errors">
            {{ form.lead_owner.errors }}
        </div>
    {% endif %}

    <button type="submit">保存</button>
</form>

生成的 HTML 大致如下:

<select name="lead_owner" id="id_lead_owner">
    <option value="Settlement, New">Settlement, New</option>
    <option value="Lead, Sales" selected>Lead, Sales</option>
</select>

Django 会根据当前的 Lead.lead_owner 添加 selected。提交表单后,UpdateView 会将新值保存到数据库。

5. 需要自定义 HTML 时手动遍历 choices

如果需要完全控制 <select> 的结构或 CSS,可以遍历表单字段的 choices:

<form method="post">
    {% csrf_token %}

    <label for="id_lead_owner">Lead owner</label>

    <select name="lead_owner" id="id_lead_owner">
        {% for value, label in form.lead_owner.field.choices %}
            <option
                value="{{ value }}"
                {% if form.lead_owner.value == value %}selected{% endif %}
            >
                {{ label }}
            </option>
        {% endfor %}
    </select>

    {% if form.lead_owner.errors %}
        {{ form.lead_owner.errors }}
    {% endif %}

    <button type="submit">保存</button>
</form>

这里应该比较:

form.lead_owner.value

不要只比较:

lead.lead_owner

表单验证失败后,form.lead_owner.value 会保留用户刚提交的值。页面重新显示时,下拉框不会跳回数据库中的旧值。

如果没有特殊的 HTML 结构要求,建议直接使用:

{{ form.lead_owner }}

这样可以少写一些模板逻辑,也能避免选中状态、错误回显和字段属性的处理出现偏差。

6. 关于“动态”的含义

当前模型中的 lead_owner 是带静态 choices 的 CharField:

choices_lead_owner = [
    ("Settlement, New", "Settlement, New"),
    ("Lead, Sales", "Lead, Sales"),
]

所以下拉框只会显示这两个选项。Corden, James、Rower, Charles 等人员不会自动出现,因为他们不在 choices_lead_owner 中。

如果 lead owner 指的是数据库中的 agent,更合适的做法是直接关联 Agent,而不是使用 CharField(choices=...):

class Lead(models.Model):
    lead_owner = models.ForeignKey(
        Agent,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name="owned_leads",
    )

表单仍然可以使用 ModelForm:

class LeadOwnerUpdateForm(forms.ModelForm):
    class Meta:
        model = Lead
        fields = ["lead_owner"]

此时 Django 会生成 ModelChoiceField,下拉框中的选项来自 Agent 数据库记录,显示文本由 Agent.__str__() 决定:

class Agent(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)

    def __str__(self):
        return f"{self.last_name}, {self.first_name}"

把现有的 lead_owner 从字符串字段改成外键会改变数据库结构,因此需要创建并执行 migration。如果数据库中已经有数据,还要设计数据迁移,把原来的字符串匹配到对应的 Agent 记录。

注意事项

  • POST 表单必须包含 {% csrf_token %}。
  • <select> 的 name 必须是 lead_owner,否则表单无法接收这个字段。
  • 提交值必须是 choices 元组中的第一个元素,例如 "Lead, Sales",不能提交未定义的值。
  • blank=True 表示表单允许留空。如果必须选择 owner,应移除 blank=True,并创建 migration。
  • 不要直接使用 Lead.objects.all() 暴露所有记录。CRM 通常需要按当前用户、团队或权限过滤 get_queryset(),防止用户修改 URL 中的 pk 后编辑无权访问的 lead。

备注:内容仅供参考。