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

Django中实现ForeignKey字段的文本搜索替代下拉框方案

实现Django航班预订网站的机场自动补全功能

下面提供两种可行的实现方案,分别适合快速集成和自定义开发场景:


方案一:使用django-select2快速实现(推荐)

django-select2是Django生态中成熟的自动补全组件,能快速为外键字段添加搜索提示功能,无需手动编写AJAX逻辑。

步骤1:安装依赖

pip install django-select2

步骤2:配置项目

在settings.py的INSTALLED_APPS中添加:

INSTALLED_APPS = [
    # 其他已有应用
    'django_select2',
]

步骤3:修改表单

替换原表单中的TextInput为Select2Widget,同时规范类名:

from django import forms
from .models import Flight
from django_select2.forms import Select2Widget

class CitySearchForm(forms.ModelForm):
    class Meta:
        model = Flight
        fields = ['origin', 'destination']
        widgets = {
            "origin": Select2Widget(attrs={'data-placeholder': '输入出发城市/机场代码'}),
            "destination": Select2Widget(attrs={'data-placeholder': '输入到达城市/机场代码'})
        }

步骤4:更新模板

在book.html中添加组件所需的静态资源,同时优化表单渲染:

<!DOCTYPE html>
<html>
<head>
    <title>预订航班</title>
    {{ form.media }} <!-- 自动加载select2的CSS和JS -->
</head>
<body>
    <form method='POST'> {% csrf_token %}
        <div style="margin: 10px 0;">
            {{ form.origin.label_tag }}
            {{ form.origin }}
        </div>
        <div style="margin: 10px 0;">
            {{ form.destination.label_tag }}
            {{ form.destination }}
        </div>
        <input type="submit" value='提交预订'/>
    </form>
</body>
</html>

步骤5:视图无需修改

保持原book视图逻辑即可,django-select2会自动处理后端搜索和AJAX请求。


方案二:原生JS+AJAX自定义实现

如果不想依赖第三方库,可以手动实现前端搜索提示和后端接口:

步骤1:修改表单结构

添加用于输入城市名称的字段,隐藏外键ID字段,并在clean方法中验证匹配机场:

from django import forms
from .models import Flight, Airport

class CitySearchForm(forms.ModelForm):
    origin_name = forms.CharField(label='出发城市', max_length=50)
    destination_name = forms.CharField(label='到达城市', max_length=50)

    class Meta:
        model = Flight
        fields = ['origin', 'destination']
        widgets = {
            "origin": forms.HiddenInput(),
            "destination": forms.HiddenInput()
        }

    def clean_origin_name(self):
        name = self.cleaned_data.get('origin_name')
        airports = Airport.objects.filter(city__icontains=name) | Airport.objects.filter(code__icontains=name)
        if not airports.exists():
            raise forms.ValidationError('未找到匹配的机场')
        # 若存在多个匹配,取第一个;如需精确匹配可调整查询条件
        self.cleaned_data['origin'] = airports.first()
        return name

    def clean_destination_name(self):
        name = self.cleaned_data.get('destination_name')
        airports = Airport.objects.filter(city__icontains=name) | Airport.objects.filter(code__icontains=name)
        if not airports.exists():
            raise forms.ValidationError('未找到匹配的机场')
        self.cleaned_data['destination'] = airports.first()
        return name

步骤2:添加后端搜索接口

在views.py中新增处理AJAX请求的视图:

from django.http import JsonResponse
from .models import Airport

def airport_search(request):
    query = request.GET.get('q', '')
    airports = Airport.objects.filter(city__icontains=query) | Airport.objects.filter(code__icontains=query)
    results = [{'id': airport.id, 'text': str(airport)} for airport in airports]
    return JsonResponse({'results': results})

步骤3:配置路由

在项目的urls.py中添加接口路由:

from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
    path('book/', views.book, name='book'),
    path('airport-search/', views.airport_search, name='airport_search'),
]

步骤4:编写前端自动补全逻辑

修改book.html,添加原生JS实现搜索提示:

<!DOCTYPE html>
<html>
<head>
    <title>预订航班</title>
    <style>
        .autocomplete-container {
            position: relative;
            margin: 10px 0;
        }
        .autocomplete-list {
            border: 1px solid #ddd;
            max-height: 200px;
            overflow-y: auto;
            position: absolute;
            top: 30px;
            left: 0;
            right: 0;
            background: white;
            z-index: 100;
        }
        .autocomplete-item {
            padding: 8px 12px;
            cursor: pointer;
        }
        .autocomplete-item:hover {
            background-color: #f5f5f5;
        }
    </style>
</head>
<body>
    <form method='POST'> {% csrf_token %}
        <div class="autocomplete-container">
            <label for="origin_name">出发城市:</label>
            <input type="text" id="origin_name" name="origin_name" placeholder="输入城市或机场代码">
            <div id="origin-autocomplete" class="autocomplete-list"></div>
            {{ form.origin }}
        </div>
        <div class="autocomplete-container">
            <label for="destination_name">到达城市:</label>
            <input type="text" id="destination_name" name="destination_name" placeholder="输入城市或机场代码">
            <div id="destination-autocomplete" class="autocomplete-list"></div>
            {{ form.destination }}
        </div>
        <input type="submit" value='提交预订'/>
    </form>

    <script>
        function initAutocomplete(inputId, hiddenId, autocompleteId) {
            const input = document.getElementById(inputId);
            const hiddenInput = document.getElementById(hiddenId);
            const autocompleteBox = document.getElementById(autocompleteId);

            input.addEventListener('input', debounce(function() {
                const query = input.value.trim();
                if (query.length < 2) {
                    autocompleteBox.innerHTML = '';
                    return;
                }

                fetch(`{% url 'airport_search' %}?q=${encodeURIComponent(query)}`)
                    .then(res => res.json())
                    .then(data => {
                        autocompleteBox.innerHTML = '';
                        data.results.forEach(item => {
                            const itemEl = document.createElement('div');
                            itemEl.className = 'autocomplete-item';
                            itemEl.textContent = item.text;
                            itemEl.addEventListener('click', () => {
                                input.value = item.text;
                                hiddenInput.value = item.id;
                                autocompleteBox.innerHTML = '';
                            });
                            autocompleteBox.appendChild(itemEl);
                        });
                    });
            }, 300));

            // 点击页面其他区域关闭下拉框
            document.addEventListener('click', (e) => {
                if (!input.contains(e.target) && !autocompleteBox.contains(e.target)) {
                    autocompleteBox.innerHTML = '';
                }
            });
        }

        // 防抖函数,减少请求频率
        function debounce(func, delay) {
            let timeoutId;
            return function(...args) {
                clearTimeout(timeoutId);
                timeoutId = setTimeout(() => func.apply(this, args), delay);
            };
        }

        // 初始化两个输入框的自动补全
        initAutocomplete('origin_name', '{{ form.origin.id_for_label }}', 'origin-autocomplete');
        initAutocomplete('destination_name', '{{ form.destination.id_for_label }}', 'destination-autocomplete');
    </script>
</body>
</html>

注意事项

  1. 方案一中的django-select2支持按城市名称和代码搜索,无需额外配置。
  2. 方案二中的debounce函数用于减少AJAX请求频率,避免输入时频繁调用接口。
  3. 若存在同名机场,可在后端查询中添加更多过滤条件(如国家、区域)来精确匹配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 04:05:26