Django 动态下拉菜单未填充

Django Dynamic Dropdown Menu Not Populating

我正在做一个项目,该项目的任务是实现一个动态下拉菜单,菜单中的选项将根据用户从上一个下拉菜单中选择的值而改变。

我在第一个下拉菜单中填充了下面 JSON 中 name 键的值(Network 1Network 2 等)。我的目标是第二个下拉列表将填充与用户在第一个下拉列表中选择的 name 值相对应的 zone 值列表。

因此,如果用户从第一个下拉列表中选择 Network 1,则第二个下拉列表将填充 network.1.private.zone 作为其唯一选项。如果用户从第一个下拉列表中选择 Network 2,则第二个下拉列表将填充 network.2.private.zone 作为其唯一选项,依此类推。

部分归功于此 guide,我在这方面取得了相当大的进步。我可以看到 AJAX 请求在浏览器中触发,当我 console.log 到浏览器时 urlnetworkId 值似乎是正确的。但我不明白为什么动态下拉菜单没有填充。这是页面的屏幕截图:

值得注意的是,我的项目没有任何模型;相反,我正在使用的所有数据都存储在 JSON 文件中,在上面引用并在下面看到。我错过了什么?

相关代码如下:

这是 JSON 文件:

[
  {
    "name": "Network 1",
    "onboard": {
      "format": "network",
      "base": "172.27.22.0",
      "mask": "255.255.255.0",
      "zones": ["network.1.onboard.zone"]
    },
    "private": {
      "format": "network",
      "base": "172.24.16.0",
      "mask": "255.255.252.0",
      "zones": ["network.1.private.zone"]
  },
    "public": {
      "format": "network",
      "base": "128.237.78.0",
      "mask": "255.255.255.0",
      "zones": ["network.1.public.zone"]
    }
  },
  {
    "name": "Network 2",
    "onboard": {
      "format": "network",
      "base": "172.27.23.0",
      "mask": "255.255.255.0",
      "zones": ["network.2.onboard.zone"]
    },
    "private": {
      "format": "network",
      "base": "172.24.234.0",
      "mask": "255.255.254.0",
      "zones": ["network.2.private.zone"]
    },
    "public": {
      "format": "network",
      "base": "172.24.234.0",
      "mask": "255.255.254.0",
      "zones": ["network.2.public.zone"]
    }
  },
  ...
]

utils.py(上面的 JSON 文件在此处引用为 WIRED_GROUPS)

...
def return_zones_by_network(network_name, network_group=WIRED_GROUPS, private=True):
    """
    Helper function that will eventually be used to construct the dynamic dropdown menu.
    **** WIRED_GROUPS is the JSON file referenced earlier in this post ****
    """
    network_zones = []
    for network in network_group:
        if network['name'] == network_name:
            if private:
                for zone in network['private']['zones']:
                    network_zones.append(zone)
            else:
                for zone in network['public']['zones']:
                    network_zones.append(zone)
    return network_zones
...

forms.py

class WiredRegistrationForm(Form):
    
    def __init__(self, groups, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['group'].choices = ((0, '---------',),)
        self.fields['group'].choices += tuple(
            (i+1, groups[i],) for i in range(len(groups))
        )
        self.fields['hostname_zones'].queryset = ((0, '---------',),)

    network = ChoiceField(
        # choices=wired_network_choices,
        choices=wired_network_choices,
        widget=Select(attrs={'class': 'form-control'})
    )

    hostname = CharField(
        label='Hostname',
        max_length=63,
        error_messages={'incomplete': 'Enter a hostname.'},
        validators=[HOSTNAME_VALIDATOR],
        widget=TextInput(attrs={
            'autocomplete': 'off',
            'autocapitalize': 'off'
        })
    )
    # this is the field that needs to be dynamically populated.
    hostname_zones = ChoiceField(
        choices=[],
        widget=Select(attrs={'class': 'form-control'})
    )

    mac_address = MACAddressField(label="MAC address")

    group = ChoiceField(
        widget=Select(attrs={'class': 'form-control'}),
    )

views.py

def load_hostname_zones(request):
    """
    A view to return a list of hostname zones for a given network.
    """
    # somehow get the network name that the user selected
    network_name = request.GET.get('network')
    network_zones = return_zones_by_network(
        network_name=network_name
    )
    return render(request, 'hosts/register/host_zones_dropdown_list_options.html', {'network_zones': network_zones})


def register(request):
    """
    The REGISTER view.
    """
    if is_requestor_wireless(request):
        return redirect('hosts:register_wireless')
    return redirect('hosts:register_dorm')


def register_wired(request):
    """
    The REGISTER WIRED view.
    """
    user = get_remote_user_from_request(request)

    if len(get_hosts_for_user(CONN, user)) >= MAX_REGISTRATIONS:
        msg = (
            'You can\'t register any more devices because you have too many '
            'currently registered to your Andrew ID. If you still need '
            'additional devices registered, please contact the '
            '<a href="https://www.cmu.edu/computing/support/" target="_blank">'
            'Help Desk</a> for assistance.'
        )
        return register_failure(request, msg)

    
    zone = ZONES['wired']['private']
    network_list = []
    for network in WIRED_GROUPS:
        network_list.append(network['name'])

    context = {
        'network_list': network_list,  # the list above
        'max_devices': MAX_REGISTRATIONS  # 100
        'zone': zone
    }

    groups = get_member_from_request(request)

    if request.method != 'POST':
        context['form'] = WiredRegistrationForm(groups)
        return render(request, 'hosts/register/wired.html', context)

    form = WiredRegistrationForm(groups, request.POST)
    context['form'] = form

    # checks if the form passes all the validation rules
    if form.is_valid():
        # access the user-input data (pretty sure the data will be a dict)
        # process the data... THESE ARE REFERENCED IN THE HTML TEMPLATE AS "form.hostname", "form.mac_address", etc.
        data = form.cleaned_data
        hostname = data['hostname']
        mac_address = data['mac_address']
        network_id = int(data['network'])
        # this code block references the datacubes library
        networks = Network.search(
            CONN,
            network=WIRED_GROUPS[network_id]['private']['cidr']
        )
        network = networks[0]
        zone = network['private']['zones']
        fqdn = f'{hostname}.{zone}'
        addr = network.next_available_ip()[0]
        ipv4addr = HostIpv4addr(CONN, {
            'ipv4addr': addr,
            'configure_for_dhcp': True,
            'mac': mac_address,
        })
        choice = form.cleaned_data['group']
        extattrs = {'suspend': 0}

        if int(choice) > 0:
            extattrs['group'] = groups[int(choice)-1]
        else:
            extattrs['andrewid'] = get_remote_user_from_request(request)
        try:
            host = Host.create(
                CONN, fqdn, ipv4addrs=[ipv4addr], extattrs=extattrs
            )
        except HTTPError:
            msg = (
                'A device with the same hostname or MAC address has '
                'already been registered.'
            )
            return register_failure(request, msg)
        grid(CONN).restartservices()
        return redirect('hosts:register_success', ref=host.objref)

    # renders the request to the hosts/register/WIRED page.
    return render(request, 'hosts/register/wired.html', context)

urls.py

app_name = 'hosts'
urlpatterns = [
    ...
    url(
        r'^register/$',
        views.register,
        name='register'
    ),
    url(
        r'^register/wired',
        views.register_wired,
        name='register_wired'
    ),
    url(
        r'^register/wired/load-hostname-zones',
        views.load_hostname_zones,
        name='register_wired_load_hostname_zones'
    ),
    ...
]

/templates/hosts/register/wired.html

{% extends 'hosts/register/base.html' %}
{% load static %}

{% block css %}
{{ block.super }}
<link href="{% static 'fonts/fontawesome/css/solid.min.css' %}" rel="stylesheet">
<link href="{% static 'fonts/fontawesome/css/fontawesome.min.css' %}" rel="stylesheet">
<link href="{% static 'css/group-hide.css' %}" rel="stylesheet">
{% endblock css %}

{% block breadcrumbs %}
{{ block.super }}
<span>Register Device</span>
<span> &#160;&#8250;&#160;</span>
<span>Wired Network</span>
{% endblock breadcrumbs %}

{% block content %}
<div class="boxes">
  <h1>Register Device - Wired Network</h1>
  <p>
    Students, faculty, and staff can complete this form to register a computer or device in a campus building. Up to {{ max_devices }}
    devices can be registered. Devices are registered with a <em>private IP</em> address, which provides additional
    security when accessing Internet resources. Contact the Help Center to request a public IP address for a device.
  </p>
  <p>
    <em>Note</em>: To register a device located in academic or administrative buildings not supported by this application as well as departmental
    computers/devices in residence halls, visit
    <a href="https://www.cmu.edu/computing/services/endpoint/network-access/wired/how-to/index.html" target="_blank">
      How to Connect to the Wired Network
    </a>.
  </p>
  <div>
    <form method="post" id="wiredRegistrationForm" action="{% url 'hosts:register_wired' %}" data-zones-url="{% url 'hosts:register_load_hostname_zones' %}" novalidate>{% csrf_token %}
{#    <form method="post" id="registerWiredForm" action="{% url 'hosts:register_wired' %}" hostname-zones-url="{% url 'hosts:load_hostname_zones' %}">{% csrf_token %}#}
{#    <form method="post" id="registerWiredForm" hostname-zones-url="{% url 'ajax_load_hostname_zones' %}">{% csrf_token %}#}

      <fieldset>
        {# network field #}
        <div>
          {{ form.network.label_tag }}
          {{ form.network }}
        </div>
        {# hostname field #}
        <div class="">
          {{ form.hostname.errors }}
          {{ form.hostname.label_tag }}
          <div>
            {{ form.hostname }}
{#            .{{ form.zone }}#}
            . {{ form.hostname_zones }}
            <p>
              A hostname is a nickname used to identify a device connected to the network. It can be comprised of any
              string of alphanumeric characters without spaces.
            </p>
          </div>
        </div>
        {# mac address field #}
        <div>
          {{ form.mac_address.errors }}
          {{ form.mac_address.label_tag }}
          <div>{{ form.mac_address }}</div>
          <p>
            A MAC address is a unique identifier assigned to a Network Interface Card (NIC) by the manufacturer. Visit
            <a href="https://www.cmu.edu/computing/services/endpoint/network-access/wireless/how-to/mac-address.html"
               target="_blank">Find My MAC Address</a> for help.
          </p>
        </div>
        <div>
          <input type="checkbox" class="groupCheckbox"/>
          <label>Register this device for a group or department.</label>
          <div class="groupSelector">
            <p>Group: {{ form.group }}</p>
          </div>
        </div>
{#        <div>#}
{#          {{ form.network.label_tag }}#}
{#          {{ form.network }}#}
{#        </div>#}
        <div>
          <div>
            <button type="reset" class="btn btn-secondary"><i class="fas fa-undo fa"></i> Reset</button>
            <button type="submit" class="btn btn-primary"><i class="fas fa-id-card fa"></i> Submit</button>
          </div>
        </div>
      </fieldset>
    </form>
    <script src="https://code.jquery.com/jquery-1.10.1.min.js"></script>
      <script>
        $("#id_network").change(function () {
          let url = $("#wiredRegistrationForm").attr('data-zones-url'); // get the URL of the "load_hostname_zones" view
          console.log(url);
          let networkId = $(this).val(); // get the selected hostname ID from the HTML input
          console.log(networkId); // this works (prints out 1, 2, 3, 4, etc., depending on what network you select)
            $.ajax({
              url: url,
              data: {
                'network': networkId // add the network id to the GET parameters (is this right???)
              },
              success: function (data) { // "data" is the return of the "load_hostname_zones" function
                $("#id_hostname_zones").html(data); // "data" is defined above...
              }
            });
        });
      </script>
  </div>
</div>
{% endblock content %}

{% block js %}
{{ block.super }}
<script src="{% static 'js/mac-address.js' %}" type="text/javascript"></script>
{% endblock js %}

/templates/hosts/register/host_zones_dropdown_list_options.html

{# This is only used to compose the tiny bit of HTML that is the hostname zone dropdown menu #}
<option value="">---------</option>
{% for hostname_zone in hostname_zones %}
{#<option value="{{ network_zones.index(network_zone) }}">{{ network_zone }}</option>#}
  <option value="{{ hostname_zone.pk }}">{{ hostname_zone.name }}</option>
{% endfor %}

所以我想通了。问题是我在 utils.py 文件中的辅助函数之间存在差异,参见此处:

def return_zones_by_network(network_name, network_group=WIRED_GROUPS, private=True):
    """
    Helper function that will eventually be used to construct the dynamic dropdown menu.
    **** WIRED_GROUPS is the JSON file referenced earlier in this post ****
    """
    network_zones = []
    for network in network_group:
        if network['name'] == network_name:
            if private:
                for zone in network['private']['zones']:
                    network_zones.append(zone)
            else:
                for zone in network['public']['zones']:
                    network_zones.append(zone)
    return network_zones  # returns a list, not a dict

/templates/hosts/register/host_zones_dropdown_list_options.html 中的小模板,见此处:

<option value="">---------</option>
{% for hostname_zone in hostname_zones %}
  <option value="{{ hostname_zone.pk }}">{{ hostname_zone.name }}</option>  # this line here
{% endfor %}

在模板中,我引用了每个 hostname_zonepkname)的属性,而实际上每个 hostname_zone 是列表中的字符串。我想我有点惊讶我没有通过引用 non-existent 属性得到类似属性错误的东西,但是下拉菜单正在动态填充,因为它现在应该是这样。

更正后的模板如下。希望这对以后的其他人有所帮助:

<option value="">---------</option>
{% for hostname_zone in hostname_zones %}
  <option value="">{{ hostname_zone }}</option>
{% endfor %}